From 3fc93e2c57f023838edc8e521f0a671ba23b02ea Mon Sep 17 00:00:00 2001 From: Olivia Godone-Maresca Date: Mon, 3 Apr 2023 17:39:34 -0400 Subject: [PATCH 01/92] Add a utility function to create tabs with lazy loading functionality --- ui/media/js/utils.js | 136 +++++++- ui/plugins/ui/merge.plugin.js | 464 ++++++++++++-------------- ui/plugins/ui/release-notes.plugin.js | 71 ++-- 3 files changed, 384 insertions(+), 287 deletions(-) diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 6eb0d643..f38cb5ac 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -683,7 +683,7 @@ class ServiceContainer { * @param {string} tag * @param {object} attributes * @param {string | Array} classes - * @param {string | HTMLElement | Array} + * @param {string | Node | Array} * @returns {HTMLElement} */ function createElement(tagName, attributes, classes, textOrElements) { @@ -699,7 +699,7 @@ function createElement(tagName, attributes, classes, textOrElements) { if (textOrElements) { const children = Array.isArray(textOrElements) ? textOrElements : [textOrElements] children.forEach(textOrElem => { - if (textOrElem instanceof HTMLElement) { + if (textOrElem instanceof Node) { element.appendChild(textOrElem) } else { element.appendChild(document.createTextNode(textOrElem)) @@ -708,3 +708,135 @@ function createElement(tagName, attributes, classes, textOrElements) { } return element } + +/** + * @typedef {object} TabOpenDetails + * @property {HTMLElement} contentElement + * @property {HTMLElement} labelElement + * @property {number} timesOpened + * @property {boolean} firstOpen + */ + +/** + * @typedef {object} CreateTabRequest + * @property {string} id + * @property {string | Node | (() => (string | Node))} label + * Label text or an HTML element + * @property {string} icon + * @property {string | Node | Promise | (() => (string | Node | Promise)) | undefined} content + * HTML string or HTML element + * @property {((TabOpenDetails, Event) => (undefined | string | Node | Promise)) | undefined} onOpen + * If an HTML string or HTML element is returned, then that will replace the tab content + * @property {string | undefined} css + */ + +/** + * @param {CreateTabRequest} request + */ + function createTab(request) { + if (!request?.id) { + console.error('createTab() error - id is required', Error().stack) + return + } + + if (!request.label) { + console.error('createTab() error - label is required', Error().stack) + return + } + + if (!request.icon) { + console.error('createTab() error - icon is required', Error().stack) + return + } + + if (!request.content && !request.onOpen) { + console.error('createTab() error - content or onOpen required', Error().stack) + return + } + + const tabsContainer = document.querySelector('.tab-container') + if (!tabsContainer) { + return + } + + const tabsContentWrapper = document.querySelector('#tab-content-wrapper') + if (!tabsContentWrapper) { + return + } + + console.debug('creating tab: ', request) + + if (request.css) { + document.querySelector('body').insertAdjacentElement( + 'beforeend', + createElement('style', { id: `tab-${request.id}-css` }, undefined, request.css), + ) + } + + const label = typeof request.label === 'function' ? request.label() : request.label + const labelElement = label instanceof Node ? label : createElement('span', undefined, undefined, label) + + const tab = createElement( + 'span', + { id: `tab-${request.id}`, 'data-times-opened': 0 }, + ['tab'], + createElement( + 'span', + undefined, + undefined, + [ + createElement( + 'i', + { style: 'margin-right: 0.25em' }, + ['fa-solid', `${request.icon.startsWith('fa-') ? '' : 'fa-'}${request.icon}`, 'icon'], + ), + labelElement, + ], + ) + ) + + + tabsContainer.insertAdjacentElement('beforeend', tab) + + const wrapper = createElement('div', { id: request.id }, ['tab-content-inner'], 'Loading..') + + const tabContent = createElement('div', { id: `tab-content-${request.id}` }, ['tab-content'], wrapper) + tabsContentWrapper.insertAdjacentElement('beforeend', tabContent) + + linkTabContents(tab) + + function replaceContent(resultFactory) { + if (resultFactory === undefined || resultFactory === null) { + return + } + const result = typeof resultFactory === 'function' ? resultFactory() : resultFactory + if (result instanceof Promise) { + result.then(replaceContent) + } else if (result instanceof Node) { + wrapper.replaceChildren(result) + } else { + wrapper.innerHTML = result + } + } + + replaceContent(request.content) + + tab.addEventListener('click', (e) => { + const timesOpened = +(tab.dataset.timesOpened || 0) + 1 + tab.dataset.timesOpened = timesOpened + + if (request.onOpen) { + const result = request.onOpen( + { + contentElement: wrapper, + labelElement, + timesOpened, + firstOpen: timesOpened === 1, + }, + e, + ) + + replaceContent(result) + } + }) +} diff --git a/ui/plugins/ui/merge.plugin.js b/ui/plugins/ui/merge.plugin.js index 6ff97286..4fce1d84 100644 --- a/ui/plugins/ui/merge.plugin.js +++ b/ui/plugins/ui/merge.plugin.js @@ -130,34 +130,11 @@ } drawDiagram(fn) } - - /////////////////////// Tab implementation - document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` - - Merge models - - `) - - document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', ` -
-
- Loading.. -
-
- `) - - const tabMerge = document.querySelector('#tab-merge') - if (tabMerge) { - linkTabContents(tabMerge) - } - const merge = document.querySelector('#merge') - if (!merge) { - // merge tab not found, dont exec plugin code. - return - } - - document.querySelector('body').insertAdjacentHTML('beforeend', ` - - `) - - merge.innerHTML = ` -
-
-

- -

- -

-

Important: Please merge models of similar type.
For e.g. SD 1.4 models with only SD 1.4/1.5 models,
SD 2.0 with SD 2.0-type, and SD 2.1 with SD 2.1-type models.

-
- - - - - - - - - - - - - -
Base name of the output file.
Mix ratio and file suffix will be appended to this.
- Image generation uses fp16, so it's a good choice.
Use fp32 if you want to use the result models for more mixes
-
-
-
-
-

-
-
-
-
-
- - Make a single file - - - Make multiple variations - -
-
-
-
- Saves a single merged model file, at the specified merge ratio.

- - - % - Model A's contribution to the mix. The rest will be from Model B. + }`, + content: ` +
+
+

+ +

+ +

+

Important: Please merge models of similar type.
For e.g. SD 1.4 models with only SD 1.4/1.5 models,
SD 2.0 with SD 2.0-type, and SD 2.1 with SD 2.1-type models.

+
+ + + + + + + + + + + + + +
Base name of the output file.
Mix ratio and file suffix will be appended to this.
+ Image generation uses fp16, so it's a good choice.
Use fp32 if you want to use the result models for more mixes
+
+
+
+
+

+
+
+
+
+
+ + Make a single file + + + Make multiple variations + +
+
+
+
+ Saves a single merged model file, at the specified merge ratio.

+ + + % + Model A's contribution to the mix. The rest will be from Model B. +
-
-
-
- Saves multiple variations of the model, at different merge ratios.
Each variation will be saved as a separate file.


- - - - - - - - - - - - - -
Number of models to create
% Smallest share of model A in the mix
% Share of model A added into the mix per step
Sigmoid function to be applied to the model share before mixing
-
- Preview of variation ratios:
- +
+
+ Saves multiple variations of the model, at different merge ratios.
Each variation will be saved as a separate file.


+ + + + + + + + + + + + + +
Number of models to create
% Smallest share of model A in the mix
% Share of model A added into the mix per step
Sigmoid function to be applied to the model share before mixing
+
+ Preview of variation ratios:
+ +
-
-
-
-
- -
-
` - - const tabSettingsSingle = document.querySelector('#tab-merge-opts-single') - const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch') - linkTabContents(tabSettingsSingle) - linkTabContents(tabSettingsBatch) - - console.log('Activate') - let mergeModelAField = new ModelDropdown(document.querySelector('#mergeModelA'), 'stable-diffusion') - let mergeModelBField = new ModelDropdown(document.querySelector('#mergeModelB'), 'stable-diffusion') - updateChart() - - // slider - const singleMergeRatioField = document.querySelector('#single-merge-ratio') - const singleMergeRatioSlider = document.querySelector('#single-merge-ratio-slider') - - function updateSingleMergeRatio() { - singleMergeRatioField.value = singleMergeRatioSlider.value / 10 - singleMergeRatioField.dispatchEvent(new Event("change")) - } - - function updateSingleMergeRatioSlider() { - if (singleMergeRatioField.value < 0) { - singleMergeRatioField.value = 0 - } else if (singleMergeRatioField.value > 100) { - singleMergeRatioField.value = 100 - } - - singleMergeRatioSlider.value = singleMergeRatioField.value * 10 - singleMergeRatioSlider.dispatchEvent(new Event("change")) - } - - singleMergeRatioSlider.addEventListener('input', updateSingleMergeRatio) - singleMergeRatioField.addEventListener('input', updateSingleMergeRatioSlider) - updateSingleMergeRatio() - - document.querySelector('.merge-config').addEventListener('change', updateChart) - - document.querySelector('#merge-button').addEventListener('click', async function(e) { - // Build request template - let model0 = mergeModelAField.value - let model1 = mergeModelBField.value - let request = { model0: model0, model1: model1 } - request['use_fp16'] = document.querySelector('#merge-fp').value == 'fp16' - let iterations = document.querySelector('#merge-count').value>>0 - let start = parseFloat( document.querySelector('#merge-start').value ) - let step = parseFloat( document.querySelector('#merge-step').value ) - - if (isTabActive(tabSettingsSingle)) { - start = parseFloat(singleMergeRatioField.value) - step = 0 - iterations = 1 - addLogMessage(`merge ratio = ${start}%`) - } else { - addLogMessage(`start = ${start}%`) - addLogMessage(`step = ${step}%`) - } - - if (start + (iterations-1) * step >= 100) { - addLogMessage('Aborting: maximum ratio is ≥ 100%') - addLogMessage('Reduce the number of variations or the step size') - addLogSeparator() - document.querySelector('#merge-count').focus() - return - } - - if (document.querySelector('#merge-filename').value == "") { - addLogMessage('Aborting: No output file name specified') - addLogSeparator() - document.querySelector('#merge-filename').focus() - return - } - - // Disable merge button - e.target.disabled=true - e.target.classList.add('disabled') - let cursor = $("body").css("cursor"); - let label = document.querySelector('#merge-button').innerHTML - $("body").css("cursor", "progress"); - document.querySelector('#merge-button').innerHTML = 'Merging models ...' - - addLogMessage("Merging models") - addLogMessage("Model A: "+model0) - addLogMessage("Model B: "+model1) - - // Batch main loop - for (let i=0; i +
+
+ +
+
`, + onOpen: ({ firstOpen }) => { + if (!firstOpen) { + return } - addLogMessage(`merging batch job ${i+1}/${iterations}, alpha = ${alpha.toFixed(5)}...`) - request['out_path'] = document.querySelector('#merge-filename').value - request['out_path'] += '-' + alpha.toFixed(5) + '.' + document.querySelector('#merge-format').value - addLogMessage(`  filename: ${request['out_path']}`) + const tabSettingsSingle = document.querySelector('#tab-merge-opts-single') + const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch') + linkTabContents(tabSettingsSingle) + linkTabContents(tabSettingsBatch) - request['ratio'] = alpha - let res = await fetch('/model/merge', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(request) }) - const data = await res.json(); - addLogMessage(JSON.stringify(data)) - } - addLogMessage("Done. The models have been saved to your models/stable-diffusion folder.") - addLogSeparator() - // Re-enable merge button - $("body").css("cursor", cursor); - document.querySelector('#merge-button').innerHTML = label - e.target.disabled=false - e.target.classList.remove('disabled') + console.log('Activate') + let mergeModelAField = new ModelDropdown(document.querySelector('#mergeModelA'), 'stable-diffusion') + let mergeModelBField = new ModelDropdown(document.querySelector('#mergeModelB'), 'stable-diffusion') + updateChart() - // Update model list - stableDiffusionModelField.innerHTML = '' - vaeModelField.innerHTML = '' - hypernetworkModelField.innerHTML = '' - await getModels() + // slider + const singleMergeRatioField = document.querySelector('#single-merge-ratio') + const singleMergeRatioSlider = document.querySelector('#single-merge-ratio-slider') + + function updateSingleMergeRatio() { + singleMergeRatioField.value = singleMergeRatioSlider.value / 10 + singleMergeRatioField.dispatchEvent(new Event("change")) + } + + function updateSingleMergeRatioSlider() { + if (singleMergeRatioField.value < 0) { + singleMergeRatioField.value = 0 + } else if (singleMergeRatioField.value > 100) { + singleMergeRatioField.value = 100 + } + + singleMergeRatioSlider.value = singleMergeRatioField.value * 10 + singleMergeRatioSlider.dispatchEvent(new Event("change")) + } + + singleMergeRatioSlider.addEventListener('input', updateSingleMergeRatio) + singleMergeRatioField.addEventListener('input', updateSingleMergeRatioSlider) + updateSingleMergeRatio() + + document.querySelector('.merge-config').addEventListener('change', updateChart) + + document.querySelector('#merge-button').addEventListener('click', async function(e) { + // Build request template + let model0 = mergeModelAField.value + let model1 = mergeModelBField.value + let request = { model0: model0, model1: model1 } + request['use_fp16'] = document.querySelector('#merge-fp').value == 'fp16' + let iterations = document.querySelector('#merge-count').value>>0 + let start = parseFloat( document.querySelector('#merge-start').value ) + let step = parseFloat( document.querySelector('#merge-step').value ) + + if (isTabActive(tabSettingsSingle)) { + start = parseFloat(singleMergeRatioField.value) + step = 0 + iterations = 1 + addLogMessage(`merge ratio = ${start}%`) + } else { + addLogMessage(`start = ${start}%`) + addLogMessage(`step = ${step}%`) + } + + if (start + (iterations-1) * step >= 100) { + addLogMessage('Aborting: maximum ratio is ≥ 100%') + addLogMessage('Reduce the number of variations or the step size') + addLogSeparator() + document.querySelector('#merge-count').focus() + return + } + + if (document.querySelector('#merge-filename').value == "") { + addLogMessage('Aborting: No output file name specified') + addLogSeparator() + document.querySelector('#merge-filename').focus() + return + } + + // Disable merge button + e.target.disabled=true + e.target.classList.add('disabled') + let cursor = $("body").css("cursor"); + let label = document.querySelector('#merge-button').innerHTML + $("body").css("cursor", "progress"); + document.querySelector('#merge-button').innerHTML = 'Merging models ...' + + addLogMessage("Merging models") + addLogMessage("Model A: "+model0) + addLogMessage("Model B: "+model1) + + // Batch main loop + for (let i=0; iDone. The models have been saved to your models/stable-diffusion folder.") + addLogSeparator() + // Re-enable merge button + $("body").css("cursor", cursor); + document.querySelector('#merge-button').innerHTML = label + e.target.disabled=false + e.target.classList.remove('disabled') + + // Update model list + stableDiffusionModelField.innerHTML = '' + vaeModelField.innerHTML = '' + hypernetworkModelField.innerHTML = '' + await getModels() + }) + }, }) })() diff --git a/ui/plugins/ui/release-notes.plugin.js b/ui/plugins/ui/release-notes.plugin.js index da7b79de..cb4db5e2 100644 --- a/ui/plugins/ui/release-notes.plugin.js +++ b/ui/plugins/ui/release-notes.plugin.js @@ -9,56 +9,41 @@ } } - document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` - - What's new? - - `) - - document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', ` -
-
- Loading.. -
-
- `) - - const tabNews = document.querySelector('#tab-news') - if (tabNews) { - linkTabContents(tabNews) - } - const news = document.querySelector('#news') - if (!news) { - // news tab not found, dont exec plugin code. - return - } - - document.querySelector('body').insertAdjacentHTML('beforeend', ` - - `) + `, + onOpen: async ({ firstOpen }) => { + if (firstOpen) { + const loadMarkedScriptPromise = loadScript('/media/js/marked.min.js') - loadScript('/media/js/marked.min.js').then(async function() { - let appConfig = await fetch('/get/app_config') - if (!appConfig.ok) { - console.error('[release-notes] Failed to get app_config.') - return - } - appConfig = await appConfig.json() + let appConfig = await fetch('/get/app_config') + if (!appConfig.ok) { + console.error('[release-notes] Failed to get app_config.') + return + } + appConfig = await appConfig.json() + + const updateBranch = appConfig.update_branch || 'main' + + let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/${updateBranch}/CHANGES.md`) + if (!releaseNotes.ok) { + console.error('[release-notes] Failed to get CHANGES.md.') + return + } + releaseNotes = await releaseNotes.text() - const updateBranch = appConfig.update_branch || 'main' + await loadMarkedScriptPromise - let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/${updateBranch}/CHANGES.md`) - if (!releaseNotes.ok) { - console.error('[release-notes] Failed to get CHANGES.md.') - return - } - releaseNotes = await releaseNotes.text() - news.innerHTML = marked.parse(releaseNotes) + return marked.parse(releaseNotes) + } + }, }) })() \ No newline at end of file From 9c091a9edf01c98d4bdd1828ac0b8a614817418a Mon Sep 17 00:00:00 2001 From: Olivia Godone-Maresca Date: Thu, 6 Apr 2023 16:37:17 -0400 Subject: [PATCH 02/92] Fix JSDoc --- ui/media/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 5599bae4..8421bfa0 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -711,7 +711,7 @@ function createElement(tagName, attributes, classes, textOrElements) { return element } -/* +/** * Add a listener for arrays * @param {keyof Array} method * @param {(args) => {}} callback From c086098af12531c4c8dcce2d26c57d5a58582bba Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 8 Apr 2023 11:11:25 +0530 Subject: [PATCH 03/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51ba812a..4d73d311 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Easy Diffusion 2.5 -### The easiest way to install and use [Stable Diffusion](https://github.com/CompVis/stable-diffusion) on your own computer. +### The easiest way to install and use [Stable Diffusion](https://github.com/CompVis/stable-diffusion) on your computer. Does not require technical knowledge, does not require pre-installed software. 1-click install, powerful features, friendly community. From 5eec05c0c45af4d091675c7869e61c90d2d04efb Mon Sep 17 00:00:00 2001 From: JeLuF Date: Fri, 21 Apr 2023 00:09:27 +0200 Subject: [PATCH 04/92] Don't write config.bat and config.sh any more --- scripts/get_config.py | 45 ++++++++++++++++++++++++++++++++++++++++ scripts/on_env_start.bat | 11 ++++++++++ scripts/on_env_start.sh | 6 ++++++ scripts/on_sd_start.bat | 17 ++++++++++++--- scripts/on_sd_start.sh | 11 +++++++++- ui/easydiffusion/app.py | 45 ---------------------------------------- 6 files changed, 86 insertions(+), 49 deletions(-) create mode 100644 scripts/get_config.py diff --git a/scripts/get_config.py b/scripts/get_config.py new file mode 100644 index 00000000..02523364 --- /dev/null +++ b/scripts/get_config.py @@ -0,0 +1,45 @@ +import os +import argparse + +# The config file is in the same directory as this script +config_directory = os.path.dirname(__file__) +config_yaml = os.path.join(config_directory, "config.yaml") +config_json = os.path.join(config_directory, "config.json") + +parser = argparse.ArgumentParser(description='Get values from config file') +parser.add_argument('--default', dest='default', action='store', + help='default value, to be used if the setting is not defined in the config file') +parser.add_argument('key', metavar='key', nargs='+', + help='config key to return') + +args = parser.parse_args() + + +if os.path.isfile(config_yaml): + import yaml + with open(config_yaml, 'r') as configfile: + try: + config = yaml.safe_load(configfile) + except Exception as e: + print(e) + exit() +elif os.path.isfile(config_json): + import json + with open(config_json, 'r') as configfile: + try: + config = json.load(configfile) + except Exception as e: + print(e) + exit() +else: + config = {} + +for k in args.key: + if k in config: + config = config[k] + else: + if args.default != None: + print(args.default) + exit() + +print(config) diff --git a/scripts/on_env_start.bat b/scripts/on_env_start.bat index ee702bb5..44144cfa 100644 --- a/scripts/on_env_start.bat +++ b/scripts/on_env_start.bat @@ -12,6 +12,16 @@ if exist "scripts\user_config.bat" ( @call scripts\user_config.bat ) +if exist "stable-diffusion\env" ( + @set PYTHONPATH=%PYTHONPATH%;%cd%\stable-diffusion\env\lib\site-packages +) + +if exist "scripts\get_config.py" ( + @FOR /F "tokens=* USEBACKQ" %%F IN (`python scripts\get_config.py --default=main update_branch`) DO ( + @SET update_branch=%%F + ) +) + if "%update_branch%"=="" ( set update_branch=main ) @@ -58,6 +68,7 @@ if "%update_branch%"=="" ( @copy sd-ui-files\scripts\on_sd_start.bat scripts\ /Y @copy sd-ui-files\scripts\check_modules.py scripts\ /Y @copy sd-ui-files\scripts\check_models.py scripts\ /Y +@copy sd-ui-files\scripts\get_config.py scripts\ /Y @copy "sd-ui-files\scripts\Start Stable Diffusion UI.cmd" . /Y @copy "sd-ui-files\scripts\Developer Console.cmd" . /Y diff --git a/scripts/on_env_start.sh b/scripts/on_env_start.sh index 4e73ca4e..3d4d990d 100755 --- a/scripts/on_env_start.sh +++ b/scripts/on_env_start.sh @@ -12,6 +12,11 @@ if [ -f "scripts/user_config.sh" ]; then source scripts/user_config.sh fi +export PYTHONPATH=$(pwd)/installer_files/env/lib/python3.8/site-packages:$(pwd)/stable-diffusion/env/lib/python3.8/site-packages + +if [ -f "scripts/get_config.py" ]; then + export update_branch="$( python scripts/get_config.py --default=main update_branch )" +fi if [ "$update_branch" == "" ]; then export update_branch="main" @@ -44,6 +49,7 @@ cp sd-ui-files/scripts/on_sd_start.sh scripts/ cp sd-ui-files/scripts/bootstrap.sh scripts/ cp sd-ui-files/scripts/check_modules.py scripts/ cp sd-ui-files/scripts/check_models.py scripts/ +cp sd-ui-files/scripts/get_config.py scripts/ cp sd-ui-files/scripts/start.sh . cp sd-ui-files/scripts/developer_console.sh . cp sd-ui-files/scripts/functions.sh scripts/ diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index e0b8c5fb..e5d94d2e 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -104,14 +104,25 @@ call python --version @cd .. @set SD_UI_PATH=%cd%\ui + +@FOR /F "tokens=* USEBACKQ" %%F IN (`python scripts\get_config.py --default=9000 net listen_port`) DO ( + @SET ED_BIND_PORT=%%F +) + +@FOR /F "tokens=* USEBACKQ" %%F IN (`python scripts\get_config.py --default=False net listen_to_network`) DO ( + if "%%F" EQU "True" ( + @SET ED_BIND_IP=0.0.0.0 + ) else ( + @SET ED_BIND_IP=127.0.0.1 + ) +) + @cd stable-diffusion @rem set any overrides set HF_HUB_DISABLE_SYMLINKS_WARNING=true -@if NOT DEFINED SD_UI_BIND_PORT set SD_UI_BIND_PORT=9000 -@if NOT DEFINED SD_UI_BIND_IP set SD_UI_BIND_IP=0.0.0.0 -@uvicorn main:server_api --app-dir "%SD_UI_PATH%" --port %SD_UI_BIND_PORT% --host %SD_UI_BIND_IP% --log-level error +@uvicorn main:server_api --app-dir "%SD_UI_PATH%" --port %ED_BIND_PORT% --host %ED_BIND_IP% --log-level error @pause diff --git a/scripts/on_sd_start.sh b/scripts/on_sd_start.sh index 858fa768..f0eeb743 100755 --- a/scripts/on_sd_start.sh +++ b/scripts/on_sd_start.sh @@ -74,8 +74,17 @@ python --version cd .. export SD_UI_PATH=`pwd`/ui +export ED_BIND_PORT="$( python scripts/get_config.py --default=9000 net listen_port )" +case "$( python scripts/get_config.py --default=False net listen_to_network )" in + "True") + export ED_BIND_IP=0.0.0.0 + ;; + "False") + export ED_BIND_IP=127.0.0.1 + ;; +esac cd stable-diffusion -uvicorn main:server_api --app-dir "$SD_UI_PATH" --port ${SD_UI_BIND_PORT:-9000} --host ${SD_UI_BIND_IP:-0.0.0.0} --log-level error +uvicorn main:server_api --app-dir "$SD_UI_PATH" --port "$ED_BIND_PORT" --host "$ED_BIND_IP" --log-level error read -p "Press any key to continue" diff --git a/ui/easydiffusion/app.py b/ui/easydiffusion/app.py index 83bb08c1..12f10a5a 100644 --- a/ui/easydiffusion/app.py +++ b/ui/easydiffusion/app.py @@ -101,51 +101,6 @@ def setConfig(config): except: log.error(traceback.format_exc()) - try: # config.bat - config_bat_path = os.path.join(CONFIG_DIR, "config.bat") - config_bat = [] - - if "update_branch" in config: - config_bat.append(f"@set update_branch={config['update_branch']}") - - config_bat.append(f"@set SD_UI_BIND_PORT={config['net']['listen_port']}") - bind_ip = "0.0.0.0" if config["net"]["listen_to_network"] else "127.0.0.1" - config_bat.append(f"@set SD_UI_BIND_IP={bind_ip}") - - # Preserve these variables if they are set - for var in PRESERVE_CONFIG_VARS: - if os.getenv(var) is not None: - config_bat.append(f"@set {var}={os.getenv(var)}") - - if len(config_bat) > 0: - with open(config_bat_path, "w", encoding="utf-8") as f: - f.write("\n".join(config_bat)) - except: - log.error(traceback.format_exc()) - - try: # config.sh - config_sh_path = os.path.join(CONFIG_DIR, "config.sh") - config_sh = ["#!/bin/bash"] - - if "update_branch" in config: - config_sh.append(f"export update_branch={config['update_branch']}") - - config_sh.append(f"export SD_UI_BIND_PORT={config['net']['listen_port']}") - bind_ip = "0.0.0.0" if config["net"]["listen_to_network"] else "127.0.0.1" - config_sh.append(f"export SD_UI_BIND_IP={bind_ip}") - - # Preserve these variables if they are set - for var in PRESERVE_CONFIG_VARS: - if os.getenv(var) is not None: - config_bat.append(f'export {var}="{shlex.quote(os.getenv(var))}"') - - if len(config_sh) > 1: - with open(config_sh_path, "w", encoding="utf-8") as f: - f.write("\n".join(config_sh)) - except: - log.error(traceback.format_exc()) - - def save_to_config(ckpt_model_name, vae_model_name, hypernetwork_model_name, vram_usage_level): config = getConfig() if "model" not in config: From 3d740555c3397ad3cea03c380f36602da7038cbc Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 22 Apr 2023 14:54:52 +0530 Subject: [PATCH 05/92] Force mac to downgrade from torch 2.0 --- scripts/check_modules.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 8ef43b09..6d39fcfc 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -47,6 +47,11 @@ def install(module_name: str, module_version: str): module_version = "1.13.1+rocm5.2" elif module_name == "torchvision": module_version = "0.14.1+rocm5.2" + elif os_name == "Darwin": + if module_name == "torch": + module_version = "1.13.1" + elif module_name == "torchvision": + module_version = "0.14.1" install_cmd = f"python -m pip install --upgrade {module_name}=={module_version}" if index_url: @@ -70,6 +75,10 @@ def init(): if module_name in ("torch", "torchvision"): if version(module_name) is None: # allow any torch version requires_install = True + elif os_name == "Darwin" and ( # force mac to downgrade from torch 2.0 + version("torch").startswith("2.") or version("torchvision").startswith("0.15.") + ): + requires_install = True elif version(module_name) not in allowed_versions: requires_install = True From 5acf5949a6ebab3d053e4c203f5e466b28d71178 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 22 Apr 2023 15:42:24 +0530 Subject: [PATCH 06/92] sdkit 1.0.81 - use tf32 = True for ampere GPUs --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 6d39fcfc..38545715 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.80", + "sdkit": "1.0.81", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From d7b91db204bfc3ed06dec777c9a4050cb247dcae Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 22 Apr 2023 15:47:26 +0530 Subject: [PATCH 07/92] changelog --- ui/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 404d88af..3ba84e1a 100644 --- a/ui/index.html +++ b/ui/index.html @@ -30,7 +30,7 @@

Easy Diffusion - v2.5.33 + v2.5.34

From 4ef10222e116ae65423b03f00390f841a705d118 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 22 Apr 2023 15:48:03 +0530 Subject: [PATCH 08/92] changelog --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 7e61b5aa..a91201f9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,7 +21,10 @@ 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.34 - 22 Apr 2023 - Set `allow_tf32` to `True` for faster performance on NVIDIA graphics cards with the Ampere architecture. +* 2.5.33 - 21 Apr 2023 - Install PyTorch 2.0 on new installations (on Windows and Linux). * 2.5.32 - 19 Apr 2023 - Automatically check for black images, and set full-precision if necessary (for attn). This means custom models based on Stable Diffusion v2.1 will just work, without needing special command-line arguments or editing of yaml config files. +* 2.5.32 - 18 Apr 2023 - Automatic support for AMD graphics cards on Linux. Thanks @DianaNites and @JeLuf. * 2.5.31 - 10 Apr 2023 - Reduce VRAM usage while upscaling. * 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. From 1f4e4d8d8211393dfbd201d43d2881c97c257792 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 22 Apr 2023 19:43:15 +0530 Subject: [PATCH 09/92] change --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a91201f9..f3fc0f94 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,7 +21,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog -* 2.5.34 - 22 Apr 2023 - Set `allow_tf32` to `True` for faster performance on NVIDIA graphics cards with the Ampere architecture. +* 2.5.34 - 22 Apr 2023 - Nothing, just keeping this line warm. * 2.5.33 - 21 Apr 2023 - Install PyTorch 2.0 on new installations (on Windows and Linux). * 2.5.32 - 19 Apr 2023 - Automatically check for black images, and set full-precision if necessary (for attn). This means custom models based on Stable Diffusion v2.1 will just work, without needing special command-line arguments or editing of yaml config files. * 2.5.32 - 18 Apr 2023 - Automatic support for AMD graphics cards on Linux. Thanks @DianaNites and @JeLuf. From 36344732ac24c1f3ed4c1c606111548c33a94bf7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 22 Apr 2023 19:47:19 +0530 Subject: [PATCH 10/92] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4d73d311..cd831c05 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ Does not require technical knowledge, does not require pre-installed software. 1 Click the download button for your operating system:

- - - + + +

The installer will take care of whatever is needed. If you face any problems, you can join the friendly [Discord community](https://discord.com/invite/u9yhsFmEkB) and ask for assistance. From ce950728450168a7c8b4554604ad3ee72af8ddb9 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 22 Apr 2023 19:53:13 +0530 Subject: [PATCH 11/92] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cd831c05..4d73d311 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ Does not require technical knowledge, does not require pre-installed software. 1 Click the download button for your operating system:

- - - + + +

The installer will take care of whatever is needed. If you face any problems, you can join the friendly [Discord community](https://discord.com/invite/u9yhsFmEkB) and ask for assistance. From bb607927d09dd05257e677570e6a340b3d3e0c6b Mon Sep 17 00:00:00 2001 From: JeLuF Date: Sun, 23 Apr 2023 12:54:20 +0200 Subject: [PATCH 12/92] Stop messing with %USERPROFILE% Set HF_HOME, so that the models don't get downloaded again. --- scripts/on_sd_start.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index d8c6f763..c3bbb576 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -8,7 +8,7 @@ @copy sd-ui-files\scripts\check_models.py scripts\ /Y if exist "%cd%\profile" ( - set USERPROFILE=%cd%\profile + set HF_HOME=%cd%\profile\.cache\huggingface ) @rem set the correct installer path (current vs legacy) From 6fbb24ae3d664d359cfdbda6d596a9c0ab62d83e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 24 Apr 2023 14:30:52 +0530 Subject: [PATCH 13/92] Revert "Stop messing with %USERPROFILE%" --- scripts/on_sd_start.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index c3bbb576..d8c6f763 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -8,7 +8,7 @@ @copy sd-ui-files\scripts\check_models.py scripts\ /Y if exist "%cd%\profile" ( - set HF_HOME=%cd%\profile\.cache\huggingface + set USERPROFILE=%cd%\profile ) @rem set the correct installer path (current vs legacy) From 3ae851ab1f59908f12498fa1fc8503f459bd5d19 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 24 Apr 2023 14:32:18 +0530 Subject: [PATCH 14/92] Revert "Revert "Stop messing with %USERPROFILE%"" --- scripts/on_sd_start.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index d8c6f763..c3bbb576 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -8,7 +8,7 @@ @copy sd-ui-files\scripts\check_models.py scripts\ /Y if exist "%cd%\profile" ( - set USERPROFILE=%cd%\profile + set HF_HOME=%cd%\profile\.cache\huggingface ) @rem set the correct installer path (current vs legacy) From 9399fb5371cb971bd55ebc941b5f26c12ce147f2 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Tue, 25 Apr 2023 21:02:36 +0200 Subject: [PATCH 15/92] Don't use python packages from the user's home directory PYTHONNOUSERSITE is required to ignore packages installed to `/home/user/.local/`. Since these folders are outside of our control, they can cause conflicts in ED's python env. https://discord.com/channels/1014774730907209781/1100375010650103808 Fixes #1193 --- scripts/on_env_start.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/on_env_start.sh b/scripts/on_env_start.sh index 4e73ca4e..af588177 100755 --- a/scripts/on_env_start.sh +++ b/scripts/on_env_start.sh @@ -4,6 +4,8 @@ source ./scripts/functions.sh printf "\n\nEasy Diffusion\n\n" +export PYTHONNOUSERSITE=y + if [ -f "scripts/config.sh" ]; then source scripts/config.sh fi From fb18c93bd680dae0619ca2647ea0fc3c5aa09795 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 26 Apr 2023 16:25:02 +0530 Subject: [PATCH 16/92] Suppress debug log --- ui/media/js/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 8421bfa0..1eeddb99 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -781,7 +781,7 @@ Array.prototype.addEventListener = function(method, callback) { return } - console.debug('creating tab: ', request) + // console.debug('creating tab: ', request) if (request.css) { document.querySelector('body').insertAdjacentElement( From 216323fcf4269b1ab4b91c1daae053d43905bc88 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 26 Apr 2023 16:25:42 +0530 Subject: [PATCH 17/92] No longer close to the fastest, the arms race continues --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f3fc0f94..91ba0547 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ ## v2.5 ### Major Changes -- **Nearly twice as fast** - significantly faster speed of image generation. We're now pretty close to automatic1111's speed. Code contributions are welcome to make our project even faster: https://github.com/easydiffusion/sdkit/#is-it-fast +- **Nearly twice as fast** - significantly faster speed of image generation. Code contributions are welcome to make our project even faster: https://github.com/easydiffusion/sdkit/#is-it-fast - **Mac M1/M2 support** - Experimental support for Mac M1/M2. Thanks @michaelgallacher, @JeLuf and vishae. - **Full support for Stable Diffusion 2.1 (including CPU)** - supports loading v1.4 or v2.0 or v2.1 models seamlessly. No need to enable "Test SD2", and no need to add `sd2_` to your SD 2.0 model file names. Works on CPU as well. - **Memory optimized Stable Diffusion 2.1** - you can now use Stable Diffusion 2.1 models, with the same low VRAM optimizations that we've always had for SD 1.4. Please note, the SD 2.0 and 2.1 models require more GPU and System RAM, as compared to the SD 1.4 and 1.5 models. From fe8c208e7c8b1f3ac2581d618269a05f10c38398 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 26 Apr 2023 16:33:43 +0530 Subject: [PATCH 18/92] Copy get_config.py in on_sd_start for the first run, when on_env_start hasn't yet been updated --- scripts/on_sd_start.bat | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/on_sd_start.bat b/scripts/on_sd_start.bat index e5d94d2e..e74b0b0e 100644 --- a/scripts/on_sd_start.bat +++ b/scripts/on_sd_start.bat @@ -7,6 +7,7 @@ @copy sd-ui-files\scripts\bootstrap.bat scripts\ /Y @copy sd-ui-files\scripts\check_modules.py scripts\ /Y @copy sd-ui-files\scripts\check_models.py scripts\ /Y +@copy sd-ui-files\scripts\get_config.py scripts\ /Y if exist "%cd%\profile" ( set USERPROFILE=%cd%\profile From 24d0e7566ff567612d2df5cd0ea86c3ed66cbdf2 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 26 Apr 2023 16:34:27 +0530 Subject: [PATCH 19/92] Copy get_config.py in on_sd_start for the first run, when on_env_start hasn't yet been updated --- scripts/on_sd_start.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/on_sd_start.sh b/scripts/on_sd_start.sh index f0eeb743..820c36ed 100755 --- a/scripts/on_sd_start.sh +++ b/scripts/on_sd_start.sh @@ -5,6 +5,7 @@ cp sd-ui-files/scripts/on_env_start.sh scripts/ cp sd-ui-files/scripts/bootstrap.sh scripts/ cp sd-ui-files/scripts/check_modules.py scripts/ cp sd-ui-files/scripts/check_models.py scripts/ +cp sd-ui-files/scripts/get_config.py scripts/ source ./scripts/functions.sh From 2333beda5f1a36fd48279db8e74058851f848839 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 27 Apr 2023 15:40:39 +0530 Subject: [PATCH 20/92] Hardware req --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 4d73d311..3cb0bf8e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ Click the download button for your operating system:

+**Hardware requirements:** +- **Windows:** NVIDIA graphics card, or run on your CPU +- **Linux:** NVIDIA or AMD graphics card, or run on your CPU +- **Mac:** M1 or M2, or run on your CPU + The installer will take care of whatever is needed. If you face any problems, you can join the friendly [Discord community](https://discord.com/invite/u9yhsFmEkB) and ask for assistance. ## On Windows: From 60f2f5ea194807c3ec4a175e373fa8652fded4c4 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 27 Apr 2023 15:48:40 +0530 Subject: [PATCH 21/92] changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 91ba0547..6b4faf95 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,7 +21,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog -* 2.5.34 - 22 Apr 2023 - Nothing, just keeping this line warm. +* 2.5.34 - 22 Apr 2023 - Don't start the browser in an incognito new profile (on Windows). Thanks @JeLuf. * 2.5.33 - 21 Apr 2023 - Install PyTorch 2.0 on new installations (on Windows and Linux). * 2.5.32 - 19 Apr 2023 - Automatically check for black images, and set full-precision if necessary (for attn). This means custom models based on Stable Diffusion v2.1 will just work, without needing special command-line arguments or editing of yaml config files. * 2.5.32 - 18 Apr 2023 - Automatic support for AMD graphics cards on Linux. Thanks @DianaNites and @JeLuf. From 400cb218ba4856c62a3e2567f9e2a4335d310e03 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 27 Apr 2023 15:58:06 +0530 Subject: [PATCH 22/92] Don't override net config if env variables don't exist --- ui/easydiffusion/app.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui/easydiffusion/app.py b/ui/easydiffusion/app.py index 12f10a5a..658de841 100644 --- a/ui/easydiffusion/app.py +++ b/ui/easydiffusion/app.py @@ -81,12 +81,8 @@ def getConfig(default_val=APP_CONFIG_DEFAULTS): config["net"] = {} if os.getenv("SD_UI_BIND_PORT") is not None: config["net"]["listen_port"] = int(os.getenv("SD_UI_BIND_PORT")) - else: - config["net"]["listen_port"] = 9000 if os.getenv("SD_UI_BIND_IP") is not None: config["net"]["listen_to_network"] = os.getenv("SD_UI_BIND_IP") == "0.0.0.0" - else: - config["net"]["listen_to_network"] = True return config except Exception as e: log.warn(traceback.format_exc()) @@ -188,7 +184,7 @@ def getIPConfig(): def open_browser(): config = getConfig() ui = config.get("ui", {}) - net = config.get("net", {"listen_port": 9000}) + net = config.get("net", {}) port = net.get("listen_port", 9000) if ui.get("open_browser_on_start", True): import webbrowser From aad1afb70ebd7d11e8083885264692edcd9f23a1 Mon Sep 17 00:00:00 2001 From: Lucas Marcelli Date: Thu, 27 Apr 2023 13:56:56 -0400 Subject: [PATCH 23/92] add prettier for JS style --- .gitignore | 1 + .prettierignore | 8 + .prettierrc.json | 7 + package.json | 8 + ui/media/js/auto-save.js | 122 +- ui/media/js/dnd.js | 365 +- ui/media/js/engine.js | 692 +- ui/media/js/image-editor.js | 1648 +- ui/media/js/image-modal.js | 118 +- ui/media/js/image-modifiers.js | 323 +- ui/media/js/main.js | 1118 +- ui/media/js/parameters.js | 358 +- ui/media/js/plugins.js | 32 +- ui/media/js/searchable-models.js | 402 +- ui/media/js/themes.js | 93 +- ui/media/js/utils.js | 487 +- ui/plugins/ui/Autoscroll.plugin.js | 28 +- ui/plugins/ui/Modifiers-dnd.plugin.js | 111 +- ui/plugins/ui/Modifiers-wheel.plugin.js | 75 +- ui/plugins/ui/custom-modifiers.plugin.js | 18 +- ui/plugins/ui/jasmine/boot0.js | 62 +- ui/plugins/ui/jasmine/boot1.js | 176 +- ui/plugins/ui/jasmine/jasmine-html.js | 1564 +- ui/plugins/ui/jasmine/jasmine.js | 18244 ++++++++++----------- ui/plugins/ui/jasmineSpec.js | 327 +- ui/plugins/ui/merge.plugin.js | 244 +- ui/plugins/ui/modifiers-toggle.plugin.js | 52 +- ui/plugins/ui/release-notes.plugin.js | 36 +- ui/plugins/ui/selftest.plugin.js | 19 +- yarn.lock | 8 + 30 files changed, 13142 insertions(+), 13604 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 package.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index b5157e17..90bf0a44 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ installer installer.tar dist .idea/* +node_modules/* \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..1f28e901 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +*.min.* +*.py +/* +!/ui +/ui/easydiffusion +/ui/hotfix +!/ui/plugins +!/ui/media \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..d8ec3571 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "printWidth": 120, + "tabWidth": 4, + "semi": false, + "arrowParens": "always", + "trailingComma": "none" +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..c9c03893 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "prettier": "prettier --write \"./**/*.js\"" + }, + "devDependencies": { + "prettier": "^1.19.1" + } +} diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js index 32d9ad1e..0bf9d855 100644 --- a/ui/media/js/auto-save.js +++ b/ui/media/js/auto-save.js @@ -55,24 +55,24 @@ const SETTINGS_IDS_LIST = [ "json_toggle" ] -const IGNORE_BY_DEFAULT = [ - "prompt" -] +const IGNORE_BY_DEFAULT = ["prompt"] -const SETTINGS_SECTIONS = [ // gets the "keys" property filled in with an ordered list of settings in this section via initSettings - { id: "editor-inputs", name: "Prompt" }, +const SETTINGS_SECTIONS = [ + // gets the "keys" property filled in with an ordered list of settings in this section via initSettings + { id: "editor-inputs", name: "Prompt" }, { id: "editor-settings", name: "Image Settings" }, { id: "system-settings", name: "System Settings" }, - { id: "container", name: "Other" } + { id: "container", name: "Other" } ] async function initSettings() { - SETTINGS_IDS_LIST.forEach(id => { + SETTINGS_IDS_LIST.forEach((id) => { var element = document.getElementById(id) if (!element) { console.error(`Missing settings element ${id}`) } - if (id in SETTINGS) { // don't create it again + if (id in SETTINGS) { + // don't create it again return } SETTINGS[id] = { @@ -87,22 +87,22 @@ async function initSettings() { element.addEventListener("change", settingChangeHandler) }) var unsorted_settings_ids = [...SETTINGS_IDS_LIST] - SETTINGS_SECTIONS.forEach(section => { + SETTINGS_SECTIONS.forEach((section) => { var name = section.name var element = document.getElementById(section.id) - var unsorted_ids = unsorted_settings_ids.map(id => `#${id}`).join(",") - var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids)); + var unsorted_ids = unsorted_settings_ids.map((id) => `#${id}`).join(",") + var children = unsorted_ids == "" ? [] : Array.from(element.querySelectorAll(unsorted_ids)) section.keys = [] - children.forEach(e => { + children.forEach((e) => { section.keys.push(e.id) }) - unsorted_settings_ids = unsorted_settings_ids.filter(id => children.find(e => e.id == id) == undefined) + unsorted_settings_ids = unsorted_settings_ids.filter((id) => children.find((e) => e.id == id) == undefined) }) loadSettings() } function getSetting(element) { - if (element.dataset && 'path' in element.dataset) { + if (element.dataset && "path" in element.dataset) { return element.dataset.path } if (typeof element === "string" || element instanceof String) { @@ -114,7 +114,7 @@ function getSetting(element) { return element.value } function setSetting(element, value) { - if (element.dataset && 'path' in element.dataset) { + if (element.dataset && "path" in element.dataset) { element.dataset.path = value return // no need to dispatch any event here because the models are not loaded yet } @@ -127,8 +127,7 @@ function setSetting(element, value) { } if (element.type == "checkbox") { element.checked = value - } - else { + } else { element.value = value } element.dispatchEvent(new Event("input")) @@ -136,7 +135,7 @@ function setSetting(element, value) { } function saveSettings() { - var saved_settings = Object.values(SETTINGS).map(setting => { + var saved_settings = Object.values(SETTINGS).map((setting) => { return { key: setting.key, value: setting.value, @@ -151,16 +150,16 @@ function loadSettings() { var saved_settings_text = localStorage.getItem(SETTINGS_KEY) if (saved_settings_text) { var saved_settings = JSON.parse(saved_settings_text) - if (saved_settings.find(s => s.key == "auto_save_settings")?.value == false) { + if (saved_settings.find((s) => s.key == "auto_save_settings")?.value == false) { setSetting("auto_save_settings", false) return } CURRENTLY_LOADING_SETTINGS = true - saved_settings.forEach(saved_setting => { + saved_settings.forEach((saved_setting) => { var setting = SETTINGS[saved_setting.key] if (!setting) { - console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`); - return null; + console.warn(`Attempted to load setting ${saved_setting.key}, but no setting found`) + return null } setting.ignore = saved_setting.ignore if (!setting.ignore) { @@ -169,10 +168,9 @@ function loadSettings() { } }) CURRENTLY_LOADING_SETTINGS = false - } - else { + } else { CURRENTLY_LOADING_SETTINGS = true - tryLoadOldSettings(); + tryLoadOldSettings() CURRENTLY_LOADING_SETTINGS = false saveSettings() } @@ -180,9 +178,9 @@ function loadSettings() { function loadDefaultSettingsSection(section_id) { CURRENTLY_LOADING_SETTINGS = true - var section = SETTINGS_SECTIONS.find(s => s.id == section_id); - section.keys.forEach(key => { - var setting = SETTINGS[key]; + var section = SETTINGS_SECTIONS.find((s) => s.id == section_id) + section.keys.forEach((key) => { + var setting = SETTINGS[key] setting.value = setting.default setSetting(setting.element, setting.value) }) @@ -218,10 +216,10 @@ function getSettingLabel(element) { function fillSaveSettingsConfigTable() { saveSettingsConfigTable.textContent = "" - SETTINGS_SECTIONS.forEach(section => { + SETTINGS_SECTIONS.forEach((section) => { var section_row = `${section.name}` saveSettingsConfigTable.insertAdjacentHTML("beforeend", section_row) - section.keys.forEach(key => { + section.keys.forEach((key) => { var setting = SETTINGS[key] var element = setting.element var checkbox_id = `shouldsave_${element.id}` @@ -234,7 +232,7 @@ function fillSaveSettingsConfigTable() { var newrow = `(${value})` saveSettingsConfigTable.insertAdjacentHTML("beforeend", newrow) var checkbox = document.getElementById(checkbox_id) - checkbox.addEventListener("input", event => { + checkbox.addEventListener("input", (event) => { setting.ignore = !checkbox.checked saveSettings() }) @@ -245,9 +243,6 @@ function fillSaveSettingsConfigTable() { // configureSettingsSaveBtn - - - var autoSaveSettings = document.getElementById("auto_save_settings") var configSettingsButton = document.createElement("button") configSettingsButton.textContent = "Configure" @@ -256,33 +251,32 @@ autoSaveSettings.insertAdjacentElement("beforebegin", configSettingsButton) autoSaveSettings.addEventListener("change", () => { configSettingsButton.style.display = autoSaveSettings.checked ? "block" : "none" }) -configSettingsButton.addEventListener('click', () => { +configSettingsButton.addEventListener("click", () => { fillSaveSettingsConfigTable() saveSettingsConfigOverlay.classList.add("active") }) -resetImageSettingsButton.addEventListener('click', event => { - loadDefaultSettingsSection("editor-settings"); +resetImageSettingsButton.addEventListener("click", (event) => { + loadDefaultSettingsSection("editor-settings") event.stopPropagation() }) - function tryLoadOldSettings() { console.log("Loading old user settings") // load v1 auto-save.js settings var old_map = { - "guidance_scale_slider": "guidance_scale", - "prompt_strength_slider": "prompt_strength" + guidance_scale_slider: "guidance_scale", + prompt_strength_slider: "prompt_strength" } var settings_key_v1 = "user_settings" var saved_settings_text = localStorage.getItem(settings_key_v1) if (saved_settings_text) { var saved_settings = JSON.parse(saved_settings_text) - Object.keys(saved_settings.should_save).forEach(key => { + Object.keys(saved_settings.should_save).forEach((key) => { key = key in old_map ? old_map[key] : key if (!(key in SETTINGS)) return SETTINGS[key].ignore = !saved_settings.should_save[key] - }); - Object.keys(saved_settings.values).forEach(key => { + }) + Object.keys(saved_settings.values).forEach((key) => { key = key in old_map ? old_map[key] : key if (!(key in SETTINGS)) return var setting = SETTINGS[key] @@ -290,38 +284,42 @@ function tryLoadOldSettings() { setting.value = saved_settings.values[key] setSetting(setting.element, setting.value) } - }); + }) localStorage.removeItem(settings_key_v1) } // load old individually stored items - var individual_settings_map = { // maps old localStorage-key to new SETTINGS-key - "soundEnabled": "sound_toggle", - "saveToDisk": "save_to_disk", - "useCPU": "use_cpu", - "diskPath": "diskPath", - "useFaceCorrection": "use_face_correction", - "useUpscaling": "use_upscale", - "showOnlyFilteredImage": "show_only_filtered_image", - "streamImageProgress": "stream_image_progress", - "outputFormat": "output_format", - "autoSaveSettings": "auto_save_settings", - }; - Object.keys(individual_settings_map).forEach(localStorageKey => { - var localStorageValue = localStorage.getItem(localStorageKey); + var individual_settings_map = { + // maps old localStorage-key to new SETTINGS-key + soundEnabled: "sound_toggle", + saveToDisk: "save_to_disk", + useCPU: "use_cpu", + diskPath: "diskPath", + useFaceCorrection: "use_face_correction", + useUpscaling: "use_upscale", + showOnlyFilteredImage: "show_only_filtered_image", + streamImageProgress: "stream_image_progress", + outputFormat: "output_format", + autoSaveSettings: "auto_save_settings" + } + Object.keys(individual_settings_map).forEach((localStorageKey) => { + var localStorageValue = localStorage.getItem(localStorageKey) if (localStorageValue !== null) { let key = individual_settings_map[localStorageKey] var setting = SETTINGS[key] if (!setting) { - console.warn(`Attempted to map old setting ${key}, but no setting found`); - return null; + console.warn(`Attempted to map old setting ${key}, but no setting found`) + return null } - if (setting.element.type == "checkbox" && (typeof localStorageValue === "string" || localStorageValue instanceof String)) { + if ( + setting.element.type == "checkbox" && + (typeof localStorageValue === "string" || localStorageValue instanceof String) + ) { localStorageValue = localStorageValue == "true" } setting.value = localStorageValue setSetting(setting.element, setting.value) - localStorage.removeItem(localStorageKey); + localStorage.removeItem(localStorageKey) } }) } diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index ebcce132..aa487c94 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -1,25 +1,25 @@ "use strict" // Opt in to a restricted variant of JavaScript const EXT_REGEX = /(?:\.([^.]+))?$/ -const TEXT_EXTENSIONS = ['txt', 'json'] -const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'tga', 'webp'] +const TEXT_EXTENSIONS = ["txt", "json"] +const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "bmp", "tiff", "tif", "tga", "webp"] function parseBoolean(stringValue) { - if (typeof stringValue === 'boolean') { + if (typeof stringValue === "boolean") { return stringValue } - if (typeof stringValue === 'number') { + if (typeof stringValue === "number") { return stringValue !== 0 } - if (typeof stringValue !== 'string') { + if (typeof stringValue !== "string") { return false } - switch(stringValue?.toLowerCase()?.trim()) { + switch (stringValue?.toLowerCase()?.trim()) { case "true": case "yes": case "on": case "1": - return true; + return true case "false": case "no": @@ -28,45 +28,50 @@ function parseBoolean(stringValue) { case "none": case null: case undefined: - return false; + return false } try { - return Boolean(JSON.parse(stringValue)); + return Boolean(JSON.parse(stringValue)) } catch { return Boolean(stringValue) } } const TASK_MAPPING = { - prompt: { name: 'Prompt', + prompt: { + name: "Prompt", setUI: (prompt) => { promptField.value = prompt }, readUI: () => promptField.value, parse: (val) => val }, - negative_prompt: { name: 'Negative Prompt', + negative_prompt: { + name: "Negative Prompt", setUI: (negative_prompt) => { negativePromptField.value = negative_prompt }, readUI: () => negativePromptField.value, parse: (val) => val }, - active_tags: { name: "Image Modifiers", + active_tags: { + name: "Image Modifiers", setUI: (active_tags) => { refreshModifiersState(active_tags) }, - readUI: () => activeTags.map(x => x.name), + readUI: () => activeTags.map((x) => x.name), parse: (val) => val }, - inactive_tags: { name: "Inactive Image Modifiers", + inactive_tags: { + name: "Inactive Image Modifiers", setUI: (inactive_tags) => { refreshInactiveTags(inactive_tags) }, - readUI: () => activeTags.filter(tag => tag.inactive === true).map(x => x.name), + readUI: () => activeTags.filter((tag) => tag.inactive === true).map((x) => x.name), parse: (val) => val }, - width: { name: 'Width', + width: { + name: "Width", setUI: (width) => { const oldVal = widthField.value widthField.value = width @@ -77,7 +82,8 @@ const TASK_MAPPING = { readUI: () => parseInt(widthField.value), parse: (val) => parseInt(val) }, - height: { name: 'Height', + height: { + name: "Height", setUI: (height) => { const oldVal = heightField.value heightField.value = height @@ -88,7 +94,8 @@ const TASK_MAPPING = { readUI: () => parseInt(heightField.value), parse: (val) => parseInt(val) }, - seed: { name: 'Seed', + seed: { + name: "Seed", setUI: (seed) => { if (!seed) { randomSeedField.checked = true @@ -97,21 +104,23 @@ const TASK_MAPPING = { return } randomSeedField.checked = false - randomSeedField.dispatchEvent(new Event('change')) // let plugins know that the state of the random seed toggle changed + randomSeedField.dispatchEvent(new Event("change")) // let plugins know that the state of the random seed toggle changed seedField.disabled = false seedField.value = seed }, readUI: () => parseInt(seedField.value), // just return the value the user is seeing in the UI parse: (val) => parseInt(val) }, - num_inference_steps: { name: 'Steps', + num_inference_steps: { + name: "Steps", setUI: (num_inference_steps) => { numInferenceStepsField.value = num_inference_steps }, readUI: () => parseInt(numInferenceStepsField.value), parse: (val) => parseInt(val) }, - guidance_scale: { name: 'Guidance Scale', + guidance_scale: { + name: "Guidance Scale", setUI: (guidance_scale) => { guidanceScaleField.value = guidance_scale updateGuidanceScaleSlider() @@ -119,7 +128,8 @@ const TASK_MAPPING = { readUI: () => parseFloat(guidanceScaleField.value), parse: (val) => parseFloat(val) }, - prompt_strength: { name: 'Prompt Strength', + prompt_strength: { + name: "Prompt Strength", setUI: (prompt_strength) => { promptStrengthField.value = prompt_strength updatePromptStrengthSlider() @@ -128,16 +138,19 @@ const TASK_MAPPING = { parse: (val) => parseFloat(val) }, - init_image: { name: 'Initial Image', + init_image: { + name: "Initial Image", setUI: (init_image) => { initImagePreview.src = init_image }, readUI: () => initImagePreview.src, parse: (val) => val }, - mask: { name: 'Mask', + mask: { + name: "Mask", setUI: (mask) => { - setTimeout(() => { // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter) + setTimeout(() => { + // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter) imageInpainter.setImg(mask) }, 250) maskSetting.checked = Boolean(mask) @@ -145,22 +158,26 @@ const TASK_MAPPING = { readUI: () => (maskSetting.checked ? imageInpainter.getImg() : undefined), parse: (val) => val }, - preserve_init_image_color_profile: { name: 'Preserve Color Profile', + preserve_init_image_color_profile: { + name: "Preserve Color Profile", setUI: (preserve_init_image_color_profile) => { applyColorCorrectionField.checked = parseBoolean(preserve_init_image_color_profile) }, readUI: () => applyColorCorrectionField.checked, parse: (val) => parseBoolean(val) }, - - use_face_correction: { name: 'Use Face Correction', + + use_face_correction: { + name: "Use Face Correction", setUI: (use_face_correction) => { const oldVal = gfpganModelField.value - gfpganModelField.value = getModelPath(use_face_correction, ['.pth']) - if (gfpganModelField.value) { // Is a valid value for the field. + gfpganModelField.value = getModelPath(use_face_correction, [".pth"]) + if (gfpganModelField.value) { + // Is a valid value for the field. useFaceCorrectionField.checked = true gfpganModelField.disabled = false - } else { // Not a valid value, restore the old value and disable the filter. + } else { + // Not a valid value, restore the old value and disable the filter. gfpganModelField.disabled = true gfpganModelField.value = oldVal useFaceCorrectionField.checked = false @@ -171,15 +188,18 @@ const TASK_MAPPING = { readUI: () => (useFaceCorrectionField.checked ? gfpganModelField.value : undefined), parse: (val) => val }, - use_upscale: { name: 'Use Upscaling', + use_upscale: { + name: "Use Upscaling", setUI: (use_upscale) => { const oldVal = upscaleModelField.value - upscaleModelField.value = getModelPath(use_upscale, ['.pth']) - if (upscaleModelField.value) { // Is a valid value for the field. + upscaleModelField.value = getModelPath(use_upscale, [".pth"]) + if (upscaleModelField.value) { + // Is a valid value for the field. useUpscalingField.checked = true upscaleModelField.disabled = false upscaleAmountField.disabled = false - } else { // Not a valid value, restore the old value and disable the filter. + } else { + // Not a valid value, restore the old value and disable the filter. upscaleModelField.disabled = true upscaleAmountField.disabled = true upscaleModelField.value = oldVal @@ -189,25 +209,28 @@ const TASK_MAPPING = { readUI: () => (useUpscalingField.checked ? upscaleModelField.value : undefined), parse: (val) => val }, - upscale_amount: { name: 'Upscale By', + upscale_amount: { + name: "Upscale By", setUI: (upscale_amount) => { upscaleAmountField.value = upscale_amount }, readUI: () => upscaleAmountField.value, parse: (val) => val }, - sampler_name: { name: 'Sampler', + sampler_name: { + name: "Sampler", setUI: (sampler_name) => { samplerField.value = sampler_name }, readUI: () => samplerField.value, parse: (val) => val }, - use_stable_diffusion_model: { name: 'Stable Diffusion model', + use_stable_diffusion_model: { + name: "Stable Diffusion model", setUI: (use_stable_diffusion_model) => { const oldVal = stableDiffusionModelField.value - use_stable_diffusion_model = getModelPath(use_stable_diffusion_model, ['.ckpt', '.safetensors']) + use_stable_diffusion_model = getModelPath(use_stable_diffusion_model, [".ckpt", ".safetensors"]) stableDiffusionModelField.value = use_stable_diffusion_model if (!stableDiffusionModelField.value) { @@ -217,35 +240,42 @@ const TASK_MAPPING = { readUI: () => stableDiffusionModelField.value, parse: (val) => val }, - use_vae_model: { name: 'VAE model', + use_vae_model: { + name: "VAE model", setUI: (use_vae_model) => { const oldVal = vaeModelField.value - use_vae_model = (use_vae_model === undefined || use_vae_model === null || use_vae_model === 'None' ? '' : use_vae_model) + use_vae_model = + use_vae_model === undefined || use_vae_model === null || use_vae_model === "None" ? "" : use_vae_model - if (use_vae_model !== '') { - use_vae_model = getModelPath(use_vae_model, ['.vae.pt', '.ckpt']) - use_vae_model = use_vae_model !== '' ? use_vae_model : oldVal + if (use_vae_model !== "") { + use_vae_model = getModelPath(use_vae_model, [".vae.pt", ".ckpt"]) + use_vae_model = use_vae_model !== "" ? use_vae_model : oldVal } vaeModelField.value = use_vae_model }, readUI: () => vaeModelField.value, parse: (val) => val }, - use_lora_model: { name: 'LoRA model', + use_lora_model: { + name: "LoRA model", setUI: (use_lora_model) => { const oldVal = loraModelField.value - use_lora_model = (use_lora_model === undefined || use_lora_model === null || use_lora_model === 'None' ? '' : use_lora_model) + use_lora_model = + use_lora_model === undefined || use_lora_model === null || use_lora_model === "None" + ? "" + : use_lora_model - if (use_lora_model !== '') { - use_lora_model = getModelPath(use_lora_model, ['.ckpt', '.safetensors']) - use_lora_model = use_lora_model !== '' ? use_lora_model : oldVal + if (use_lora_model !== "") { + use_lora_model = getModelPath(use_lora_model, [".ckpt", ".safetensors"]) + use_lora_model = use_lora_model !== "" ? use_lora_model : oldVal } loraModelField.value = use_lora_model }, readUI: () => loraModelField.value, parse: (val) => val }, - lora_alpha: { name: 'LoRA Strength', + lora_alpha: { + name: "LoRA Strength", setUI: (lora_alpha) => { loraAlphaField.value = lora_alpha updateLoraAlphaSlider() @@ -253,22 +283,29 @@ const TASK_MAPPING = { readUI: () => parseFloat(loraAlphaField.value), parse: (val) => parseFloat(val) }, - use_hypernetwork_model: { name: 'Hypernetwork model', + use_hypernetwork_model: { + name: "Hypernetwork model", setUI: (use_hypernetwork_model) => { const oldVal = hypernetworkModelField.value - use_hypernetwork_model = (use_hypernetwork_model === undefined || use_hypernetwork_model === null || use_hypernetwork_model === 'None' ? '' : use_hypernetwork_model) + use_hypernetwork_model = + use_hypernetwork_model === undefined || + use_hypernetwork_model === null || + use_hypernetwork_model === "None" + ? "" + : use_hypernetwork_model - if (use_hypernetwork_model !== '') { - use_hypernetwork_model = getModelPath(use_hypernetwork_model, ['.pt']) - use_hypernetwork_model = use_hypernetwork_model !== '' ? use_hypernetwork_model : oldVal + if (use_hypernetwork_model !== "") { + use_hypernetwork_model = getModelPath(use_hypernetwork_model, [".pt"]) + use_hypernetwork_model = use_hypernetwork_model !== "" ? use_hypernetwork_model : oldVal } hypernetworkModelField.value = use_hypernetwork_model - hypernetworkModelField.dispatchEvent(new Event('change')) + hypernetworkModelField.dispatchEvent(new Event("change")) }, readUI: () => hypernetworkModelField.value, parse: (val) => val }, - hypernetwork_strength: { name: 'Hypernetwork Strength', + hypernetwork_strength: { + name: "Hypernetwork Strength", setUI: (hypernetwork_strength) => { hypernetworkStrengthField.value = hypernetwork_strength updateHypernetworkStrengthSlider() @@ -277,7 +314,8 @@ const TASK_MAPPING = { parse: (val) => parseFloat(val) }, - num_outputs: { name: 'Parallel Images', + num_outputs: { + name: "Parallel Images", setUI: (num_outputs) => { numOutputsParallelField.value = num_outputs }, @@ -285,7 +323,8 @@ const TASK_MAPPING = { parse: (val) => val }, - use_cpu: { name: 'Use CPU', + use_cpu: { + name: "Use CPU", setUI: (use_cpu) => { useCPUField.checked = use_cpu }, @@ -293,28 +332,32 @@ const TASK_MAPPING = { parse: (val) => val }, - stream_image_progress: { name: 'Stream Image Progress', + stream_image_progress: { + name: "Stream Image Progress", setUI: (stream_image_progress) => { - streamImageProgressField.checked = (parseInt(numOutputsTotalField.value) > 50 ? false : stream_image_progress) + streamImageProgressField.checked = parseInt(numOutputsTotalField.value) > 50 ? false : stream_image_progress }, readUI: () => streamImageProgressField.checked, parse: (val) => Boolean(val) }, - show_only_filtered_image: { name: 'Show only the corrected/upscaled image', + show_only_filtered_image: { + name: "Show only the corrected/upscaled image", setUI: (show_only_filtered_image) => { showOnlyFilteredImageField.checked = show_only_filtered_image }, readUI: () => showOnlyFilteredImageField.checked, parse: (val) => Boolean(val) }, - output_format: { name: 'Output Format', + output_format: { + name: "Output Format", setUI: (output_format) => { outputFormatField.value = output_format }, readUI: () => outputFormatField.value, parse: (val) => val }, - save_to_disk_path: { name: 'Save to disk path', + save_to_disk_path: { + name: "Save to disk path", setUI: (save_to_disk_path) => { saveToDiskField.checked = Boolean(save_to_disk_path) diskPathField.value = save_to_disk_path @@ -327,14 +370,14 @@ const TASK_MAPPING = { function restoreTaskToUI(task, fieldsToSkip) { fieldsToSkip = fieldsToSkip || [] - if ('numOutputsTotal' in task) { + if ("numOutputsTotal" in task) { numOutputsTotalField.value = task.numOutputsTotal } - if ('seed' in task) { + if ("seed" in task) { randomSeedField.checked = false seedField.value = task.seed } - if (!('reqBody' in task)) { + if (!("reqBody" in task)) { return } for (const key in TASK_MAPPING) { @@ -344,31 +387,31 @@ function restoreTaskToUI(task, fieldsToSkip) { } // properly reset fields not present in the task - if (!('use_hypernetwork_model' in task.reqBody)) { + if (!("use_hypernetwork_model" in task.reqBody)) { hypernetworkModelField.value = "" hypernetworkModelField.dispatchEvent(new Event("change")) } - - if (!('use_lora_model' in task.reqBody)) { + + if (!("use_lora_model" in task.reqBody)) { loraModelField.value = "" 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)) { + if (!("original_prompt" in task.reqBody)) { promptField.value = task.reqBody.prompt } - + // properly reset checkboxes - if (!('use_face_correction' in task.reqBody)) { + if (!("use_face_correction" in task.reqBody)) { useFaceCorrectionField.checked = false gfpganModelField.disabled = true } - if (!('use_upscale' in task.reqBody)) { + if (!("use_upscale" in task.reqBody)) { useUpscalingField.checked = false } - if (!('mask' in task.reqBody) && maskSetting.checked) { + if (!("mask" in task.reqBody) && maskSetting.checked) { maskSetting.checked = false maskSetting.dispatchEvent(new Event("click")) } @@ -379,15 +422,18 @@ function restoreTaskToUI(task, fieldsToSkip) { if (IMAGE_REGEX.test(initImagePreview.src) && task.reqBody.init_image == undefined) { // hide source image initImageClearBtn.dispatchEvent(new Event("click")) - } - else if (task.reqBody.init_image !== undefined) { + } else if (task.reqBody.init_image !== undefined) { // listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpainter) - initImagePreview.addEventListener('load', function() { - if (Boolean(task.reqBody.mask)) { - imageInpainter.setImg(task.reqBody.mask) - maskSetting.checked = true - } - }, { once: true }) + initImagePreview.addEventListener( + "load", + function() { + if (Boolean(task.reqBody.mask)) { + imageInpainter.setImg(task.reqBody.mask) + maskSetting.checked = true + } + }, + { once: true } + ) initImagePreview.src = task.reqBody.init_image } } @@ -397,28 +443,26 @@ function readUI() { reqBody[key] = TASK_MAPPING[key].readUI() } return { - 'numOutputsTotal': parseInt(numOutputsTotalField.value), - 'seed': TASK_MAPPING['seed'].readUI(), - 'reqBody': reqBody + numOutputsTotal: parseInt(numOutputsTotalField.value), + seed: TASK_MAPPING["seed"].readUI(), + reqBody: reqBody } } -function getModelPath(filename, extensions) -{ +function getModelPath(filename, extensions) { if (typeof filename !== "string") { return } - + let pathIdx - if (filename.includes('/models/stable-diffusion/')) { - pathIdx = filename.indexOf('/models/stable-diffusion/') + 25 // Linux, Mac paths - } - else if (filename.includes('\\models\\stable-diffusion\\')) { - pathIdx = filename.indexOf('\\models\\stable-diffusion\\') + 25 // Linux, Mac paths + if (filename.includes("/models/stable-diffusion/")) { + pathIdx = filename.indexOf("/models/stable-diffusion/") + 25 // Linux, Mac paths + } else if (filename.includes("\\models\\stable-diffusion\\")) { + pathIdx = filename.indexOf("\\models\\stable-diffusion\\") + 25 // Linux, Mac paths } if (pathIdx >= 0) { filename = filename.slice(pathIdx) } - extensions.forEach(ext => { + extensions.forEach((ext) => { if (filename.endsWith(ext)) { filename = filename.slice(0, filename.length - ext.length) } @@ -427,26 +471,26 @@ function getModelPath(filename, extensions) } const TASK_TEXT_MAPPING = { - prompt: 'Prompt', - width: 'Width', - height: 'Height', - seed: 'Seed', - num_inference_steps: 'Steps', - guidance_scale: 'Guidance Scale', - prompt_strength: 'Prompt Strength', - use_face_correction: 'Use Face Correction', - use_upscale: 'Use Upscaling', - upscale_amount: 'Upscale By', - sampler_name: 'Sampler', - negative_prompt: 'Negative Prompt', - use_stable_diffusion_model: 'Stable Diffusion model', - use_hypernetwork_model: 'Hypernetwork model', - hypernetwork_strength: 'Hypernetwork Strength' + prompt: "Prompt", + width: "Width", + height: "Height", + seed: "Seed", + num_inference_steps: "Steps", + guidance_scale: "Guidance Scale", + prompt_strength: "Prompt Strength", + use_face_correction: "Use Face Correction", + use_upscale: "Use Upscaling", + upscale_amount: "Upscale By", + sampler_name: "Sampler", + negative_prompt: "Negative Prompt", + use_stable_diffusion_model: "Stable Diffusion model", + use_hypernetwork_model: "Hypernetwork model", + hypernetwork_strength: "Hypernetwork Strength" } function parseTaskFromText(str) { const taskReqBody = {} - const lines = str.split('\n') + const lines = str.split("\n") if (lines.length === 0) { return } @@ -454,14 +498,14 @@ function parseTaskFromText(str) { // Prompt let knownKeyOnFirstLine = false for (let key in TASK_TEXT_MAPPING) { - if (lines[0].startsWith(TASK_TEXT_MAPPING[key] + ':')) { + if (lines[0].startsWith(TASK_TEXT_MAPPING[key] + ":")) { knownKeyOnFirstLine = true break } } if (!knownKeyOnFirstLine) { taskReqBody.prompt = lines[0] - console.log('Prompt:', taskReqBody.prompt) + console.log("Prompt:", taskReqBody.prompt) } for (const key in TASK_TEXT_MAPPING) { @@ -469,18 +513,18 @@ function parseTaskFromText(str) { continue } - const name = TASK_TEXT_MAPPING[key]; + const name = TASK_TEXT_MAPPING[key] let val = undefined - const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, 'igm') - const match = reName.exec(str); + const reName = new RegExp(`${name}\\ *:\\ *(.*)(?:\\r\\n|\\r|\\n)*`, "igm") + const match = reName.exec(str) if (match) { str = str.slice(0, match.index) + str.slice(match.index + match[0].length) val = match[1] } if (val !== undefined) { taskReqBody[key] = TASK_MAPPING[key].parse(val.trim()) - console.log(TASK_MAPPING[key].name + ':', taskReqBody[key]) + console.log(TASK_MAPPING[key].name + ":", taskReqBody[key]) if (!str) { break } @@ -490,18 +534,19 @@ function parseTaskFromText(str) { return undefined } const task = { reqBody: taskReqBody } - if ('seed' in taskReqBody) { + if ("seed" in taskReqBody) { task.seed = taskReqBody.seed } return task } async function parseContent(text) { - text = text.trim(); - if (text.startsWith('{') && text.endsWith('}')) { + text = text.trim() + if (text.startsWith("{") && text.endsWith("}")) { try { const task = JSON.parse(text) - if (!('reqBody' in task)) { // support the format saved to the disk, by the UI + if (!("reqBody" in task)) { + // support the format saved to the disk, by the UI task.reqBody = Object.assign({}, task) } restoreTaskToUI(task) @@ -513,7 +558,8 @@ async function parseContent(text) { } // Normal txt file. const task = parseTaskFromText(text) - if (text.toLowerCase().includes('seed:') && task) { // only parse valid task content + if (text.toLowerCase().includes("seed:") && task) { + // only parse valid task content restoreTaskToUI(task) return true } else { @@ -530,21 +576,25 @@ async function readFile(file, i) { } function dropHandler(ev) { - console.log('Content dropped...') + console.log("Content dropped...") let items = [] - if (ev?.dataTransfer?.items) { // Use DataTransferItemList interface + if (ev?.dataTransfer?.items) { + // Use DataTransferItemList interface items = Array.from(ev.dataTransfer.items) - items = items.filter(item => item.kind === 'file') - items = items.map(item => item.getAsFile()) - } else if (ev?.dataTransfer?.files) { // Use DataTransfer interface + items = items.filter((item) => item.kind === "file") + items = items.map((item) => item.getAsFile()) + } else if (ev?.dataTransfer?.files) { + // Use DataTransfer interface items = Array.from(ev.dataTransfer.files) } - items.forEach(item => {item.file_ext = EXT_REGEX.exec(item.name.toLowerCase())[1]}) + items.forEach((item) => { + item.file_ext = EXT_REGEX.exec(item.name.toLowerCase())[1] + }) - let text_items = items.filter(item => TEXT_EXTENSIONS.includes(item.file_ext)) - let image_items = items.filter(item => IMAGE_EXTENSIONS.includes(item.file_ext)) + let text_items = items.filter((item) => TEXT_EXTENSIONS.includes(item.file_ext)) + let image_items = items.filter((item) => IMAGE_EXTENSIONS.includes(item.file_ext)) if (image_items.length > 0 && ev.target == initImageSelector) { return // let the event bubble up, so that the Init Image filepicker can receive this @@ -554,7 +604,7 @@ function dropHandler(ev) { text_items.forEach(readFile) } function dragOverHandler(ev) { - console.log('Content in drop zone') + console.log("Content in drop zone") // Prevent default behavior (Prevent file/content from being opened) ev.preventDefault() @@ -562,73 +612,72 @@ function dragOverHandler(ev) { ev.dataTransfer.dropEffect = "copy" let img = new Image() - img.src = '//' + location.host + '/media/images/favicon-32x32.png' + img.src = "//" + location.host + "/media/images/favicon-32x32.png" ev.dataTransfer.setDragImage(img, 16, 16) } document.addEventListener("drop", dropHandler) document.addEventListener("dragover", dragOverHandler) -const TASK_REQ_NO_EXPORT = [ - "use_cpu", - "save_to_disk_path" -] -const resetSettings = document.getElementById('reset-image-settings') +const TASK_REQ_NO_EXPORT = ["use_cpu", "save_to_disk_path"] +const resetSettings = document.getElementById("reset-image-settings") -function checkReadTextClipboardPermission (result) { +function checkReadTextClipboardPermission(result) { if (result.state != "granted" && result.state != "prompt") { return } // PASTE ICON - const pasteIcon = document.createElement('i') - pasteIcon.className = 'fa-solid fa-paste section-button' + const pasteIcon = document.createElement("i") + pasteIcon.className = "fa-solid fa-paste section-button" pasteIcon.innerHTML = `Paste Image Settings` - pasteIcon.addEventListener('click', async (event) => { + pasteIcon.addEventListener("click", async (event) => { event.stopPropagation() // Add css class 'active' - pasteIcon.classList.add('active') + pasteIcon.classList.add("active") // In 350 ms remove the 'active' class - asyncDelay(350).then(() => pasteIcon.classList.remove('active')) + asyncDelay(350).then(() => pasteIcon.classList.remove("active")) // Retrieve clipboard content and try to parse it - const text = await navigator.clipboard.readText(); + const text = await navigator.clipboard.readText() await parseContent(text) }) resetSettings.parentNode.insertBefore(pasteIcon, resetSettings) } -navigator.permissions.query({ name: "clipboard-read" }).then(checkReadTextClipboardPermission, (reason) => console.log('clipboard-read is not available. %o', reason)) +navigator.permissions + .query({ name: "clipboard-read" }) + .then(checkReadTextClipboardPermission, (reason) => console.log("clipboard-read is not available. %o", reason)) -document.addEventListener('paste', async (event) => { +document.addEventListener("paste", async (event) => { if (event.target) { const targetTag = event.target.tagName.toLowerCase() // Disable when targeting input elements. - if (targetTag === 'input' || targetTag === 'textarea') { + if (targetTag === "input" || targetTag === "textarea") { return } } - const paste = (event.clipboardData || window.clipboardData).getData('text') + const paste = (event.clipboardData || window.clipboardData).getData("text") const selection = window.getSelection() - if (paste != "" && selection.toString().trim().length <= 0 && await parseContent(paste)) { + if (paste != "" && selection.toString().trim().length <= 0 && (await parseContent(paste))) { event.preventDefault() return } }) // Adds a copy and a paste icon if the browser grants permission to write to clipboard. -function checkWriteToClipboardPermission (result) { +function checkWriteToClipboardPermission(result) { if (result.state != "granted" && result.state != "prompt") { return } // COPY ICON - const copyIcon = document.createElement('i') - copyIcon.className = 'fa-solid fa-clipboard section-button' + const copyIcon = document.createElement("i") + copyIcon.className = "fa-solid fa-clipboard section-button" copyIcon.innerHTML = `Copy Image Settings` - copyIcon.addEventListener('click', (event) => { + copyIcon.addEventListener("click", (event) => { event.stopPropagation() // Add css class 'active' - copyIcon.classList.add('active') + copyIcon.classList.add("active") // In 350 ms remove the 'active' class - asyncDelay(350).then(() => copyIcon.classList.remove('active')) + asyncDelay(350).then(() => copyIcon.classList.remove("active")) const uiState = readUI() TASK_REQ_NO_EXPORT.forEach((key) => delete uiState.reqBody[key]) if (uiState.reqBody.init_image && !IMAGE_REGEX.test(uiState.reqBody.init_image)) { @@ -641,8 +690,8 @@ function checkWriteToClipboardPermission (result) { } // Determine which access we have to the clipboard. Clipboard access is only available on localhost or via TLS. navigator.permissions.query({ name: "clipboard-write" }).then(checkWriteToClipboardPermission, (e) => { - if (e instanceof TypeError && typeof navigator?.clipboard?.writeText === 'function') { + if (e instanceof TypeError && typeof navigator?.clipboard?.writeText === "function") { // Fix for firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1560373 - checkWriteToClipboardPermission({state:"granted"}) + checkWriteToClipboardPermission({ state: "granted" }) } }) diff --git a/ui/media/js/engine.js b/ui/media/js/engine.js index 02871b23..a0eb9cef 100644 --- a/ui/media/js/engine.js +++ b/ui/media/js/engine.js @@ -1,6 +1,7 @@ /** SD-UI Backend control and classes. */ -(function () { "use strict"; +;(function() { + "use strict" const RETRY_DELAY_IF_BUFFER_IS_EMPTY = 1000 // ms const RETRY_DELAY_IF_SERVER_IS_BUSY = 30 * 1000 // ms, status_code 503, already a task running const RETRY_DELAY_ON_ERROR = 4000 // ms @@ -14,25 +15,28 @@ * Allows closing the connection while the server buffers more data. */ class ChunkedStreamReader { - #bufferedString = '' // Data received waiting to be read. + #bufferedString = "" // Data received waiting to be read. #url #fetchOptions #response - constructor(url, initialContent='', options={}) { - if (typeof url !== 'string' && !(url instanceof String)) { - throw new Error('url is not a string.') + constructor(url, initialContent = "", options = {}) { + if (typeof url !== "string" && !(url instanceof String)) { + throw new Error("url is not a string.") } - if (typeof initialContent !== 'undefined' && typeof initialContent !== 'string') { - throw new Error('initialContent is not a string.') + if (typeof initialContent !== "undefined" && typeof initialContent !== "string") { + throw new Error("initialContent is not a string.") } this.#bufferedString = initialContent this.#url = url - this.#fetchOptions = Object.assign({ - headers: { - 'Content-Type': 'application/json' - } - }, options) + this.#fetchOptions = Object.assign( + { + headers: { + "Content-Type": "application/json" + } + }, + options + ) this.onNext = undefined } @@ -53,7 +57,7 @@ } parse(value) { - if (typeof value === 'undefined') { + if (typeof value === "undefined") { return } if (!isArrayOrTypedArray(value)) { @@ -62,7 +66,7 @@ if (value.length === 0) { return value } - if (typeof this.textDecoder === 'undefined') { + if (typeof this.textDecoder === "undefined") { this.textDecoder = new TextDecoder() } return [this.textDecoder.decode(value)] @@ -73,8 +77,8 @@ onError(response) { throw new Error(response.statusText) } - onNext({value, done}, response) { - return {value, done} + onNext({ value, done }, response) { + return { value, done } } async *[Symbol.asyncIterator]() { @@ -93,9 +97,9 @@ continue } // Request status indicate failure - console.warn('Stream %o stopped unexpectedly.', this.#response) + console.warn("Stream %o stopped unexpectedly.", this.#response) value = await Promise.resolve(this.onError(this.#response)) - if (typeof value === 'boolean' && value) { + if (typeof value === "boolean" && value) { continue } return value @@ -106,8 +110,10 @@ const readState = await reader.read() value = this.parse(readState.value) if (value) { - for(let sVal of value) { - ({value: sVal, done} = await Promise.resolve(this.onNext({value:sVal, done:readState.done}))) + for (let sVal of value) { + ;({ value: sVal, done } = await Promise.resolve( + this.onNext({ value: sVal, done: readState.done }) + )) yield sVal if (done) { return this.onComplete(sVal) @@ -117,12 +123,12 @@ if (done) { return } - } while(value && !done) + } while (value && !done) } while (!done && (this.#response.ok || this.#response.status === 425)) } *readStreamAsJSON(jsonStr, throwOnError) { - if (typeof jsonStr !== 'string') { - throw new Error('jsonStr is not a string.') + if (typeof jsonStr !== "string") { + throw new Error("jsonStr is not a string.") } do { if (this.#bufferedString.length > 0) { @@ -132,19 +138,19 @@ } else { jsonStr = this.#bufferedString } - this.#bufferedString = '' + this.#bufferedString = "" } if (!jsonStr) { return } // Find next delimiter - let lastChunkIdx = jsonStr.indexOf('}{') + let lastChunkIdx = jsonStr.indexOf("}{") if (lastChunkIdx >= 0) { this.#bufferedString = jsonStr.substring(0, lastChunkIdx + 1) jsonStr = jsonStr.substring(lastChunkIdx + 1) } else { this.#bufferedString = jsonStr - jsonStr = '' + jsonStr = "" } if (this.#bufferedString.length <= 0) { return @@ -153,10 +159,11 @@ // this results in having to parse JSON like {"step": 1}{"step": 2}{"step": 3}{"ste... // which is obviously invalid and can happen at any point while rendering. // So we need to extract only the next {} section - try { // Try to parse + try { + // Try to parse const jsonObj = JSON.parse(this.#bufferedString) this.#bufferedString = jsonStr - jsonStr = '' + jsonStr = "" yield jsonObj } catch (e) { if (throwOnError) { @@ -168,18 +175,18 @@ } throw e } - } while (this.#bufferedString.length > 0 && this.#bufferedString.indexOf('}') >= 0) + } while (this.#bufferedString.length > 0 && this.#bufferedString.indexOf("}") >= 0) } } - const EVENT_IDLE = 'idle' - const EVENT_STATUS_CHANGED = 'statusChange' - const EVENT_UNHANDLED_REJECTION = 'unhandledRejection' - const EVENT_TASK_QUEUED = 'taskQueued' - const EVENT_TASK_START = 'taskStart' - const EVENT_TASK_END = 'taskEnd' - const EVENT_TASK_ERROR = 'task_error' - const EVENT_UNEXPECTED_RESPONSE = 'unexpectedResponse' + const EVENT_IDLE = "idle" + const EVENT_STATUS_CHANGED = "statusChange" + const EVENT_UNHANDLED_REJECTION = "unhandledRejection" + const EVENT_TASK_QUEUED = "taskQueued" + const EVENT_TASK_START = "taskStart" + const EVENT_TASK_END = "taskEnd" + const EVENT_TASK_ERROR = "task_error" + const EVENT_UNEXPECTED_RESPONSE = "unexpectedResponse" const EVENTS_TYPES = [ EVENT_IDLE, EVENT_STATUS_CHANGED, @@ -190,85 +197,86 @@ EVENT_TASK_END, EVENT_TASK_ERROR, - EVENT_UNEXPECTED_RESPONSE, + EVENT_UNEXPECTED_RESPONSE ] Object.freeze(EVENTS_TYPES) const eventSource = new GenericEventSource(EVENTS_TYPES) function setServerStatus(msgType, msg) { - return eventSource.fireEvent(EVENT_STATUS_CHANGED, {type: msgType, message: msg}) + return eventSource.fireEvent(EVENT_STATUS_CHANGED, { type: msgType, message: msg }) } const ServerStates = { - init: 'Init', - loadingModel: 'LoadingModel', - online: 'Online', - rendering: 'Rendering', - unavailable: 'Unavailable', + init: "Init", + loadingModel: "LoadingModel", + online: "Online", + rendering: "Rendering", + unavailable: "Unavailable" } Object.freeze(ServerStates) let sessionId = Date.now() - let serverState = {'status': ServerStates.unavailable, 'time': Date.now()} + let serverState = { status: ServerStates.unavailable, time: Date.now() } async function healthCheck() { - if (Date.now() < serverState.time + (HEALTH_PING_INTERVAL / 2) && isServerAvailable()) { + if (Date.now() < serverState.time + HEALTH_PING_INTERVAL / 2 && isServerAvailable()) { // Ping confirmed online less than half of HEALTH_PING_INTERVAL ago. return true } if (Date.now() >= serverState.time + SERVER_STATE_VALIDITY_DURATION) { - console.warn('WARNING! SERVER_STATE_VALIDITY_DURATION has elapsed since the last Ping completed.') + console.warn("WARNING! SERVER_STATE_VALIDITY_DURATION has elapsed since the last Ping completed.") } try { let res = undefined - if (typeof sessionId !== 'undefined') { - res = await fetch('/ping?session_id=' + sessionId) + if (typeof sessionId !== "undefined") { + res = await fetch("/ping?session_id=" + sessionId) } else { - res = await fetch('/ping') + res = await fetch("/ping") } serverState = await res.json() - if (typeof serverState !== 'object' || typeof serverState.status !== 'string') { + if (typeof serverState !== "object" || typeof serverState.status !== "string") { console.error(`Server reply didn't contain a state value.`) - serverState = {'status': ServerStates.unavailable, 'time': Date.now()} - setServerStatus('error', 'offline') + serverState = { status: ServerStates.unavailable, time: Date.now() } + setServerStatus("error", "offline") return false } // Set status - switch(serverState.status) { + switch (serverState.status) { case ServerStates.init: // Wait for init to complete before updating status. break case ServerStates.online: - setServerStatus('online', 'ready') + setServerStatus("online", "ready") break case ServerStates.loadingModel: - setServerStatus('busy', 'loading..') + setServerStatus("busy", "loading..") break case ServerStates.rendering: - setServerStatus('busy', 'rendering..') + setServerStatus("busy", "rendering..") break - default: // Unavailable - console.error('Ping received an unexpected server status. Status: %s', serverState.status) - setServerStatus('error', serverState.status.toLowerCase()) + default: + // Unavailable + console.error("Ping received an unexpected server status. Status: %s", serverState.status) + setServerStatus("error", serverState.status.toLowerCase()) break } serverState.time = Date.now() return true } catch (e) { console.error(e) - serverState = {'status': ServerStates.unavailable, 'time': Date.now()} - setServerStatus('error', 'offline') + serverState = { status: ServerStates.unavailable, time: Date.now() } + setServerStatus("error", "offline") } return false } function isServerAvailable() { - if (typeof serverState !== 'object') { - console.error('serverState not set to a value. Connection to server could be lost...') + if (typeof serverState !== "object") { + console.error("serverState not set to a value. Connection to server could be lost...") return false } if (Date.now() >= serverState.time + SERVER_STATE_VALIDITY_DURATION) { - console.warn('SERVER_STATE_VALIDITY_DURATION elapsed. Connection to server could be lost...') + console.warn("SERVER_STATE_VALIDITY_DURATION elapsed. Connection to server could be lost...") return false } switch (serverState.status) { @@ -277,51 +285,54 @@ case ServerStates.online: return true default: - console.warn('Unexpected server status. Server could be unavailable... Status: %s', serverState.status) + console.warn("Unexpected server status. Server could be unavailable... Status: %s", serverState.status) return false } } async function waitUntil(isReadyFn, delay, timeout) { - if (typeof delay === 'number') { + if (typeof delay === "number") { const msDelay = delay delay = () => asyncDelay(msDelay) } - if (typeof delay !== 'function') { - throw new Error('delay is not a number or a function.') + if (typeof delay !== "function") { + throw new Error("delay is not a number or a function.") } - if (typeof timeout !== 'undefined' && typeof timeout !== 'number') { - throw new Error('timeout is not a number.') + if (typeof timeout !== "undefined" && typeof timeout !== "number") { + throw new Error("timeout is not a number.") } - if (typeof timeout === 'undefined' || timeout < 0) { + if (typeof timeout === "undefined" || timeout < 0) { timeout = Number.MAX_SAFE_INTEGER } timeout = Date.now() + timeout - while (timeout > Date.now() - && Date.now() < serverState.time + SERVER_STATE_VALIDITY_DURATION - && !Boolean(await Promise.resolve(isReadyFn())) + while ( + timeout > Date.now() && + Date.now() < serverState.time + SERVER_STATE_VALIDITY_DURATION && + !Boolean(await Promise.resolve(isReadyFn())) ) { await delay() - if (!isServerAvailable()) { // Can fail if ping got frozen/suspended... - if (await healthCheck() && isServerAvailable()) { // Force a recheck of server status before failure... + if (!isServerAvailable()) { + // Can fail if ping got frozen/suspended... + if ((await healthCheck()) && isServerAvailable()) { + // Force a recheck of server status before failure... continue // Continue waiting if last healthCheck confirmed the server is still alive. } - throw new Error('Connection with server lost.') + throw new Error("Connection with server lost.") } } if (Date.now() >= serverState.time + SERVER_STATE_VALIDITY_DURATION) { - console.warn('SERVER_STATE_VALIDITY_DURATION elapsed. Released waitUntil on stale server state.') + console.warn("SERVER_STATE_VALIDITY_DURATION elapsed. Released waitUntil on stale server state.") } } const TaskStatus = { - init: 'init', - pending: 'pending', // Queued locally, not yet posted to server - waiting: 'waiting', // Waiting to run on server - processing: 'processing', - stopped: 'stopped', - completed: 'completed', - failed: 'failed', + init: "init", + pending: "pending", // Queued locally, not yet posted to server + waiting: "waiting", // Waiting to run on server + processing: "processing", + stopped: "stopped", + completed: "completed", + failed: "failed" } Object.freeze(TaskStatus) @@ -329,7 +340,7 @@ TaskStatus.init, TaskStatus.pending, TaskStatus.waiting, - TaskStatus.processing, + TaskStatus.processing //Don't add status that are final. ] @@ -345,12 +356,15 @@ #id = undefined #exception = undefined - constructor(options={}) { + constructor(options = {}) { this._reqBody = Object.assign({}, options) - if (typeof this._reqBody.session_id === 'undefined') { + if (typeof this._reqBody.session_id === "undefined") { this._reqBody.session_id = sessionId - } else if (this._reqBody.session_id !== SD.sessionId && String(this._reqBody.session_id) !== String(SD.sessionId)) { - throw new Error('Use SD.sessionId to set the request session_id.') + } else if ( + this._reqBody.session_id !== SD.sessionId && + String(this._reqBody.session_id) !== String(SD.sessionId) + ) { + throw new Error("Use SD.sessionId to set the request session_id.") } this._reqBody.session_id = String(this._reqBody.session_id) } @@ -359,8 +373,8 @@ return this.#id } _setId(id) { - if (typeof this.#id !== 'undefined') { - throw new Error('The task ID can only be set once.') + if (typeof this.#id !== "undefined") { + throw new Error("The task ID can only be set once.") } this.#id = id } @@ -372,32 +386,32 @@ if (this.isCompleted || this.isStopped || this.hasFailed) { return } - if (typeof exception !== 'undefined') { - if (typeof exception === 'string') { + if (typeof exception !== "undefined") { + if (typeof exception === "string") { exception = new Error(exception) } - if (typeof exception !== 'object') { - throw new Error('exception is not an object.') + if (typeof exception !== "object") { + throw new Error("exception is not an object.") } if (!(exception instanceof Error)) { - throw new Error('exception is not an Error or a string.') + throw new Error("exception is not an Error or a string.") } } - const res = await fetch('/image/stop?task=' + this.id) + const res = await fetch("/image/stop?task=" + this.id) if (!res.ok) { - console.log('Stop response:', res) + console.log("Stop response:", res) throw new Error(res.statusText) } task_queue.delete(this) this.#exception = exception - this.#status = (exception ? TaskStatus.failed : TaskStatus.stopped) + this.#status = exception ? TaskStatus.failed : TaskStatus.stopped } get reqBody() { if (this.#status === TaskStatus.init) { return this._reqBody } - console.warn('Task reqBody cannot be changed after the init state.') + console.warn("Task reqBody cannot be changed after the init state.") return Object.assign({}, this._reqBody) } @@ -436,29 +450,29 @@ * @returns the response from the render request. * @memberof Task */ - async post(url, timeout=-1) { - if(this.status !== TaskStatus.init && this.status !== TaskStatus.pending) { + async post(url, timeout = -1) { + if (this.status !== TaskStatus.init && this.status !== TaskStatus.pending) { throw new Error(`Task status ${this.status} is not valid for post.`) } this._setStatus(TaskStatus.pending) Object.freeze(this._reqBody) - const abortSignal = (timeout >= 0 ? AbortSignal.timeout(timeout) : undefined) + const abortSignal = timeout >= 0 ? AbortSignal.timeout(timeout) : undefined let res = undefined try { this.checkReqBody() do { abortSignal?.throwIfAborted() res = await fetch(url, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json" }, body: JSON.stringify(this._reqBody), signal: abortSignal }) // status_code 503, already a task running. - } while (res.status === 503 && await asyncDelay(RETRY_DELAY_IF_SERVER_IS_BUSY)) + } while (res.status === 503 && (await asyncDelay(RETRY_DELAY_IF_SERVER_IS_BUSY))) } catch (err) { this.abort(err) throw err @@ -479,20 +493,20 @@ if (!value || value.length <= 0) { return } - return reader.readStreamAsJSON(value.join('')) + return reader.readStreamAsJSON(value.join("")) } - reader.onNext = function({done, value}) { + reader.onNext = function({ done, value }) { // By default is completed when the return value has a status defined. - if (typeof value === 'object' && 'status' in value) { + if (typeof value === "object" && "status" in value) { done = true } - return {done, value} + return { done, value } } return reader } _setReader(reader) { - if (typeof this.#reader !== 'undefined') { - throw new Error('The task reader can only be set once.') + if (typeof this.#reader !== "undefined") { + throw new Error("The task reader can only be set once.") } this.#reader = reader } @@ -501,25 +515,26 @@ return this.#reader } if (!this.streamUrl) { - throw new Error('The task has no stream Url defined.') + throw new Error("The task has no stream Url defined.") } this.#reader = Task.getReader(this.streamUrl) const task = this const onNext = this.#reader.onNext - this.#reader.onNext = function({done, value}) { - if (value && typeof value === 'object') { - if (task.status === TaskStatus.init - || task.status === TaskStatus.pending - || task.status === TaskStatus.waiting + this.#reader.onNext = function({ done, value }) { + if (value && typeof value === "object") { + if ( + task.status === TaskStatus.init || + task.status === TaskStatus.pending || + task.status === TaskStatus.waiting ) { task._setStatus(TaskStatus.processing) } - if ('step' in value && 'total_steps' in value) { + if ("step" in value && "total_steps" in value) { task.step = value.step task.total_steps = value.total_steps } } - return onNext.call(this, {done, value}) + return onNext.call(this, { done, value }) } this.#reader.onComplete = function(value) { task.result = value @@ -536,12 +551,12 @@ return this.#reader } - async waitUntil({timeout=-1, callback, status, signal}) { + async waitUntil({ timeout = -1, callback, status, signal }) { const currentIdx = TASK_STATUS_ORDER.indexOf(this.#status) if (currentIdx <= 0) { return false } - const stIdx = (status ? TASK_STATUS_ORDER.indexOf(status) : currentIdx + 1) + const stIdx = status ? TASK_STATUS_ORDER.indexOf(status) : currentIdx + 1 if (stIdx >= 0 && stIdx <= currentIdx) { return true } @@ -552,26 +567,34 @@ return false } const task = this - switch(this.#status) { + switch (this.#status) { case TaskStatus.pending: case TaskStatus.waiting: // Wait for server status to include this task. await waitUntil( async () => { - if (task.#id && typeof serverState.tasks === 'object' && Object.keys(serverState.tasks).includes(String(task.#id))) { + if ( + task.#id && + typeof serverState.tasks === "object" && + Object.keys(serverState.tasks).includes(String(task.#id)) + ) { return true } - if (await Promise.resolve(callback?.call(task)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(task))) || signal?.aborted) { return true } }, TASK_STATE_SERVER_UPDATE_DELAY, - timeout, + timeout ) - if (this.#id && typeof serverState.tasks === 'object' && Object.keys(serverState.tasks).includes(String(task.#id))) { + if ( + this.#id && + typeof serverState.tasks === "object" && + Object.keys(serverState.tasks).includes(String(task.#id)) + ) { this._setStatus(TaskStatus.waiting) } - if (await Promise.resolve(callback?.call(this)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(this))) || signal?.aborted) { return false } if (stIdx >= 0 && stIdx <= TASK_STATUS_ORDER.indexOf(TaskStatus.waiting)) { @@ -580,21 +603,25 @@ // Wait for task to start on server. await waitUntil( async () => { - if (typeof serverState.tasks !== 'object' || serverState.tasks[String(task.#id)] !== 'pending') { + if ( + typeof serverState.tasks !== "object" || + serverState.tasks[String(task.#id)] !== "pending" + ) { return true } - if (await Promise.resolve(callback?.call(task)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(task))) || signal?.aborted) { return true } }, TASK_STATE_SERVER_UPDATE_DELAY, - timeout, + timeout ) - const state = (typeof serverState.tasks === 'object' ? serverState.tasks[String(task.#id)] : undefined) - if (state === 'running' || state === 'buffer' || state === 'completed') { + const state = + typeof serverState.tasks === "object" ? serverState.tasks[String(task.#id)] : undefined + if (state === "running" || state === "buffer" || state === "completed") { this._setStatus(TaskStatus.processing) } - if (await Promise.resolve(callback?.call(task)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(task))) || signal?.aborted) { return false } if (stIdx >= 0 && stIdx <= TASK_STATUS_ORDER.indexOf(TaskStatus.processing)) { @@ -603,15 +630,18 @@ case TaskStatus.processing: await waitUntil( async () => { - if (typeof serverState.tasks !== 'object' || serverState.tasks[String(task.#id)] !== 'running') { + if ( + typeof serverState.tasks !== "object" || + serverState.tasks[String(task.#id)] !== "running" + ) { return true } - if (await Promise.resolve(callback?.call(task)) || signal?.aborted) { + if ((await Promise.resolve(callback?.call(task))) || signal?.aborted) { return true } }, TASK_STATE_SERVER_UPDATE_DELAY, - timeout, + timeout ) await Promise.resolve(callback?.call(this)) default: @@ -625,22 +655,22 @@ } this._setStatus(TaskStatus.pending) task_queue.set(this, promiseGenerator) - await eventSource.fireEvent(EVENT_TASK_QUEUED, {task:this}) + await eventSource.fireEvent(EVENT_TASK_QUEUED, { task: this }) await Task.enqueue(promiseGenerator, ...args) - await this.waitUntil({status: TaskStatus.completed}) + await this.waitUntil({ status: TaskStatus.completed }) if (this.exception) { throw this.exception } return this.result } static async enqueue(promiseGenerator, ...args) { - if (typeof promiseGenerator === 'undefined') { - throw new Error('To enqueue a concurrent task, a *Promise Generator is needed but undefined was found.') + if (typeof promiseGenerator === "undefined") { + throw new Error("To enqueue a concurrent task, a *Promise Generator is needed but undefined was found.") } //if (Symbol.asyncIterator in result || Symbol.iterator in result) { - //concurrent_generators.set(result, Promise.resolve(args)) - if (typeof promiseGenerator === 'function') { - concurrent_generators.set(asGenerator({callback: promiseGenerator}), Promise.resolve(args)) + //concurrent_generators.set(result, Promise.resolve(args)) + if (typeof promiseGenerator === "function") { + concurrent_generators.set(asGenerator({ callback: promiseGenerator }), Promise.resolve(args)) } else { concurrent_generators.set(promiseGenerator, Promise.resolve(args)) } @@ -649,23 +679,23 @@ } static enqueueNew(task, classCtor, progressCallback) { if (task.status !== TaskStatus.init) { - throw new Error('Task has an invalid status to add to queue.') + throw new Error("Task has an invalid status to add to queue.") } if (!(task instanceof classCtor)) { - throw new Error('Task is not a instance of classCtor.') + throw new Error("Task is not a instance of classCtor.") } let promiseGenerator = undefined - if (typeof progressCallback === 'undefined') { + if (typeof progressCallback === "undefined") { promiseGenerator = classCtor.start(task) - } else if (typeof progressCallback === 'function') { + } else if (typeof progressCallback === "function") { promiseGenerator = classCtor.start(task, progressCallback) } else { - throw new Error('progressCallback is not a function.') + throw new Error("progressCallback is not a function.") } return Task.prototype.enqueue.call(task, promiseGenerator) } - static async run(promiseGenerator, {callback, signal, timeout=-1}={}) { + static async run(promiseGenerator, { callback, signal, timeout = -1 } = {}) { let value = undefined let done = undefined if (timeout < 0) { @@ -673,20 +703,20 @@ } timeout = Date.now() + timeout do { - ({value, done} = await Promise.resolve(promiseGenerator.next(value))) + ;({ value, done } = await Promise.resolve(promiseGenerator.next(value))) if (value instanceof Promise) { value = await value } if (callback) { - ({value, done} = await Promise.resolve(callback.call(promiseGenerator, {value, done}))) + ;({ value, done } = await Promise.resolve(callback.call(promiseGenerator, { value, done }))) } if (value instanceof Promise) { value = await value } - } while(!done && !signal?.aborted && timeout > Date.now()) + } while (!done && !signal?.aborted && timeout > Date.now()) return value } - static async *asGenerator({callback, generator, signal, timeout=-1}={}) { + static async *asGenerator({ callback, generator, signal, timeout = -1 } = {}) { let value = undefined let done = undefined if (timeout < 0) { @@ -694,69 +724,69 @@ } timeout = Date.now() + timeout do { - ({value, done} = await Promise.resolve(generator.next(value))) + ;({ value, done } = await Promise.resolve(generator.next(value))) if (value instanceof Promise) { value = await value } if (callback) { - ({value, done} = await Promise.resolve(callback.call(generator, {value, done}))) + ;({ value, done } = await Promise.resolve(callback.call(generator, { value, done }))) if (value instanceof Promise) { value = await value } } value = yield value - } while(!done && !signal?.aborted && timeout > Date.now()) + } while (!done && !signal?.aborted && timeout > Date.now()) return value } } const TASK_REQUIRED = { - "session_id": 'string', - "prompt": 'string', - "negative_prompt": 'string', - "width": 'number', - "height": 'number', - "seed": 'number', + session_id: "string", + prompt: "string", + negative_prompt: "string", + width: "number", + height: "number", + seed: "number", - "sampler_name": 'string', - "use_stable_diffusion_model": 'string', - "num_inference_steps": 'number', - "guidance_scale": 'number', + sampler_name: "string", + use_stable_diffusion_model: "string", + num_inference_steps: "number", + guidance_scale: "number", - "num_outputs": 'number', - "stream_progress_updates": 'boolean', - "stream_image_progress": 'boolean', - "show_only_filtered_image": 'boolean', - "output_format": 'string', - "output_quality": 'number', + num_outputs: "number", + stream_progress_updates: "boolean", + stream_image_progress: "boolean", + show_only_filtered_image: "boolean", + output_format: "string", + output_quality: "number" } const TASK_DEFAULTS = { - "sampler_name": "plms", - "use_stable_diffusion_model": "sd-v1-4", - "num_inference_steps": 50, - "guidance_scale": 7.5, - "negative_prompt": "", + sampler_name: "plms", + use_stable_diffusion_model: "sd-v1-4", + num_inference_steps: 50, + guidance_scale: 7.5, + negative_prompt: "", - "num_outputs": 1, - "stream_progress_updates": true, - "stream_image_progress": true, - "show_only_filtered_image": true, - "block_nsfw": false, - "output_format": "png", - "output_quality": 75, - "output_lossless": false, + num_outputs: 1, + stream_progress_updates: true, + stream_image_progress: true, + show_only_filtered_image: true, + block_nsfw: false, + output_format: "png", + output_quality: 75, + output_lossless: false } const TASK_OPTIONAL = { - "device": 'string', - "init_image": 'string', - "mask": 'string', - "save_to_disk_path": 'string', - "use_face_correction": 'string', - "use_upscale": 'string', - "use_vae_model": 'string', - "use_hypernetwork_model": 'string', - "hypernetwork_strength": 'number', - "output_lossless": 'boolean', + device: "string", + init_image: "string", + mask: "string", + save_to_disk_path: "string", + use_face_correction: "string", + use_upscale: "string", + use_vae_model: "string", + use_hypernetwork_model: "string", + hypernetwork_strength: "number", + output_lossless: "boolean" } // Higer values will result in... @@ -764,36 +794,42 @@ const MAX_SEED_VALUE = 4294967295 class RenderTask extends Task { - constructor(options={}) { + constructor(options = {}) { super(options) - if (typeof this._reqBody.seed === 'undefined') { + if (typeof this._reqBody.seed === "undefined") { this._reqBody.seed = Math.floor(Math.random() * (MAX_SEED_VALUE + 1)) } - if (typeof typeof this._reqBody.seed === 'number' && (this._reqBody.seed > MAX_SEED_VALUE || this._reqBody.seed < 0)) { + if ( + typeof typeof this._reqBody.seed === "number" && + (this._reqBody.seed > MAX_SEED_VALUE || this._reqBody.seed < 0) + ) { throw new Error(`seed must be in range 0 to ${MAX_SEED_VALUE}.`) } - if ('use_cpu' in this._reqBody) { + if ("use_cpu" in this._reqBody) { if (this._reqBody.use_cpu) { - this._reqBody.device = 'cpu' + this._reqBody.device = "cpu" } delete this._reqBody.use_cpu } if (this._reqBody.init_image) { - if (typeof this._reqBody.prompt_strength === 'undefined') { + if (typeof this._reqBody.prompt_strength === "undefined") { this._reqBody.prompt_strength = 0.8 - } else if (typeof this._reqBody.prompt_strength !== 'number') { - throw new Error(`prompt_strength need to be of type number but ${typeof this._reqBody.prompt_strength} was found.`) + } else if (typeof this._reqBody.prompt_strength !== "number") { + throw new Error( + `prompt_strength need to be of type number but ${typeof this._reqBody + .prompt_strength} was found.` + ) } } - if ('modifiers' in this._reqBody) { + if ("modifiers" in this._reqBody) { if (Array.isArray(this._reqBody.modifiers) && this._reqBody.modifiers.length > 0) { this._reqBody.modifiers = this._reqBody.modifiers.filter((val) => val.trim()) if (this._reqBody.modifiers.length > 0) { - this._reqBody.prompt = `${this._reqBody.prompt}, ${this._reqBody.modifiers.join(', ')}` + this._reqBody.prompt = `${this._reqBody.prompt}, ${this._reqBody.modifiers.join(", ")}` } } - if (typeof this._reqBody.modifiers === 'string' && this._reqBody.modifiers.length > 0) { + if (typeof this._reqBody.modifiers === "string" && this._reqBody.modifiers.length > 0) { this._reqBody.modifiers = this._reqBody.modifiers.trim() if (this._reqBody.modifiers.length > 0) { this._reqBody.prompt = `${this._reqBody.prompt}, ${this._reqBody.modifiers}` @@ -806,13 +842,15 @@ checkReqBody() { for (const key in TASK_DEFAULTS) { - if (typeof this._reqBody[key] === 'undefined') { + if (typeof this._reqBody[key] === "undefined") { this._reqBody[key] = TASK_DEFAULTS[key] } } for (const key in TASK_REQUIRED) { if (typeof this._reqBody[key] !== TASK_REQUIRED[key]) { - throw new Error(`${key} need to be of type ${TASK_REQUIRED[key]} but ${typeof this._reqBody[key]} was found.`) + throw new Error( + `${key} need to be of type ${TASK_REQUIRED[key]} but ${typeof this._reqBody[key]} was found.` + ) } } for (const key in this._reqBody) { @@ -826,7 +864,11 @@ continue } if (typeof this._reqBody[key] !== TASK_OPTIONAL[key]) { - throw new Error(`${key} need to be of type ${TASK_OPTIONAL[key]} but ${typeof this._reqBody[key]} was found.`) + throw new Error( + `${key} need to be of type ${TASK_OPTIONAL[key]} but ${typeof this._reqBody[ + key + ]} was found.` + ) } } } @@ -837,23 +879,26 @@ * @returns the response from the render request. * @memberof Task */ - async post(timeout=-1) { - performance.mark('make-render-request') - if (performance.getEntriesByName('click-makeImage', 'mark').length > 0) { - performance.measure('diff', 'click-makeImage', 'make-render-request') - console.log('delay between clicking and making the server request:', performance.getEntriesByName('diff', 'measure')[0].duration + ' ms') + async post(timeout = -1) { + performance.mark("make-render-request") + if (performance.getEntriesByName("click-makeImage", "mark").length > 0) { + performance.measure("diff", "click-makeImage", "make-render-request") + console.log( + "delay between clicking and making the server request:", + performance.getEntriesByName("diff", "measure")[0].duration + " ms" + ) } - let jsonResponse = await super.post('/render', timeout) - if (typeof jsonResponse?.task !== 'number') { - console.warn('Endpoint error response: ', jsonResponse) - const event = Object.assign({task:this}, jsonResponse) + let jsonResponse = await super.post("/render", timeout) + if (typeof jsonResponse?.task !== "number") { + console.warn("Endpoint error response: ", jsonResponse) + const event = Object.assign({ task: this }, jsonResponse) await eventSource.fireEvent(EVENT_UNEXPECTED_RESPONSE, event) - if ('continueWith' in event) { + if ("continueWith" in event) { jsonResponse = await Promise.resolve(event.continueWith) } - if (typeof jsonResponse?.task !== 'number') { - const err = new Error(jsonResponse?.detail || 'Endpoint response does not contains a task ID.') + if (typeof jsonResponse?.task !== "number") { + const err = new Error(jsonResponse?.detail || "Endpoint response does not contains a task ID.") this.abort(err) throw err } @@ -870,71 +915,72 @@ return Task.enqueueNew(this, RenderTask, progressCallback) } *start(progressCallback) { - if (typeof progressCallback !== 'undefined' && typeof progressCallback !== 'function') { - throw new Error('progressCallback is not a function. progressCallback type: ' + typeof progressCallback) + if (typeof progressCallback !== "undefined" && typeof progressCallback !== "function") { + throw new Error("progressCallback is not a function. progressCallback type: " + typeof progressCallback) } if (this.isStopped) { return } this._setStatus(TaskStatus.pending) - progressCallback?.call(this, {reqBody: this._reqBody}) + progressCallback?.call(this, { reqBody: this._reqBody }) Object.freeze(this._reqBody) // Post task request to backend let renderRequest = undefined try { renderRequest = yield this.post() - yield progressCallback?.call(this, {renderResponse: renderRequest}) + yield progressCallback?.call(this, { renderResponse: renderRequest }) } catch (e) { yield progressCallback?.call(this, { detail: e.message }) throw e } - try { // Wait for task to start on server. + try { + // Wait for task to start on server. yield this.waitUntil({ - callback: function() { return progressCallback?.call(this, {}) }, - status: TaskStatus.processing, + callback: function() { + return progressCallback?.call(this, {}) + }, + status: TaskStatus.processing }) } catch (e) { this.abort(err) throw e } // Update class status and callback. - const taskState = (typeof serverState.tasks === 'object' ? serverState.tasks[String(this.id)] : undefined) - switch(taskState) { - case 'pending': // Session has pending tasks. - console.error('Server %o render request %o is still waiting.', serverState, renderRequest) + const taskState = typeof serverState.tasks === "object" ? serverState.tasks[String(this.id)] : undefined + switch (taskState) { + case "pending": // Session has pending tasks. + console.error("Server %o render request %o is still waiting.", serverState, renderRequest) //Only update status if not already set by waitUntil - if (this.status === TaskStatus.init - || this.status === TaskStatus.pending - ) { + if (this.status === TaskStatus.init || this.status === TaskStatus.pending) { // Set status as Waiting in backend. this._setStatus(TaskStatus.waiting) } break - case 'running': - case 'buffer': + case "running": + case "buffer": // Normal expected messages. this._setStatus(TaskStatus.processing) break - case 'completed': + case "completed": if (this.isPending) { // Set state to processing until we read the reply this._setStatus(TaskStatus.processing) } - console.warn('Server %o render request %o completed unexpectedly', serverState, renderRequest) + console.warn("Server %o render request %o completed unexpectedly", serverState, renderRequest) break // Continue anyway to try to read cached result. - case 'error': + case "error": this._setStatus(TaskStatus.failed) - console.error('Server %o render request %o has failed', serverState, renderRequest) + console.error("Server %o render request %o has failed", serverState, renderRequest) break // Still valid, Update UI with error message - case 'stopped': + case "stopped": this._setStatus(TaskStatus.stopped) - console.log('Server %o render request %o was stopped', serverState, renderRequest) + console.log("Server %o render request %o was stopped", serverState, renderRequest) return false default: if (!progressCallback) { - const err = new Error('Unexpected server task state: ' + taskState || 'Undefined') + const err = new Error("Unexpected server task state: " + taskState || "Undefined") this.abort(err) throw err } @@ -967,17 +1013,17 @@ let done = undefined yield progressCallback?.call(this, { stream: streamGenerator }) do { - ({value, done} = yield streamGenerator.next()) - if (typeof value !== 'object') { + ;({ value, done } = yield streamGenerator.next()) + if (typeof value !== "object") { continue } yield progressCallback?.call(this, { update: value }) - } while(!done) + } while (!done) return value } static start(task, progressCallback) { - if (typeof task !== 'object') { - throw new Error ('task is not an object. task type: ' + typeof task) + if (typeof task !== "object") { + throw new Error("task is not an object. task type: " + typeof task) } if (!(task instanceof Task)) { if (task.reqBody) { @@ -994,15 +1040,14 @@ } } class FilterTask extends Task { - constructor(options={}) { - } + constructor(options = {}) {} /** Send current task to server. * @param {*} [timeout=-1] Optional timeout value in ms * @returns the response from the render request. * @memberof Task */ - async post(timeout=-1) { - let jsonResponse = await super.post('/filter', timeout) + async post(timeout = -1) { + let jsonResponse = await super.post("/filter", timeout) //this._setId(jsonResponse.task) this._setStatus(TaskStatus.waiting) } @@ -1010,16 +1055,16 @@ return Task.enqueueNew(this, FilterTask, progressCallback) } *start(progressCallback) { - if (typeof progressCallback !== 'undefined' && typeof progressCallback !== 'function') { - throw new Error('progressCallback is not a function. progressCallback type: ' + typeof progressCallback) + if (typeof progressCallback !== "undefined" && typeof progressCallback !== "function") { + throw new Error("progressCallback is not a function. progressCallback type: " + typeof progressCallback) } if (this.isStopped) { return } } static start(task, progressCallback) { - if (typeof task !== 'object') { - throw new Error ('task is not an object. task type: ' + typeof task) + if (typeof task !== "object") { + throw new Error("task is not an object. task type: " + typeof task) } if (!(task instanceof Task)) { if (task.reqBody) { @@ -1036,26 +1081,30 @@ } } - const getSystemInfo = debounce(async function() { - let systemInfo = { - devices: { - all: {}, - active: {}, - }, - hosts: [] - } - try { - const res = await fetch('/get/system_info') - if (!res.ok) { - console.error('Invalid response fetching devices', res.statusText) - return systemInfo + const getSystemInfo = debounce( + async function() { + let systemInfo = { + devices: { + all: {}, + active: {} + }, + hosts: [] } - systemInfo = await res.json() - } catch (e) { - console.error('error fetching system info', e) - } - return systemInfo - }, 250, true) + try { + const res = await fetch("/get/system_info") + if (!res.ok) { + console.error("Invalid response fetching devices", res.statusText) + return systemInfo + } + systemInfo = await res.json() + } catch (e) { + console.error("error fetching system info", e) + } + return systemInfo + }, + 250, + true + ) async function getDevices() { let systemInfo = getSystemInfo() return systemInfo.devices @@ -1067,26 +1116,26 @@ async function getModels() { let models = { - 'stable-diffusion': [], - 'vae': [], + "stable-diffusion": [], + vae: [] } try { - const res = await fetch('/get/models') + const res = await fetch("/get/models") if (!res.ok) { - console.error('Invalid response fetching models', res.statusText) + console.error("Invalid response fetching models", res.statusText) return models } models = await res.json() - console.log('get models response', models) + console.log("get models response", models) } catch (e) { - console.log('get models error', e) + console.log("get models error", e) } return models } function getServerCapacity() { let activeDevicesCount = Object.keys(serverState?.devices?.active || {}).length - if (typeof window === "object" && window.document.visibilityState === 'hidden') { + if (typeof window === "object" && window.document.visibilityState === "hidden") { activeDevicesCount = 1 + activeDevicesCount } return activeDevicesCount @@ -1094,7 +1143,7 @@ let idleEventPromise = undefined function continueTasks() { - if (typeof navigator?.scheduling?.isInputPending === 'function') { + if (typeof navigator?.scheduling?.isInputPending === "function") { const inputPendingOptions = { // Report mouse/pointer move events when queue is empty. // Delay idle after mouse moves stops. @@ -1108,7 +1157,9 @@ const serverCapacity = getServerCapacity() if (task_queue.size <= 0 && concurrent_generators.size <= 0) { if (!idleEventPromise?.isPending) { - idleEventPromise = makeQuerablePromise(eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity, idle: true})) + idleEventPromise = makeQuerablePromise( + eventSource.fireEvent(EVENT_IDLE, { capacity: serverCapacity, idle: true }) + ) } // Calling idle could result in task being added to queue. // if (task_queue.size <= 0 && concurrent_generators.size <= 0) { @@ -1117,7 +1168,9 @@ } if (task_queue.size < serverCapacity) { if (!idleEventPromise?.isPending) { - idleEventPromise = makeQuerablePromise(eventSource.fireEvent(EVENT_IDLE, {capacity: serverCapacity - task_queue.size})) + idleEventPromise = makeQuerablePromise( + eventSource.fireEvent(EVENT_IDLE, { capacity: serverCapacity - task_queue.size }) + ) } } const completedTasks = [] @@ -1128,25 +1181,25 @@ let value = promise.resolvedValue?.value || promise.resolvedValue if (promise.isRejected) { console.error(promise.rejectReason) - const event = {generator, reason: promise.rejectReason} + const event = { generator, reason: promise.rejectReason } eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, event) - if ('continueWith' in event) { + if ("continueWith" in event) { value = Promise.resolve(event.continueWith) } else { concurrent_generators.delete(generator) - completedTasks.push({generator, promise}) + completedTasks.push({ generator, promise }) continue } } if (value instanceof Promise) { - promise = makeQuerablePromise(value.then((val) => ({done: promise.resolvedValue?.done, value: val}))) + promise = makeQuerablePromise(value.then((val) => ({ done: promise.resolvedValue?.done, value: val }))) concurrent_generators.set(generator, promise) continue } weak_results.set(generator, value) if (promise.resolvedValue?.done) { concurrent_generators.delete(generator) - completedTasks.push({generator, promise}) + completedTasks.push({ generator, promise }) continue } @@ -1161,12 +1214,16 @@ for (let [task, generator] of task_queue.entries()) { const cTsk = completedTasks.find((item) => item.generator === generator) if (cTsk?.promise?.rejectReason || task.hasFailed) { - eventSource.fireEvent(EVENT_TASK_ERROR, {task, generator, reason: cTsk?.promise?.rejectReason || task.exception }) + eventSource.fireEvent(EVENT_TASK_ERROR, { + task, + generator, + reason: cTsk?.promise?.rejectReason || task.exception + }) task_queue.delete(task) continue } if (task.isCompleted || task.isStopped || cTsk) { - const eventEndArgs = {task, generator} + const eventEndArgs = { task, generator } if (task.isStopped) { eventEndArgs.stopped = true } @@ -1178,13 +1235,13 @@ break } if (!generator) { - if (typeof task.start === 'function') { + if (typeof task.start === "function") { generator = task.start() } } else if (concurrent_generators.has(generator)) { continue } - const event = {task, generator}; + const event = { task, generator } const beforeStart = eventSource.fireEvent(EVENT_TASK_START, event) // optional beforeStart promise to wait on before starting task. const promise = makeQuerablePromise(beforeStart.then(() => Promise.resolve(event.beforeStart))) concurrent_generators.set(event.generator, promise) @@ -1206,16 +1263,16 @@ taskPromise = makeQuerablePromise(taskPromise.resolvedValue) continue } - if (typeof navigator?.scheduling?.isInputPending === 'function' && navigator.scheduling.isInputPending()) { + if (typeof navigator?.scheduling?.isInputPending === "function" && navigator.scheduling.isInputPending()) { return } const continuePromise = continueTasks().catch(async function(err) { console.error(err) - await eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, {reason: err}) + await eventSource.fireEvent(EVENT_UNHANDLED_REJECTION, { reason: err }) await asyncDelay(RETRY_DELAY_ON_ERROR) }) taskPromise = makeQuerablePromise(continuePromise) - } while(taskPromise?.isResolved) + } while (taskPromise?.isResolved) } const SD = { @@ -1227,8 +1284,8 @@ FilterTask, Events: EVENTS_TYPES, - init: async function(options={}) { - if ('events' in options) { + init: async function(options = {}) { + if ("events" in options) { for (const key in options.events) { eventSource.addEventListener(key, options.events[key]) } @@ -1256,55 +1313,56 @@ render: (...args) => RenderTask.run(...args), filter: (...args) => FilterTask.run(...args), - waitUntil, - }; + waitUntil + } Object.defineProperties(SD, { serverState: { configurable: false, - get: () => serverState, + get: () => serverState }, isAvailable: { configurable: false, - get: () => isServerAvailable(), + get: () => isServerAvailable() }, serverCapacity: { configurable: false, - get: () => getServerCapacity(), + get: () => getServerCapacity() }, sessionId: { configurable: false, get: () => sessionId, set: (val) => { - if (typeof val === 'undefined') { + if (typeof val === "undefined") { throw new Error("Can't set sessionId to undefined.") } sessionId = val - }, + } }, MAX_SEED_VALUE: { configurable: false, - get: () => MAX_SEED_VALUE, + get: () => MAX_SEED_VALUE }, activeTasks: { configurable: false, - get: () => task_queue, - }, + get: () => task_queue + } }) Object.defineProperties(getGlobal(), { SD: { configurable: false, - get: () => SD, + get: () => SD }, - sessionId: { //TODO Remove in the future in favor of SD.sessionId + sessionId: { + //TODO Remove in the future in favor of SD.sessionId configurable: false, get: () => { - console.warn('Deprecated window.sessionId has been replaced with SD.sessionId.') + console.warn("Deprecated window.sessionId has been replaced with SD.sessionId.") console.trace() return SD.sessionId }, set: (val) => { - console.warn('Deprecated window.sessionId has been replaced with SD.sessionId.') + console.warn("Deprecated window.sessionId has been replaced with SD.sessionId.") console.trace() SD.sessionId = val } diff --git a/ui/media/js/image-editor.js b/ui/media/js/image-editor.js index b095d54d..e9f766c8 100644 --- a/ui/media/js/image-editor.js +++ b/ui/media/js/image-editor.js @@ -3,764 +3,796 @@ var editorControlsLeft = document.getElementById("image-editor-controls-left") const IMAGE_EDITOR_MAX_SIZE = 800 const IMAGE_EDITOR_BUTTONS = [ - { - name: "Cancel", - icon: "fa-regular fa-circle-xmark", - handler: editor => { - editor.hide() - } - }, - { - name: "Save", - icon: "fa-solid fa-floppy-disk", - handler: editor => { - editor.saveImage() - } - } + { + name: "Cancel", + icon: "fa-regular fa-circle-xmark", + handler: (editor) => { + editor.hide() + } + }, + { + name: "Save", + icon: "fa-solid fa-floppy-disk", + handler: (editor) => { + editor.saveImage() + } + } ] const defaultToolBegin = (editor, ctx, x, y, is_overlay = false) => { - ctx.beginPath() - ctx.moveTo(x, y) + ctx.beginPath() + ctx.moveTo(x, y) } const defaultToolMove = (editor, ctx, x, y, is_overlay = false) => { - ctx.lineTo(x, y) - if (is_overlay) { - ctx.clearRect(0, 0, editor.width, editor.height) - ctx.stroke() - } + ctx.lineTo(x, y) + if (is_overlay) { + ctx.clearRect(0, 0, editor.width, editor.height) + ctx.stroke() + } } const defaultToolEnd = (editor, ctx, x, y, is_overlay = false) => { - ctx.stroke() - if (is_overlay) { - ctx.clearRect(0, 0, editor.width, editor.height) - } + ctx.stroke() + if (is_overlay) { + ctx.clearRect(0, 0, editor.width, editor.height) + } } const toolDoNothing = (editor, ctx, x, y, is_overlay = false) => {} const IMAGE_EDITOR_TOOLS = [ - { - id: "draw", - name: "Draw", - icon: "fa-solid fa-pencil", - cursor: "url(/media/images/fa-pencil.svg) 0 24, pointer", - begin: defaultToolBegin, - move: defaultToolMove, - end: defaultToolEnd - }, - { - id: "erase", - name: "Erase", - icon: "fa-solid fa-eraser", - cursor: "url(/media/images/fa-eraser.svg) 0 14, pointer", - begin: defaultToolBegin, - move: (editor, ctx, x, y, is_overlay = false) => { - ctx.lineTo(x, y) - if (is_overlay) { - ctx.clearRect(0, 0, editor.width, editor.height) - ctx.globalCompositeOperation = "source-over" - ctx.globalAlpha = 1 - ctx.filter = "none" - ctx.drawImage(editor.canvas_current, 0, 0) - editor.setBrush(editor.layers.overlay) - ctx.stroke() - editor.canvas_current.style.opacity = 0 - } - }, - end: (editor, ctx, x, y, is_overlay = false) => { - ctx.stroke() - if (is_overlay) { - ctx.clearRect(0, 0, editor.width, editor.height) - editor.canvas_current.style.opacity = "" - } - }, - setBrush: (editor, layer) => { - layer.ctx.globalCompositeOperation = "destination-out" - } - }, - { - id: "fill", - name: "Fill", - icon: "fa-solid fa-fill", - cursor: "url(/media/images/fa-fill.svg) 20 6, pointer", - begin: (editor, ctx, x, y, is_overlay = false) => { - if (!is_overlay) { - var color = hexToRgb(ctx.fillStyle) - color.a = parseInt(ctx.globalAlpha * 255) // layer.ctx.globalAlpha - flood_fill(editor, ctx, parseInt(x), parseInt(y), color) - } - }, - move: toolDoNothing, - end: toolDoNothing - }, - { - id: "colorpicker", - name: "Picker", - icon: "fa-solid fa-eye-dropper", - cursor: "url(/media/images/fa-eye-dropper.svg) 0 24, pointer", - begin: (editor, ctx, x, y, is_overlay = false) => { - if (!is_overlay) { - var img_rgb = editor.layers.background.ctx.getImageData(x, y, 1, 1).data - var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data - var drawn_opacity = drawn_rgb[3] / 255 - editor.custom_color_input.value = rgbToHex({ - r: (drawn_rgb[0] * drawn_opacity) + (img_rgb[0] * (1 - drawn_opacity)), - g: (drawn_rgb[1] * drawn_opacity) + (img_rgb[1] * (1 - drawn_opacity)), - b: (drawn_rgb[2] * drawn_opacity) + (img_rgb[2] * (1 - drawn_opacity)), - }) - editor.custom_color_input.dispatchEvent(new Event("change")) - } - }, - move: toolDoNothing, - end: toolDoNothing - } + { + id: "draw", + name: "Draw", + icon: "fa-solid fa-pencil", + cursor: "url(/media/images/fa-pencil.svg) 0 24, pointer", + begin: defaultToolBegin, + move: defaultToolMove, + end: defaultToolEnd + }, + { + id: "erase", + name: "Erase", + icon: "fa-solid fa-eraser", + cursor: "url(/media/images/fa-eraser.svg) 0 14, pointer", + begin: defaultToolBegin, + move: (editor, ctx, x, y, is_overlay = false) => { + ctx.lineTo(x, y) + if (is_overlay) { + ctx.clearRect(0, 0, editor.width, editor.height) + ctx.globalCompositeOperation = "source-over" + ctx.globalAlpha = 1 + ctx.filter = "none" + ctx.drawImage(editor.canvas_current, 0, 0) + editor.setBrush(editor.layers.overlay) + ctx.stroke() + editor.canvas_current.style.opacity = 0 + } + }, + end: (editor, ctx, x, y, is_overlay = false) => { + ctx.stroke() + if (is_overlay) { + ctx.clearRect(0, 0, editor.width, editor.height) + editor.canvas_current.style.opacity = "" + } + }, + setBrush: (editor, layer) => { + layer.ctx.globalCompositeOperation = "destination-out" + } + }, + { + id: "fill", + name: "Fill", + icon: "fa-solid fa-fill", + cursor: "url(/media/images/fa-fill.svg) 20 6, pointer", + begin: (editor, ctx, x, y, is_overlay = false) => { + if (!is_overlay) { + var color = hexToRgb(ctx.fillStyle) + color.a = parseInt(ctx.globalAlpha * 255) // layer.ctx.globalAlpha + flood_fill(editor, ctx, parseInt(x), parseInt(y), color) + } + }, + move: toolDoNothing, + end: toolDoNothing + }, + { + id: "colorpicker", + name: "Picker", + icon: "fa-solid fa-eye-dropper", + cursor: "url(/media/images/fa-eye-dropper.svg) 0 24, pointer", + begin: (editor, ctx, x, y, is_overlay = false) => { + if (!is_overlay) { + var img_rgb = editor.layers.background.ctx.getImageData(x, y, 1, 1).data + var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data + var drawn_opacity = drawn_rgb[3] / 255 + editor.custom_color_input.value = rgbToHex({ + r: drawn_rgb[0] * drawn_opacity + img_rgb[0] * (1 - drawn_opacity), + g: drawn_rgb[1] * drawn_opacity + img_rgb[1] * (1 - drawn_opacity), + b: drawn_rgb[2] * drawn_opacity + img_rgb[2] * (1 - drawn_opacity) + }) + editor.custom_color_input.dispatchEvent(new Event("change")) + } + }, + move: toolDoNothing, + end: toolDoNothing + } ] const IMAGE_EDITOR_ACTIONS = [ - { - id: "load_mask", - name: "Load mask from file", - className: "load_mask", - icon: "fa-regular fa-folder-open", - handler: (editor) => { - let el = document.createElement('input') - el.setAttribute("type", "file") - el.addEventListener("change", function() { - if (this.files.length === 0) { - return - } + { + id: "load_mask", + name: "Load mask from file", + className: "load_mask", + icon: "fa-regular fa-folder-open", + handler: (editor) => { + let el = document.createElement("input") + el.setAttribute("type", "file") + el.addEventListener("change", function() { + if (this.files.length === 0) { + return + } - let reader = new FileReader() - let file = this.files[0] + let reader = new FileReader() + let file = this.files[0] - reader.addEventListener('load', function(event) { - let maskData = reader.result + reader.addEventListener("load", function(event) { + let maskData = reader.result - editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height) - var image = new Image() - image.onload = () => { - editor.layers.drawing.ctx.drawImage(image, 0, 0, editor.width, editor.height) - } - image.src = maskData - }) + editor.layers.drawing.ctx.clearRect(0, 0, editor.width, editor.height) + var image = new Image() + image.onload = () => { + editor.layers.drawing.ctx.drawImage(image, 0, 0, editor.width, editor.height) + } + image.src = maskData + }) - if (file) { - reader.readAsDataURL(file) - } - }) + if (file) { + reader.readAsDataURL(file) + } + }) - el.click() - }, - trackHistory: true - }, - { - id: "fill_all", - name: "Fill all", - icon: "fa-solid fa-paint-roller", - handler: (editor) => { - editor.ctx_current.globalCompositeOperation = "source-over" - editor.ctx_current.rect(0, 0, editor.width, editor.height) - editor.ctx_current.fill() - editor.setBrush() - }, - trackHistory: true - }, - { - id: "clear", - name: "Clear", - icon: "fa-solid fa-xmark", - handler: (editor) => { - editor.ctx_current.clearRect(0, 0, editor.width, editor.height) - imageEditor.setImage(null, editor.width, editor.height) // properly reset the drawing canvas - }, - trackHistory: true - }, - { - id: "undo", - name: "Undo", - icon: "fa-solid fa-rotate-left", - handler: (editor) => { - editor.history.undo() - }, - trackHistory: false - }, - { - id: "redo", - name: "Redo", - icon: "fa-solid fa-rotate-right", - handler: (editor) => { - editor.history.redo() - }, - trackHistory: false - } + el.click() + }, + trackHistory: true + }, + { + id: "fill_all", + name: "Fill all", + icon: "fa-solid fa-paint-roller", + handler: (editor) => { + editor.ctx_current.globalCompositeOperation = "source-over" + editor.ctx_current.rect(0, 0, editor.width, editor.height) + editor.ctx_current.fill() + editor.setBrush() + }, + trackHistory: true + }, + { + id: "clear", + name: "Clear", + icon: "fa-solid fa-xmark", + handler: (editor) => { + editor.ctx_current.clearRect(0, 0, editor.width, editor.height) + imageEditor.setImage(null, editor.width, editor.height) // properly reset the drawing canvas + }, + trackHistory: true + }, + { + id: "undo", + name: "Undo", + icon: "fa-solid fa-rotate-left", + handler: (editor) => { + editor.history.undo() + }, + trackHistory: false + }, + { + id: "redo", + name: "Redo", + icon: "fa-solid fa-rotate-right", + handler: (editor) => { + editor.history.redo() + }, + trackHistory: false + } ] var IMAGE_EDITOR_SECTIONS = [ - { - name: "tool", - title: "Tool", - default: "draw", - options: Array.from(IMAGE_EDITOR_TOOLS.map(t => t.id)), - initElement: (element, option) => { - var tool_info = IMAGE_EDITOR_TOOLS.find(t => t.id == option) - element.className = "image-editor-button button" - var sub_element = document.createElement("div") - var icon = document.createElement("i") - tool_info.icon.split(" ").forEach(c => icon.classList.add(c)) - sub_element.appendChild(icon) - sub_element.append(tool_info.name) - element.appendChild(sub_element) - } - }, - { - name: "color", - title: "Color", - default: "#f1c232", - options: [ - "custom", - "#ea9999", "#e06666", "#cc0000", "#990000", "#660000", - "#f9cb9c", "#f6b26b", "#e69138", "#b45f06", "#783f04", - "#ffe599", "#ffd966", "#f1c232", "#bf9000", "#7f6000", - "#b6d7a8", "#93c47d", "#6aa84f", "#38761d", "#274e13", - "#a4c2f4", "#6d9eeb", "#3c78d8", "#1155cc", "#1c4587", - "#b4a7d6", "#8e7cc3", "#674ea7", "#351c75", "#20124d", - "#d5a6bd", "#c27ba0", "#a64d79", "#741b47", "#4c1130", - "#ffffff", "#c0c0c0", "#838383", "#525252", "#000000", - ], - initElement: (element, option) => { - if (option == "custom") { - var input = document.createElement("input") - input.type = "color" - element.appendChild(input) - var span = document.createElement("span") - span.textContent = "Custom" - span.onclick = function(e) { - input.click() - } - element.appendChild(span) - } - else { - element.style.background = option - } - }, - getCustom: editor => { - var input = editor.popup.querySelector(".image_editor_color input") - return input.value - } - }, - { - name: "brush_size", - title: "Brush Size", - default: 48, - options: [ 6, 12, 16, 24, 30, 40, 48, 64 ], - initElement: (element, option) => { - element.parentElement.style.flex = option - element.style.width = option + "px" - element.style.height = option + "px" - element.style['margin-right'] = '2px' - element.style["border-radius"] = (option / 2).toFixed() + "px" - } - }, - { - name: "opacity", - title: "Opacity", - default: 0, - options: [ 0, 0.2, 0.4, 0.6, 0.8 ], - initElement: (element, option) => { - element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px` - } - }, - { - name: "sharpness", - title: "Sharpness", - default: 0, - options: [ 0, 0.05, 0.1, 0.2, 0.3 ], - initElement: (element, option) => { - var size = 32 - var blur_amount = parseInt(option * size) - var sub_element = document.createElement("div") - sub_element.style.background = `var(--background-color3)` - sub_element.style.filter = `blur(${blur_amount}px)` - sub_element.style.width = `${size - 2}px` - sub_element.style.height = `${size - 2}px` - sub_element.style['border-radius'] = `${size}px` - element.style.background = "none" - element.appendChild(sub_element) - } - } + { + name: "tool", + title: "Tool", + default: "draw", + options: Array.from(IMAGE_EDITOR_TOOLS.map((t) => t.id)), + initElement: (element, option) => { + var tool_info = IMAGE_EDITOR_TOOLS.find((t) => t.id == option) + element.className = "image-editor-button button" + var sub_element = document.createElement("div") + var icon = document.createElement("i") + tool_info.icon.split(" ").forEach((c) => icon.classList.add(c)) + sub_element.appendChild(icon) + sub_element.append(tool_info.name) + element.appendChild(sub_element) + } + }, + { + name: "color", + title: "Color", + default: "#f1c232", + options: [ + "custom", + "#ea9999", + "#e06666", + "#cc0000", + "#990000", + "#660000", + "#f9cb9c", + "#f6b26b", + "#e69138", + "#b45f06", + "#783f04", + "#ffe599", + "#ffd966", + "#f1c232", + "#bf9000", + "#7f6000", + "#b6d7a8", + "#93c47d", + "#6aa84f", + "#38761d", + "#274e13", + "#a4c2f4", + "#6d9eeb", + "#3c78d8", + "#1155cc", + "#1c4587", + "#b4a7d6", + "#8e7cc3", + "#674ea7", + "#351c75", + "#20124d", + "#d5a6bd", + "#c27ba0", + "#a64d79", + "#741b47", + "#4c1130", + "#ffffff", + "#c0c0c0", + "#838383", + "#525252", + "#000000" + ], + initElement: (element, option) => { + if (option == "custom") { + var input = document.createElement("input") + input.type = "color" + element.appendChild(input) + var span = document.createElement("span") + span.textContent = "Custom" + span.onclick = function(e) { + input.click() + } + element.appendChild(span) + } else { + element.style.background = option + } + }, + getCustom: (editor) => { + var input = editor.popup.querySelector(".image_editor_color input") + return input.value + } + }, + { + name: "brush_size", + title: "Brush Size", + default: 48, + options: [6, 12, 16, 24, 30, 40, 48, 64], + initElement: (element, option) => { + element.parentElement.style.flex = option + element.style.width = option + "px" + element.style.height = option + "px" + element.style["margin-right"] = "2px" + element.style["border-radius"] = (option / 2).toFixed() + "px" + } + }, + { + name: "opacity", + title: "Opacity", + default: 0, + options: [0, 0.2, 0.4, 0.6, 0.8], + initElement: (element, option) => { + element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px` + } + }, + { + name: "sharpness", + title: "Sharpness", + default: 0, + options: [0, 0.05, 0.1, 0.2, 0.3], + initElement: (element, option) => { + var size = 32 + var blur_amount = parseInt(option * size) + var sub_element = document.createElement("div") + sub_element.style.background = `var(--background-color3)` + sub_element.style.filter = `blur(${blur_amount}px)` + sub_element.style.width = `${size - 2}px` + sub_element.style.height = `${size - 2}px` + sub_element.style["border-radius"] = `${size}px` + element.style.background = "none" + element.appendChild(sub_element) + } + } ] class EditorHistory { - constructor(editor) { - this.editor = editor - this.events = [] // stack of all events (actions/edits) - this.current_edit = null - this.rewind_index = 0 // how many events back into the history we've rewound to. (current state is just after event at index 'length - this.rewind_index - 1') - } - push(event) { - // probably add something here eventually to save state every x events - if (this.rewind_index != 0) { - this.events = this.events.slice(0, 0 - this.rewind_index) - this.rewind_index = 0 - } - var snapshot_frequency = 20 // (every x edits, take a snapshot of the current drawing state, for faster rewinding) - if (this.events.length > 0 && this.events.length % snapshot_frequency == 0) { - event.snapshot = this.editor.layers.drawing.ctx.getImageData(0, 0, this.editor.width, this.editor.height) - } - this.events.push(event) - } - pushAction(action) { - this.push({ - type: "action", - id: action - }); - } - editBegin(x, y) { - this.current_edit = { - type: "edit", - id: this.editor.getOptionValue("tool"), - options: Object.assign({}, this.editor.options), - points: [ { x: x, y: y } ] - } - } - editMove(x, y) { - if (this.current_edit) { - this.current_edit.points.push({ x: x, y: y }) - } - } - editEnd(x, y) { - if (this.current_edit) { - this.push(this.current_edit) - this.current_edit = null - } - } - clear() { - this.events = [] - } - undo() { - this.rewindTo(this.rewind_index + 1) - } - redo() { - this.rewindTo(this.rewind_index - 1) - } - rewindTo(new_rewind_index) { - if (new_rewind_index < 0 || new_rewind_index > this.events.length) { - return; // do nothing if target index is out of bounds - } + constructor(editor) { + this.editor = editor + this.events = [] // stack of all events (actions/edits) + this.current_edit = null + this.rewind_index = 0 // how many events back into the history we've rewound to. (current state is just after event at index 'length - this.rewind_index - 1') + } + push(event) { + // probably add something here eventually to save state every x events + if (this.rewind_index != 0) { + this.events = this.events.slice(0, 0 - this.rewind_index) + this.rewind_index = 0 + } + var snapshot_frequency = 20 // (every x edits, take a snapshot of the current drawing state, for faster rewinding) + if (this.events.length > 0 && this.events.length % snapshot_frequency == 0) { + event.snapshot = this.editor.layers.drawing.ctx.getImageData(0, 0, this.editor.width, this.editor.height) + } + this.events.push(event) + } + pushAction(action) { + this.push({ + type: "action", + id: action + }) + } + editBegin(x, y) { + this.current_edit = { + type: "edit", + id: this.editor.getOptionValue("tool"), + options: Object.assign({}, this.editor.options), + points: [{ x: x, y: y }] + } + } + editMove(x, y) { + if (this.current_edit) { + this.current_edit.points.push({ x: x, y: y }) + } + } + editEnd(x, y) { + if (this.current_edit) { + this.push(this.current_edit) + this.current_edit = null + } + } + clear() { + this.events = [] + } + undo() { + this.rewindTo(this.rewind_index + 1) + } + redo() { + this.rewindTo(this.rewind_index - 1) + } + rewindTo(new_rewind_index) { + if (new_rewind_index < 0 || new_rewind_index > this.events.length) { + return // do nothing if target index is out of bounds + } - var ctx = this.editor.layers.drawing.ctx - ctx.clearRect(0, 0, this.editor.width, this.editor.height) + var ctx = this.editor.layers.drawing.ctx + ctx.clearRect(0, 0, this.editor.width, this.editor.height) - var target_index = this.events.length - 1 - new_rewind_index - var snapshot_index = target_index - while (snapshot_index > -1) { - if (this.events[snapshot_index].snapshot) { - break - } - snapshot_index-- - } + var target_index = this.events.length - 1 - new_rewind_index + var snapshot_index = target_index + while (snapshot_index > -1) { + if (this.events[snapshot_index].snapshot) { + break + } + snapshot_index-- + } - if (snapshot_index != -1) { - ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0); - } + if (snapshot_index != -1) { + ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0) + } - for (var i = (snapshot_index + 1); i <= target_index; i++) { - var event = this.events[i] - if (event.type == "action") { - var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == event.id) - action.handler(this.editor) - } - else if (event.type == "edit") { - var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == event.id) - this.editor.setBrush(this.editor.layers.drawing, event.options) + for (var i = snapshot_index + 1; i <= target_index; i++) { + var event = this.events[i] + if (event.type == "action") { + var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == event.id) + action.handler(this.editor) + } else if (event.type == "edit") { + var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == event.id) + this.editor.setBrush(this.editor.layers.drawing, event.options) - var first_point = event.points[0] - tool.begin(this.editor, ctx, first_point.x, first_point.y) - for (var point_i = 1; point_i < event.points.length; point_i++) { - tool.move(this.editor, ctx, event.points[point_i].x, event.points[point_i].y) - } - var last_point = event.points[event.points.length - 1] - tool.end(this.editor, ctx, last_point.x, last_point.y) - } - } + var first_point = event.points[0] + tool.begin(this.editor, ctx, first_point.x, first_point.y) + for (var point_i = 1; point_i < event.points.length; point_i++) { + tool.move(this.editor, ctx, event.points[point_i].x, event.points[point_i].y) + } + var last_point = event.points[event.points.length - 1] + tool.end(this.editor, ctx, last_point.x, last_point.y) + } + } - // re-set brush to current settings - this.editor.setBrush(this.editor.layers.drawing) + // re-set brush to current settings + this.editor.setBrush(this.editor.layers.drawing) - this.rewind_index = new_rewind_index - } + this.rewind_index = new_rewind_index + } } class ImageEditor { - constructor(popup, inpainter = false) { - this.inpainter = inpainter - this.popup = popup - this.history = new EditorHistory(this) - if (inpainter) { - this.popup.classList.add("inpainter") - } - this.drawing = false - this.temp_previous_tool = null // used for the ctrl-colorpicker functionality - this.container = popup.querySelector(".editor-controls-center > div") - this.layers = {} - var layer_names = [ - "background", - "drawing", - "overlay" - ] - layer_names.forEach(name => { - let canvas = document.createElement("canvas") - canvas.className = `editor-canvas-${name}` - this.container.appendChild(canvas) - this.layers[name] = { - name: name, - canvas: canvas, - ctx: canvas.getContext("2d") - } - }) + constructor(popup, inpainter = false) { + this.inpainter = inpainter + this.popup = popup + this.history = new EditorHistory(this) + if (inpainter) { + this.popup.classList.add("inpainter") + } + this.drawing = false + this.temp_previous_tool = null // used for the ctrl-colorpicker functionality + this.container = popup.querySelector(".editor-controls-center > div") + this.layers = {} + var layer_names = ["background", "drawing", "overlay"] + layer_names.forEach((name) => { + let canvas = document.createElement("canvas") + canvas.className = `editor-canvas-${name}` + this.container.appendChild(canvas) + this.layers[name] = { + name: name, + canvas: canvas, + ctx: canvas.getContext("2d") + } + }) - // add mouse handlers - this.container.addEventListener("mousedown", this.mouseHandler.bind(this)) - this.container.addEventListener("mouseup", this.mouseHandler.bind(this)) - this.container.addEventListener("mousemove", this.mouseHandler.bind(this)) - this.container.addEventListener("mouseout", this.mouseHandler.bind(this)) - this.container.addEventListener("mouseenter", this.mouseHandler.bind(this)) + // add mouse handlers + this.container.addEventListener("mousedown", this.mouseHandler.bind(this)) + this.container.addEventListener("mouseup", this.mouseHandler.bind(this)) + this.container.addEventListener("mousemove", this.mouseHandler.bind(this)) + this.container.addEventListener("mouseout", this.mouseHandler.bind(this)) + this.container.addEventListener("mouseenter", this.mouseHandler.bind(this)) - this.container.addEventListener("touchstart", this.mouseHandler.bind(this)) - this.container.addEventListener("touchmove", this.mouseHandler.bind(this)) - this.container.addEventListener("touchcancel", this.mouseHandler.bind(this)) - this.container.addEventListener("touchend", this.mouseHandler.bind(this)) + this.container.addEventListener("touchstart", this.mouseHandler.bind(this)) + this.container.addEventListener("touchmove", this.mouseHandler.bind(this)) + this.container.addEventListener("touchcancel", this.mouseHandler.bind(this)) + this.container.addEventListener("touchend", this.mouseHandler.bind(this)) - // initialize editor controls - this.options = {} - this.optionElements = {} - IMAGE_EDITOR_SECTIONS.forEach(section => { - section.id = `image_editor_${section.name}` - var sectionElement = document.createElement("div") - sectionElement.className = section.id - - var title = document.createElement("h4") - title.innerText = section.title - sectionElement.appendChild(title) - - var optionsContainer = document.createElement("div") - optionsContainer.classList.add("editor-options-container") - - this.optionElements[section.name] = [] - section.options.forEach((option, index) => { - var optionHolder = document.createElement("div") - var optionElement = document.createElement("div") - optionHolder.appendChild(optionElement) - section.initElement(optionElement, option) - optionElement.addEventListener("click", target => this.selectOption(section.name, index)) - optionsContainer.appendChild(optionHolder) - this.optionElements[section.name].push(optionElement) - }) - this.selectOption(section.name, section.options.indexOf(section.default)) - - sectionElement.appendChild(optionsContainer) - - this.popup.querySelector(".editor-controls-left").appendChild(sectionElement) - }) + // initialize editor controls + this.options = {} + this.optionElements = {} + IMAGE_EDITOR_SECTIONS.forEach((section) => { + section.id = `image_editor_${section.name}` + var sectionElement = document.createElement("div") + sectionElement.className = section.id - this.custom_color_input = this.popup.querySelector(`input[type="color"]`) - this.custom_color_input.addEventListener("change", () => { - this.custom_color_input.parentElement.style.background = this.custom_color_input.value - this.selectOption("color", 0) - }) + var title = document.createElement("h4") + title.innerText = section.title + sectionElement.appendChild(title) - if (this.inpainter) { - this.selectOption("color", IMAGE_EDITOR_SECTIONS.find(s => s.name == "color").options.indexOf("#ffffff")) - this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find(s => s.name == "opacity").options.indexOf(0.4)) - } + var optionsContainer = document.createElement("div") + optionsContainer.classList.add("editor-options-container") - // initialize the right-side controls - var buttonContainer = document.createElement("div") - IMAGE_EDITOR_BUTTONS.forEach(button => { - var element = document.createElement("div") - var icon = document.createElement("i") - element.className = "image-editor-button button" - icon.className = button.icon - element.appendChild(icon) - element.append(button.name) - buttonContainer.appendChild(element) - element.addEventListener("click", event => button.handler(this)) - }) - var actionsContainer = document.createElement("div") - var actionsTitle = document.createElement("h4") - actionsTitle.textContent = "Actions" - actionsContainer.appendChild(actionsTitle); - IMAGE_EDITOR_ACTIONS.forEach(action => { - var element = document.createElement("div") - var icon = document.createElement("i") - element.className = "image-editor-button button" - if (action.className) { - element.className += " " + action.className - } - icon.className = action.icon - element.appendChild(icon) - element.append(action.name) - actionsContainer.appendChild(element) - element.addEventListener("click", event => this.runAction(action.id)) - }) - this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer) - this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer) + this.optionElements[section.name] = [] + section.options.forEach((option, index) => { + var optionHolder = document.createElement("div") + var optionElement = document.createElement("div") + optionHolder.appendChild(optionElement) + section.initElement(optionElement, option) + optionElement.addEventListener("click", (target) => this.selectOption(section.name, index)) + optionsContainer.appendChild(optionHolder) + this.optionElements[section.name].push(optionElement) + }) + this.selectOption(section.name, section.options.indexOf(section.default)) - this.keyHandlerBound = this.keyHandler.bind(this) + sectionElement.appendChild(optionsContainer) - this.setSize(512, 512) - } - show() { - this.popup.classList.add("active") - document.addEventListener("keydown", this.keyHandlerBound, true) - document.addEventListener("keyup", this.keyHandlerBound, true) - } - hide() { - this.popup.classList.remove("active") - document.removeEventListener("keydown", this.keyHandlerBound, true) - document.removeEventListener("keyup", this.keyHandlerBound, true) - } - setSize(width, height) { - if (width == this.width && height == this.height) { - return - } + this.popup.querySelector(".editor-controls-left").appendChild(sectionElement) + }) - if (width > height) { - var max_size = Math.min(parseInt(window.innerWidth * 0.9), width, 768) - var multiplier = max_size / width - width = (multiplier * width).toFixed() - height = (multiplier * height).toFixed() - } - else { - var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768) - var multiplier = max_size / height - width = (multiplier * width).toFixed() - height = (multiplier * height).toFixed() - } - this.width = parseInt(width) - this.height = parseInt(height) - - this.container.style.width = width + "px" - this.container.style.height = height + "px" - - Object.values(this.layers).forEach(layer => { - layer.canvas.width = width - layer.canvas.height = height - }) + this.custom_color_input = this.popup.querySelector(`input[type="color"]`) + this.custom_color_input.addEventListener("change", () => { + this.custom_color_input.parentElement.style.background = this.custom_color_input.value + this.selectOption("color", 0) + }) - if (this.inpainter) { - this.saveImage() // We've reset the size of the image so inpainting is different - } - this.setBrush() - this.history.clear() - } - get tool() { - var tool_id = this.getOptionValue("tool") - return IMAGE_EDITOR_TOOLS.find(t => t.id == tool_id); - } - loadTool() { - this.drawing = false - this.container.style.cursor = this.tool.cursor; - } - setImage(url, width, height) { - this.setSize(width, height) - this.layers.background.ctx.clearRect(0, 0, this.width, this.height) - if (!(url && this.inpainter)) { - this.layers.drawing.ctx.clearRect(0, 0, this.width, this.height) - } - if (url) { - var image = new Image() - image.onload = () => { - this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height) - } - image.src = url - } - else { - this.layers.background.ctx.fillStyle = "#ffffff" - this.layers.background.ctx.beginPath() - this.layers.background.ctx.rect(0, 0, this.width, this.height) - this.layers.background.ctx.fill() - } - this.history.clear() - } - saveImage() { - if (!this.inpainter) { - // This is not an inpainter, so save the image as the new img2img input - this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height) - var base64 = this.layers.background.canvas.toDataURL() - initImagePreview.src = base64 // this will trigger the rest of the app to use it - } - else { - // This is an inpainter, so make sure the toggle is set accordingly - var is_blank = !this.layers.drawing.ctx - .getImageData(0, 0, this.width, this.height).data - .some(channel => channel !== 0) - maskSetting.checked = !is_blank - } - this.hide() - } - getImg() { // a drop-in replacement of the drawingboard version - return this.layers.drawing.canvas.toDataURL() - } - setImg(dataUrl) { // a drop-in replacement of the drawingboard version - var image = new Image() - image.onload = () => { - var ctx = this.layers.drawing.ctx; - ctx.clearRect(0, 0, this.width, this.height) - ctx.globalCompositeOperation = "source-over" - ctx.globalAlpha = 1 - ctx.filter = "none" - ctx.drawImage(image, 0, 0, this.width, this.height) - this.setBrush(this.layers.drawing) - } - image.src = dataUrl - } - runAction(action_id) { - var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == action_id) - if (action.trackHistory) { - this.history.pushAction(action_id) - } - action.handler(this) - } - setBrush(layer = null, options = null) { - if (options == null) { - options = this.options - } - if (layer) { - layer.ctx.lineCap = "round" - layer.ctx.lineJoin = "round" - layer.ctx.lineWidth = options.brush_size - layer.ctx.fillStyle = options.color - layer.ctx.strokeStyle = options.color - var sharpness = parseInt(options.sharpness * options.brush_size) - layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)` - layer.ctx.globalAlpha = (1 - options.opacity) - layer.ctx.globalCompositeOperation = "source-over" - var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == options.tool) - if (tool && tool.setBrush) { - tool.setBrush(editor, layer) - } - } - else { - Object.values([ "drawing", "overlay" ]).map(name => this.layers[name]).forEach(l => { - this.setBrush(l) - }) - } - } - get ctx_overlay() { - return this.layers.overlay.ctx - } - get ctx_current() { // the idea is this will help support having custom layers and editing each one - return this.layers.drawing.ctx - } - get canvas_current() { - return this.layers.drawing.canvas - } - keyHandler(event) { // handles keybinds like ctrl+z, ctrl+y - if (!this.popup.classList.contains("active")) { - document.removeEventListener("keydown", this.keyHandlerBound) - document.removeEventListener("keyup", this.keyHandlerBound) - return // this catches if something else closes the window but doesnt properly unbind the key handler - } + if (this.inpainter) { + this.selectOption("color", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "color").options.indexOf("#ffffff")) + this.selectOption("opacity", IMAGE_EDITOR_SECTIONS.find((s) => s.name == "opacity").options.indexOf(0.4)) + } - // keybindings - if (event.type == "keydown") { - if ((event.key == "z" || event.key == "Z") && event.ctrlKey) { - if (!event.shiftKey) { - this.history.undo() - } - else { - this.history.redo() - } - event.stopPropagation(); - event.preventDefault(); - } - if (event.key == "y" && event.ctrlKey) { - this.history.redo() - event.stopPropagation(); - event.preventDefault(); - } - if (event.key === "Escape") { - this.hide() - event.stopPropagation(); - event.preventDefault(); - } - } - - // dropper ctrl holding handler stuff - var dropper_active = this.temp_previous_tool != null; - if (dropper_active && !event.ctrlKey) { - this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == this.temp_previous_tool)) - this.temp_previous_tool = null - } - else if (!dropper_active && event.ctrlKey) { - this.temp_previous_tool = this.getOptionValue("tool") - this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == "colorpicker")) - } - } - mouseHandler(event) { - var bbox = this.layers.overlay.canvas.getBoundingClientRect() - var x = (event.clientX || 0) - bbox.left - var y = (event.clientY || 0) - bbox.top - var type = event.type; - var touchmap = { - touchstart: "mousedown", - touchmove: "mousemove", - touchend: "mouseup", - touchcancel: "mouseup" - } - if (type in touchmap) { - type = touchmap[type] - if (event.touches && event.touches[0]) { - var touch = event.touches[0] - var x = (touch.clientX || 0) - bbox.left - var y = (touch.clientY || 0) - bbox.top - } - } - event.preventDefault() - // do drawing-related stuff - if (type == "mousedown" || (type == "mouseenter" && event.buttons == 1)) { - this.drawing = true - this.tool.begin(this, this.ctx_current, x, y) - this.tool.begin(this, this.ctx_overlay, x, y, true) - this.history.editBegin(x, y) - } - if (type == "mouseup" || type == "mousemove") { - if (this.drawing) { - if (x > 0 && y > 0) { - this.tool.move(this, this.ctx_current, x, y) - this.tool.move(this, this.ctx_overlay, x, y, true) - this.history.editMove(x, y) - } - } - } - if (type == "mouseup" || type == "mouseout") { - if (this.drawing) { - this.drawing = false - this.tool.end(this, this.ctx_current, x, y) - this.tool.end(this, this.ctx_overlay, x, y, true) - this.history.editEnd(x, y) - } - } - } - getOptionValue(section_name) { - var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name) - return this.options && section_name in this.options ? this.options[section_name] : section.default - } - selectOption(section_name, option_index) { - var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name) - var value = section.options[option_index] - this.options[section_name] = value == "custom" ? section.getCustom(this) : value - - this.optionElements[section_name].forEach(element => element.classList.remove("active")) - this.optionElements[section_name][option_index].classList.add("active") - - // change the editor - this.setBrush() - if (section.name == "tool") { - this.loadTool() - } - } + // initialize the right-side controls + var buttonContainer = document.createElement("div") + IMAGE_EDITOR_BUTTONS.forEach((button) => { + var element = document.createElement("div") + var icon = document.createElement("i") + element.className = "image-editor-button button" + icon.className = button.icon + element.appendChild(icon) + element.append(button.name) + buttonContainer.appendChild(element) + element.addEventListener("click", (event) => button.handler(this)) + }) + var actionsContainer = document.createElement("div") + var actionsTitle = document.createElement("h4") + actionsTitle.textContent = "Actions" + actionsContainer.appendChild(actionsTitle) + IMAGE_EDITOR_ACTIONS.forEach((action) => { + var element = document.createElement("div") + var icon = document.createElement("i") + element.className = "image-editor-button button" + if (action.className) { + element.className += " " + action.className + } + icon.className = action.icon + element.appendChild(icon) + element.append(action.name) + actionsContainer.appendChild(element) + element.addEventListener("click", (event) => this.runAction(action.id)) + }) + this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer) + this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer) + + this.keyHandlerBound = this.keyHandler.bind(this) + + this.setSize(512, 512) + } + show() { + this.popup.classList.add("active") + document.addEventListener("keydown", this.keyHandlerBound, true) + document.addEventListener("keyup", this.keyHandlerBound, true) + } + hide() { + this.popup.classList.remove("active") + document.removeEventListener("keydown", this.keyHandlerBound, true) + document.removeEventListener("keyup", this.keyHandlerBound, true) + } + setSize(width, height) { + if (width == this.width && height == this.height) { + return + } + + if (width > height) { + var max_size = Math.min(parseInt(window.innerWidth * 0.9), width, 768) + var multiplier = max_size / width + width = (multiplier * width).toFixed() + height = (multiplier * height).toFixed() + } else { + var max_size = Math.min(parseInt(window.innerHeight * 0.9), height, 768) + var multiplier = max_size / height + width = (multiplier * width).toFixed() + height = (multiplier * height).toFixed() + } + this.width = parseInt(width) + this.height = parseInt(height) + + this.container.style.width = width + "px" + this.container.style.height = height + "px" + + Object.values(this.layers).forEach((layer) => { + layer.canvas.width = width + layer.canvas.height = height + }) + + if (this.inpainter) { + this.saveImage() // We've reset the size of the image so inpainting is different + } + this.setBrush() + this.history.clear() + } + get tool() { + var tool_id = this.getOptionValue("tool") + return IMAGE_EDITOR_TOOLS.find((t) => t.id == tool_id) + } + loadTool() { + this.drawing = false + this.container.style.cursor = this.tool.cursor + } + setImage(url, width, height) { + this.setSize(width, height) + this.layers.background.ctx.clearRect(0, 0, this.width, this.height) + if (!(url && this.inpainter)) { + this.layers.drawing.ctx.clearRect(0, 0, this.width, this.height) + } + if (url) { + var image = new Image() + image.onload = () => { + this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height) + } + image.src = url + } else { + this.layers.background.ctx.fillStyle = "#ffffff" + this.layers.background.ctx.beginPath() + this.layers.background.ctx.rect(0, 0, this.width, this.height) + this.layers.background.ctx.fill() + } + this.history.clear() + } + saveImage() { + if (!this.inpainter) { + // This is not an inpainter, so save the image as the new img2img input + this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height) + var base64 = this.layers.background.canvas.toDataURL() + initImagePreview.src = base64 // this will trigger the rest of the app to use it + } else { + // This is an inpainter, so make sure the toggle is set accordingly + var is_blank = !this.layers.drawing.ctx + .getImageData(0, 0, this.width, this.height) + .data.some((channel) => channel !== 0) + maskSetting.checked = !is_blank + } + this.hide() + } + getImg() { + // a drop-in replacement of the drawingboard version + return this.layers.drawing.canvas.toDataURL() + } + setImg(dataUrl) { + // a drop-in replacement of the drawingboard version + var image = new Image() + image.onload = () => { + var ctx = this.layers.drawing.ctx + ctx.clearRect(0, 0, this.width, this.height) + ctx.globalCompositeOperation = "source-over" + ctx.globalAlpha = 1 + ctx.filter = "none" + ctx.drawImage(image, 0, 0, this.width, this.height) + this.setBrush(this.layers.drawing) + } + image.src = dataUrl + } + runAction(action_id) { + var action = IMAGE_EDITOR_ACTIONS.find((a) => a.id == action_id) + if (action.trackHistory) { + this.history.pushAction(action_id) + } + action.handler(this) + } + setBrush(layer = null, options = null) { + if (options == null) { + options = this.options + } + if (layer) { + layer.ctx.lineCap = "round" + layer.ctx.lineJoin = "round" + layer.ctx.lineWidth = options.brush_size + layer.ctx.fillStyle = options.color + layer.ctx.strokeStyle = options.color + var sharpness = parseInt(options.sharpness * options.brush_size) + layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)` + layer.ctx.globalAlpha = 1 - options.opacity + layer.ctx.globalCompositeOperation = "source-over" + var tool = IMAGE_EDITOR_TOOLS.find((t) => t.id == options.tool) + if (tool && tool.setBrush) { + tool.setBrush(editor, layer) + } + } else { + Object.values(["drawing", "overlay"]) + .map((name) => this.layers[name]) + .forEach((l) => { + this.setBrush(l) + }) + } + } + get ctx_overlay() { + return this.layers.overlay.ctx + } + get ctx_current() { + // the idea is this will help support having custom layers and editing each one + return this.layers.drawing.ctx + } + get canvas_current() { + return this.layers.drawing.canvas + } + keyHandler(event) { + // handles keybinds like ctrl+z, ctrl+y + if (!this.popup.classList.contains("active")) { + document.removeEventListener("keydown", this.keyHandlerBound) + document.removeEventListener("keyup", this.keyHandlerBound) + return // this catches if something else closes the window but doesnt properly unbind the key handler + } + + // keybindings + if (event.type == "keydown") { + if ((event.key == "z" || event.key == "Z") && event.ctrlKey) { + if (!event.shiftKey) { + this.history.undo() + } else { + this.history.redo() + } + event.stopPropagation() + event.preventDefault() + } + if (event.key == "y" && event.ctrlKey) { + this.history.redo() + event.stopPropagation() + event.preventDefault() + } + if (event.key === "Escape") { + this.hide() + event.stopPropagation() + event.preventDefault() + } + } + + // dropper ctrl holding handler stuff + var dropper_active = this.temp_previous_tool != null + if (dropper_active && !event.ctrlKey) { + this.selectOption( + "tool", + IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == this.temp_previous_tool) + ) + this.temp_previous_tool = null + } else if (!dropper_active && event.ctrlKey) { + this.temp_previous_tool = this.getOptionValue("tool") + this.selectOption( + "tool", + IMAGE_EDITOR_TOOLS.findIndex((t) => t.id == "colorpicker") + ) + } + } + mouseHandler(event) { + var bbox = this.layers.overlay.canvas.getBoundingClientRect() + var x = (event.clientX || 0) - bbox.left + var y = (event.clientY || 0) - bbox.top + var type = event.type + var touchmap = { + touchstart: "mousedown", + touchmove: "mousemove", + touchend: "mouseup", + touchcancel: "mouseup" + } + if (type in touchmap) { + type = touchmap[type] + if (event.touches && event.touches[0]) { + var touch = event.touches[0] + var x = (touch.clientX || 0) - bbox.left + var y = (touch.clientY || 0) - bbox.top + } + } + event.preventDefault() + // do drawing-related stuff + if (type == "mousedown" || (type == "mouseenter" && event.buttons == 1)) { + this.drawing = true + this.tool.begin(this, this.ctx_current, x, y) + this.tool.begin(this, this.ctx_overlay, x, y, true) + this.history.editBegin(x, y) + } + if (type == "mouseup" || type == "mousemove") { + if (this.drawing) { + if (x > 0 && y > 0) { + this.tool.move(this, this.ctx_current, x, y) + this.tool.move(this, this.ctx_overlay, x, y, true) + this.history.editMove(x, y) + } + } + } + if (type == "mouseup" || type == "mouseout") { + if (this.drawing) { + this.drawing = false + this.tool.end(this, this.ctx_current, x, y) + this.tool.end(this, this.ctx_overlay, x, y, true) + this.history.editEnd(x, y) + } + } + } + getOptionValue(section_name) { + var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name) + return this.options && section_name in this.options ? this.options[section_name] : section.default + } + selectOption(section_name, option_index) { + var section = IMAGE_EDITOR_SECTIONS.find((s) => s.name == section_name) + var value = section.options[option_index] + this.options[section_name] = value == "custom" ? section.getCustom(this) : value + + this.optionElements[section_name].forEach((element) => element.classList.remove("active")) + this.optionElements[section_name][option_index].classList.add("active") + + // change the editor + this.setBrush() + if (section.name == "tool") { + this.loadTool() + } + } } const imageEditor = new ImageEditor(document.getElementById("image-editor")) @@ -770,114 +802,126 @@ imageEditor.setImage(null, 512, 512) imageInpainter.setImage(null, 512, 512) document.getElementById("init_image_button_draw").addEventListener("click", () => { - imageEditor.show() + imageEditor.show() }) document.getElementById("init_image_button_inpaint").addEventListener("click", () => { - imageInpainter.show() + imageInpainter.show() }) img2imgUnload() // no init image when the app starts - function rgbToHex(rgb) { - function componentToHex(c) { - var hex = parseInt(c).toString(16) - return hex.length == 1 ? "0" + hex : hex - } - return "#" + componentToHex(rgb.r) + componentToHex(rgb.g) + componentToHex(rgb.b) + function componentToHex(c) { + var hex = parseInt(c).toString(16) + return hex.length == 1 ? "0" + hex : hex + } + return "#" + componentToHex(rgb.r) + componentToHex(rgb.g) + componentToHex(rgb.b) } function hexToRgb(hex) { - var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16) - } : null; + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) + return result + ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } + : null } function pixelCompare(int1, int2) { - return Math.abs(int1 - int2) < 4 + return Math.abs(int1 - int2) < 4 } // adapted from https://ben.akrin.com/canvas_fill/fill_04.html function flood_fill(editor, the_canvas_context, x, y, color) { - pixel_stack = [{x:x, y:y}] ; - pixels = the_canvas_context.getImageData( 0, 0, editor.width, editor.height ) ; - var linear_cords = ( y * editor.width + x ) * 4 ; - var original_color = {r:pixels.data[linear_cords], - g:pixels.data[linear_cords+1], - b:pixels.data[linear_cords+2], - a:pixels.data[linear_cords+3]} ; - - var opacity = color.a / 255; - var new_color = { - r: parseInt((color.r * opacity) + (original_color.r * (1 - opacity))), - g: parseInt((color.g * opacity) + (original_color.g * (1 - opacity))), - b: parseInt((color.b * opacity) + (original_color.b * (1 - opacity))) - } + pixel_stack = [{ x: x, y: y }] + pixels = the_canvas_context.getImageData(0, 0, editor.width, editor.height) + var linear_cords = (y * editor.width + x) * 4 + var original_color = { + r: pixels.data[linear_cords], + g: pixels.data[linear_cords + 1], + b: pixels.data[linear_cords + 2], + a: pixels.data[linear_cords + 3] + } - if ((pixelCompare(new_color.r, original_color.r) && - pixelCompare(new_color.g, original_color.g) && - pixelCompare(new_color.b, original_color.b))) - { - return; // This color is already the color we want, so do nothing - } - var max_stack_size = editor.width * editor.height; - while( pixel_stack.length > 0 && pixel_stack.length < max_stack_size ) { - new_pixel = pixel_stack.shift() ; - x = new_pixel.x ; - y = new_pixel.y ; - - linear_cords = ( y * editor.width + x ) * 4 ; - while( y-->=0 && - (pixelCompare(pixels.data[linear_cords], original_color.r) && - pixelCompare(pixels.data[linear_cords+1], original_color.g) && - pixelCompare(pixels.data[linear_cords+2], original_color.b))) { - linear_cords -= editor.width * 4 ; - } - linear_cords += editor.width * 4 ; - y++ ; + var opacity = color.a / 255 + var new_color = { + r: parseInt(color.r * opacity + original_color.r * (1 - opacity)), + g: parseInt(color.g * opacity + original_color.g * (1 - opacity)), + b: parseInt(color.b * opacity + original_color.b * (1 - opacity)) + } - var reached_left = false ; - var reached_right = false ; - while( y++ 0 && pixel_stack.length < max_stack_size) { + new_pixel = pixel_stack.shift() + x = new_pixel.x + y = new_pixel.y - if( x>0 ) { - if( pixelCompare(pixels.data[linear_cords-4], original_color.r) && - pixelCompare(pixels.data[linear_cords-4+1], original_color.g) && - pixelCompare(pixels.data[linear_cords-4+2], original_color.b)) { - if( !reached_left ) { - pixel_stack.push( {x:x-1, y:y} ) ; - reached_left = true ; - } - } else if( reached_left ) { - reached_left = false ; - } - } - - if( x= 0 && + pixelCompare(pixels.data[linear_cords], original_color.r) && + pixelCompare(pixels.data[linear_cords + 1], original_color.g) && + pixelCompare(pixels.data[linear_cords + 2], original_color.b) + ) { + linear_cords -= editor.width * 4 + } + linear_cords += editor.width * 4 + y++ + + var reached_left = false + var reached_right = false + while ( + y++ < editor.height && + pixelCompare(pixels.data[linear_cords], original_color.r) && + pixelCompare(pixels.data[linear_cords + 1], original_color.g) && + pixelCompare(pixels.data[linear_cords + 2], original_color.b) + ) { + pixels.data[linear_cords] = new_color.r + pixels.data[linear_cords + 1] = new_color.g + pixels.data[linear_cords + 2] = new_color.b + pixels.data[linear_cords + 3] = 255 + + if (x > 0) { + if ( + pixelCompare(pixels.data[linear_cords - 4], original_color.r) && + pixelCompare(pixels.data[linear_cords - 4 + 1], original_color.g) && + pixelCompare(pixels.data[linear_cords - 4 + 2], original_color.b) + ) { + if (!reached_left) { + pixel_stack.push({ x: x - 1, y: y }) + reached_left = true + } + } else if (reached_left) { + reached_left = false + } + } + + if (x < editor.width - 1) { + if ( + pixelCompare(pixels.data[linear_cords + 4], original_color.r) && + pixelCompare(pixels.data[linear_cords + 4 + 1], original_color.g) && + pixelCompare(pixels.data[linear_cords + 4 + 2], original_color.b) + ) { + if (!reached_right) { + pixel_stack.push({ x: x + 1, y: y }) + reached_right = true + } + } else if (reached_right) { + reached_right = false + } + } + + linear_cords += editor.width * 4 + } + } + the_canvas_context.putImageData(pixels, 0, 0) } diff --git a/ui/media/js/image-modal.js b/ui/media/js/image-modal.js index 367c754c..df255756 100644 --- a/ui/media/js/image-modal.js +++ b/ui/media/js/image-modal.js @@ -11,56 +11,35 @@ * @type {(() => (string | ImageModalRequest) | string | ImageModalRequest) => {}} */ const imageModal = (function() { - const backElem = createElement( - 'i', - undefined, - ['fa-solid', 'fa-arrow-left', 'tertiaryButton'], - ) + const backElem = createElement("i", undefined, ["fa-solid", "fa-arrow-left", "tertiaryButton"]) - const forwardElem = createElement( - 'i', - undefined, - ['fa-solid', 'fa-arrow-right', 'tertiaryButton'], - ) + const forwardElem = createElement("i", undefined, ["fa-solid", "fa-arrow-right", "tertiaryButton"]) - const zoomElem = createElement( - 'i', - undefined, - ['fa-solid', 'tertiaryButton'], - ) + const zoomElem = createElement("i", undefined, ["fa-solid", "tertiaryButton"]) - const closeElem = createElement( - 'i', - undefined, - ['fa-solid', 'fa-xmark', 'tertiaryButton'], - ) + const closeElem = createElement("i", undefined, ["fa-solid", "fa-xmark", "tertiaryButton"]) - const menuBarElem = createElement('div', undefined, 'menu-bar', [backElem, forwardElem, zoomElem, closeElem]) + const menuBarElem = createElement("div", undefined, "menu-bar", [backElem, forwardElem, zoomElem, closeElem]) - const imageContainer = createElement('div', undefined, 'image-wrapper') + const imageContainer = createElement("div", undefined, "image-wrapper") - const backdrop = createElement('div', undefined, 'backdrop') + const backdrop = createElement("div", undefined, "backdrop") - const modalContainer = createElement('div', undefined, 'content', [menuBarElem, imageContainer]) + const modalContainer = createElement("div", undefined, "content", [menuBarElem, imageContainer]) - const modalElem = createElement( - 'div', - { id: 'viewFullSizeImgModal' }, - ['popup'], - [backdrop, modalContainer], - ) + const modalElem = createElement("div", { id: "viewFullSizeImgModal" }, ["popup"], [backdrop, modalContainer]) document.body.appendChild(modalElem) const setZoomLevel = (value) => { - const img = imageContainer.querySelector('img') + const img = imageContainer.querySelector("img") if (value) { - zoomElem.classList.remove('fa-magnifying-glass-plus') - zoomElem.classList.add('fa-magnifying-glass-minus') + zoomElem.classList.remove("fa-magnifying-glass-plus") + zoomElem.classList.add("fa-magnifying-glass-minus") if (img) { - img.classList.remove('natural-zoom') + img.classList.remove("natural-zoom") - let zoomLevel = typeof value === 'number' ? value : img.dataset.zoomLevel + let zoomLevel = typeof value === "number" ? value : img.dataset.zoomLevel if (!zoomLevel) { zoomLevel = 100 } @@ -70,36 +49,35 @@ const imageModal = (function() { img.height = img.naturalHeight * (+zoomLevel / 100) } } else { - zoomElem.classList.remove('fa-magnifying-glass-minus') - zoomElem.classList.add('fa-magnifying-glass-plus') + zoomElem.classList.remove("fa-magnifying-glass-minus") + zoomElem.classList.add("fa-magnifying-glass-plus") if (img) { - img.classList.add('natural-zoom') - img.removeAttribute('width') - img.removeAttribute('height') + img.classList.add("natural-zoom") + img.removeAttribute("width") + img.removeAttribute("height") } } } - zoomElem.addEventListener( - 'click', - () => setZoomLevel(imageContainer.querySelector('img')?.classList?.contains('natural-zoom')), + zoomElem.addEventListener("click", () => + setZoomLevel(imageContainer.querySelector("img")?.classList?.contains("natural-zoom")) ) const state = { previous: undefined, - next: undefined, + next: undefined } const clear = () => { - imageContainer.innerHTML = '' + imageContainer.innerHTML = "" - Object.keys(state).forEach(key => delete state[key]) + Object.keys(state).forEach((key) => delete state[key]) } const close = () => { clear() - modalElem.classList.remove('active') - document.body.style.overflow = 'initial' + modalElem.classList.remove("active") + document.body.style.overflow = "initial" } /** @@ -113,27 +91,27 @@ const imageModal = (function() { clear() - const options = typeof optionsFactory === 'function' ? optionsFactory() : optionsFactory - const src = typeof options === 'string' ? options : options.src + const options = typeof optionsFactory === "function" ? optionsFactory() : optionsFactory + const src = typeof options === "string" ? options : options.src - const imgElem = createElement('img', { src }, 'natural-zoom') + const imgElem = createElement("img", { src }, "natural-zoom") imageContainer.appendChild(imgElem) - modalElem.classList.add('active') - document.body.style.overflow = 'hidden' + modalElem.classList.add("active") + document.body.style.overflow = "hidden" setZoomLevel(false) - if (typeof options === 'object' && options.previous) { + if (typeof options === "object" && options.previous) { state.previous = options.previous - backElem.style.display = 'unset' + backElem.style.display = "unset" } else { - backElem.style.display = 'none' + backElem.style.display = "none" } - if (typeof options === 'object' && options.next) { + if (typeof options === "object" && options.next) { state.next = options.next - forwardElem.style.display = 'unset' + forwardElem.style.display = "unset" } else { - forwardElem.style.display = 'none' + forwardElem.style.display = "none" } } @@ -141,7 +119,7 @@ const imageModal = (function() { if (state.previous) { init(state.previous) } else { - backElem.style.display = 'none' + backElem.style.display = "none" } } @@ -149,27 +127,27 @@ const imageModal = (function() { if (state.next) { init(state.next) } else { - forwardElem.style.display = 'none' + forwardElem.style.display = "none" } } - window.addEventListener('keydown', (e) => { - if (modalElem.classList.contains('active')) { + window.addEventListener("keydown", (e) => { + if (modalElem.classList.contains("active")) { switch (e.key) { - case 'Escape': + case "Escape": close() break - case 'ArrowLeft': + case "ArrowLeft": back() break - case 'ArrowRight': + case "ArrowRight": forward() break } } }) - window.addEventListener('click', (e) => { - if (modalElem.classList.contains('active')) { + window.addEventListener("click", (e) => { + if (modalElem.classList.contains("active")) { if (e.target === backdrop || e.target === closeElem) { close() } @@ -180,9 +158,9 @@ const imageModal = (function() { } }) - backElem.addEventListener('click', back) + backElem.addEventListener("click", back) - forwardElem.addEventListener('click', forward) + forwardElem.addEventListener("click", forward) /** * @param {() => (string | ImageModalRequest) | string | ImageModalRequest} optionsFactory diff --git a/ui/media/js/image-modifiers.js b/ui/media/js/image-modifiers.js index eaea07f6..0671a7c7 100644 --- a/ui/media/js/image-modifiers.js +++ b/ui/media/js/image-modifiers.js @@ -3,26 +3,26 @@ let modifiers = [] let customModifiersGroupElement = undefined let customModifiersInitialContent -let editorModifierEntries = document.querySelector('#editor-modifiers-entries') -let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list') -let editorTagsContainer = document.querySelector('#editor-inputs-tags-container') -let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider') -let previewImageField = document.querySelector('#preview-image') -let modifierSettingsBtn = document.querySelector('#modifier-settings-btn') -let modifierSettingsOverlay = document.querySelector('#modifier-settings-config') -let customModifiersTextBox = document.querySelector('#custom-modifiers-input') -let customModifierEntriesToolbar = document.querySelector('#editor-modifiers-entries-toolbar') +let editorModifierEntries = document.querySelector("#editor-modifiers-entries") +let editorModifierTagsList = document.querySelector("#editor-inputs-tags-list") +let editorTagsContainer = document.querySelector("#editor-inputs-tags-container") +let modifierCardSizeSlider = document.querySelector("#modifier-card-size-slider") +let previewImageField = document.querySelector("#preview-image") +let modifierSettingsBtn = document.querySelector("#modifier-settings-btn") +let modifierSettingsOverlay = document.querySelector("#modifier-settings-config") +let customModifiersTextBox = document.querySelector("#custom-modifiers-input") +let customModifierEntriesToolbar = document.querySelector("#editor-modifiers-entries-toolbar") -const modifierThumbnailPath = 'media/modifier-thumbnails' -const activeCardClass = 'modifier-card-active' +const modifierThumbnailPath = "media/modifier-thumbnails" +const activeCardClass = "modifier-card-active" const CUSTOM_MODIFIERS_KEY = "customModifiers" function createModifierCard(name, previews, removeBy) { - const modifierCard = document.createElement('div') + const modifierCard = document.createElement("div") let style = previewImageField.value - let styleIndex = (style=='portrait') ? 0 : 1 + let styleIndex = style == "portrait" ? 0 : 1 - modifierCard.className = 'modifier-card' + modifierCard.className = "modifier-card" modifierCard.innerHTML = `
@@ -34,35 +34,35 @@ function createModifierCard(name, previews, removeBy) {

` - const image = modifierCard.querySelector('.modifier-card-image') - const errorText = modifierCard.querySelector('.modifier-card-error-label') - const label = modifierCard.querySelector('.modifier-card-label') + const image = modifierCard.querySelector(".modifier-card-image") + const errorText = modifierCard.querySelector(".modifier-card-error-label") + const label = modifierCard.querySelector(".modifier-card-label") - errorText.innerText = 'No Image' + errorText.innerText = "No Image" - if (typeof previews == 'object') { - image.src = previews[styleIndex]; // portrait - image.setAttribute('preview-type', style) + if (typeof previews == "object") { + image.src = previews[styleIndex] // portrait + image.setAttribute("preview-type", style) } else { image.remove() } const maxLabelLength = 30 - const cardLabel = removeBy ? name.replace('by ', '') : name + const cardLabel = removeBy ? name.replace("by ", "") : name - if(cardLabel.length <= maxLabelLength) { - label.querySelector('p').innerText = cardLabel + if (cardLabel.length <= maxLabelLength) { + label.querySelector("p").innerText = cardLabel } else { - const tooltipText = document.createElement('span') - tooltipText.className = 'tooltip-text' + const tooltipText = document.createElement("span") + tooltipText.className = "tooltip-text" tooltipText.innerText = name - label.classList.add('tooltip') + label.classList.add("tooltip") label.appendChild(tooltipText) - label.querySelector('p').innerText = cardLabel.substring(0, maxLabelLength) + '...' + label.querySelector("p").innerText = cardLabel.substring(0, maxLabelLength) + "..." } - label.querySelector('p').dataset.fullName = name // preserve the full name + label.querySelector("p").dataset.fullName = name // preserve the full name return modifierCard } @@ -71,55 +71,58 @@ function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) { const title = modifierGroup.category const modifiers = modifierGroup.modifiers - const titleEl = document.createElement('h5') - titleEl.className = 'collapsible' + const titleEl = document.createElement("h5") + titleEl.className = "collapsible" titleEl.innerText = title - const modifiersEl = document.createElement('div') - modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf') + const modifiersEl = document.createElement("div") + modifiersEl.classList.add("collapsible-content", "editor-modifiers-leaf") if (initiallyExpanded === true) { - titleEl.className += ' active' + titleEl.className += " active" } - modifiers.forEach(modObj => { + modifiers.forEach((modObj) => { const modifierName = modObj.modifier - const modifierPreviews = modObj?.previews?.map(preview => `${IMAGE_REGEX.test(preview.image) ? preview.image : modifierThumbnailPath + '/' + preview.path}`) + const modifierPreviews = modObj?.previews?.map( + (preview) => + `${IMAGE_REGEX.test(preview.image) ? preview.image : modifierThumbnailPath + "/" + preview.path}` + ) const modifierCard = createModifierCard(modifierName, modifierPreviews, removeBy) - if(typeof modifierCard == 'object') { + if (typeof modifierCard == "object") { modifiersEl.appendChild(modifierCard) const trimmedName = trimModifiers(modifierName) - modifierCard.addEventListener('click', () => { - if (activeTags.map(x => trimModifiers(x.name)).includes(trimmedName)) { + modifierCard.addEventListener("click", () => { + if (activeTags.map((x) => trimModifiers(x.name)).includes(trimmedName)) { // remove modifier from active array - activeTags = activeTags.filter(x => trimModifiers(x.name) != trimmedName) + activeTags = activeTags.filter((x) => trimModifiers(x.name) != trimmedName) toggleCardState(trimmedName, false) } else { // add modifier to active array activeTags.push({ - 'name': modifierName, - 'element': modifierCard.cloneNode(true), - 'originElement': modifierCard, - 'previews': modifierPreviews + name: modifierName, + element: modifierCard.cloneNode(true), + originElement: modifierCard, + previews: modifierPreviews }) toggleCardState(trimmedName, true) } refreshTagsList() - document.dispatchEvent(new Event('refreshImageModifiers')) + document.dispatchEvent(new Event("refreshImageModifiers")) }) } }) - let brk = document.createElement('br') - brk.style.clear = 'both' + let brk = document.createElement("br") + brk.style.clear = "both" modifiersEl.appendChild(brk) - let e = document.createElement('div') - e.className = 'modifier-category' + let e = document.createElement("div") + e.className = "modifier-category" e.appendChild(titleEl) e.appendChild(modifiersEl) @@ -130,87 +133,98 @@ function createModifierGroup(modifierGroup, initiallyExpanded, removeBy) { function trimModifiers(tag) { // Remove trailing '-' and/or '+' - tag = tag.replace(/[-+]+$/, ''); + tag = tag.replace(/[-+]+$/, "") // Remove parentheses at beginning and end - return tag.replace(/^[(]+|[\s)]+$/g, ''); + return tag.replace(/^[(]+|[\s)]+$/g, "") } async function loadModifiers() { try { - let res = await fetch('/get/modifiers') + let res = await fetch("/get/modifiers") if (res.status === 200) { res = await res.json() - modifiers = res; // update global variable + modifiers = res // update global variable res.reverse() res.forEach((modifierGroup, idx) => { - createModifierGroup(modifierGroup, idx === res.length - 1, modifierGroup === 'Artist' ? true : false) // only remove "By " for artists + createModifierGroup(modifierGroup, idx === res.length - 1, modifierGroup === "Artist" ? true : false) // only remove "By " for artists }) createCollapsibles(editorModifierEntries) } } catch (e) { - console.error('error fetching modifiers', e) + console.error("error fetching modifiers", e) } loadCustomModifiers() resizeModifierCards(modifierCardSizeSlider.value) - document.dispatchEvent(new Event('loadImageModifiers')) + document.dispatchEvent(new Event("loadImageModifiers")) } function refreshModifiersState(newTags, inactiveTags) { // clear existing modifiers - document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => { - const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName // pick the full modifier name - if (activeTags.map(x => x.name).includes(modifierName)) { - modifierCard.classList.remove(activeCardClass) - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' - } - }) + document + .querySelector("#editor-modifiers") + .querySelectorAll(".modifier-card") + .forEach((modifierCard) => { + const modifierName = modifierCard.querySelector(".modifier-card-label p").dataset.fullName // pick the full modifier name + if (activeTags.map((x) => x.name).includes(modifierName)) { + modifierCard.classList.remove(activeCardClass) + modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+" + } + }) activeTags = [] // set new modifiers - newTags.forEach(tag => { + newTags.forEach((tag) => { let found = false - document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => { - const modifierName = modifierCard.querySelector('.modifier-card-label p').dataset.fullName - const shortModifierName = modifierCard.querySelector('.modifier-card-label p').innerText - if (trimModifiers(tag) == trimModifiers(modifierName)) { - // add modifier to active array - if (!activeTags.map(x => x.name).includes(tag)) { // only add each tag once even if several custom modifier cards share the same tag - const imageModifierCard = modifierCard.cloneNode(true) - imageModifierCard.querySelector('.modifier-card-label p').innerText = tag.replace(modifierName, shortModifierName) - activeTags.push({ - 'name': tag, - 'element': imageModifierCard, - 'originElement': modifierCard - }) + document + .querySelector("#editor-modifiers") + .querySelectorAll(".modifier-card") + .forEach((modifierCard) => { + const modifierName = modifierCard.querySelector(".modifier-card-label p").dataset.fullName + const shortModifierName = modifierCard.querySelector(".modifier-card-label p").innerText + if (trimModifiers(tag) == trimModifiers(modifierName)) { + // add modifier to active array + if (!activeTags.map((x) => x.name).includes(tag)) { + // only add each tag once even if several custom modifier cards share the same tag + const imageModifierCard = modifierCard.cloneNode(true) + imageModifierCard.querySelector(".modifier-card-label p").innerText = tag.replace( + modifierName, + shortModifierName + ) + activeTags.push({ + name: tag, + element: imageModifierCard, + originElement: modifierCard + }) + } + modifierCard.classList.add(activeCardClass) + modifierCard.querySelector(".modifier-card-image-overlay").innerText = "-" + found = true } - modifierCard.classList.add(activeCardClass) - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-' - found = true - } - }) - if (found == false) { // custom tag went missing, create one here + }) + if (found == false) { + // custom tag went missing, create one here let modifierCard = createModifierCard(tag, undefined, false) // create a modifier card for the missing tag, no image - - modifierCard.addEventListener('click', () => { - if (activeTags.map(x => x.name).includes(tag)) { + + modifierCard.addEventListener("click", () => { + if (activeTags.map((x) => x.name).includes(tag)) { // remove modifier from active array - activeTags = activeTags.filter(x => x.name != tag) + activeTags = activeTags.filter((x) => x.name != tag) modifierCard.classList.remove(activeCardClass) - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' + modifierCard.querySelector(".modifier-card-image-overlay").innerText = "+" } refreshTagsList() }) activeTags.push({ - 'name': tag, - 'element': modifierCard, - 'originElement': undefined // no origin element for missing tags + name: tag, + element: modifierCard, + originElement: undefined // no origin element for missing tags }) } }) @@ -220,41 +234,44 @@ function refreshModifiersState(newTags, inactiveTags) { function refreshInactiveTags(inactiveTags) { // update inactive tags if (inactiveTags !== undefined && inactiveTags.length > 0) { - activeTags.forEach (tag => { - if (inactiveTags.find(element => element === tag.name) !== undefined) { + activeTags.forEach((tag) => { + if (inactiveTags.find((element) => element === tag.name) !== undefined) { tag.inactive = true } }) } - + // update cards - let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay') - overlays.forEach (i => { - let modifierName = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].dataset.fullName - if (inactiveTags?.find(element => element === modifierName) !== undefined) { - i.parentElement.classList.add('modifier-toggle-inactive') + let overlays = document.querySelector("#editor-inputs-tags-list").querySelectorAll(".modifier-card-overlay") + overlays.forEach((i) => { + let modifierName = i.parentElement.getElementsByClassName("modifier-card-label")[0].getElementsByTagName("p")[0] + .dataset.fullName + if (inactiveTags?.find((element) => element === modifierName) !== undefined) { + i.parentElement.classList.add("modifier-toggle-inactive") } }) } function refreshTagsList(inactiveTags) { - editorModifierTagsList.innerHTML = '' + editorModifierTagsList.innerHTML = "" if (activeTags.length == 0) { - editorTagsContainer.style.display = 'none' + editorTagsContainer.style.display = "none" return } else { - editorTagsContainer.style.display = 'block' + editorTagsContainer.style.display = "block" } activeTags.forEach((tag, index) => { - tag.element.querySelector('.modifier-card-image-overlay').innerText = '-' - tag.element.classList.add('modifier-card-tiny') + tag.element.querySelector(".modifier-card-image-overlay").innerText = "-" + tag.element.classList.add("modifier-card-tiny") editorModifierTagsList.appendChild(tag.element) - tag.element.addEventListener('click', () => { - let idx = activeTags.findIndex(o => { return o.name === tag.name }) + tag.element.addEventListener("click", () => { + let idx = activeTags.findIndex((o) => { + return o.name === tag.name + }) if (idx !== -1) { toggleCardState(activeTags[idx].name, false) @@ -262,88 +279,91 @@ function refreshTagsList(inactiveTags) { activeTags.splice(idx, 1) refreshTagsList() } - document.dispatchEvent(new Event('refreshImageModifiers')) + document.dispatchEvent(new Event("refreshImageModifiers")) }) }) - let brk = document.createElement('br') - brk.style.clear = 'both' + let brk = document.createElement("br") + brk.style.clear = "both" editorModifierTagsList.appendChild(brk) refreshInactiveTags(inactiveTags) - document.dispatchEvent(new Event('refreshImageModifiers')) // notify plugins that the image tags have been refreshed + document.dispatchEvent(new Event("refreshImageModifiers")) // notify plugins that the image tags have been refreshed } function toggleCardState(modifierName, makeActive) { - document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(card => { - const name = card.querySelector('.modifier-card-label').innerText - if ( trimModifiers(modifierName) == trimModifiers(name) - || trimModifiers(modifierName) == 'by ' + trimModifiers(name)) { - if(makeActive) { - card.classList.add(activeCardClass) - card.querySelector('.modifier-card-image-overlay').innerText = '-' + document + .querySelector("#editor-modifiers") + .querySelectorAll(".modifier-card") + .forEach((card) => { + const name = card.querySelector(".modifier-card-label").innerText + if ( + trimModifiers(modifierName) == trimModifiers(name) || + trimModifiers(modifierName) == "by " + trimModifiers(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 = "+" + } } - else{ - card.classList.remove(activeCardClass) - card.querySelector('.modifier-card-image-overlay').innerText = '+' - } - } - }) + }) } function changePreviewImages(val) { - const previewImages = document.querySelectorAll('.modifier-card-image-container img') + const previewImages = document.querySelectorAll(".modifier-card-image-container img") let previewArr = [] - modifiers.map(x => x.modifiers).forEach(x => previewArr.push(...x.map(m => m.previews))) - - previewArr = previewArr.map(x => { + modifiers.map((x) => x.modifiers).forEach((x) => previewArr.push(...x.map((m) => m.previews))) + + previewArr = previewArr.map((x) => { let obj = {} - x.forEach(preview => { + x.forEach((preview) => { obj[preview.name] = preview.path }) - + return obj }) - previewImages.forEach(previewImage => { - const currentPreviewType = previewImage.getAttribute('preview-type') - const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + '/').pop() + previewImages.forEach((previewImage) => { + const currentPreviewType = previewImage.getAttribute("preview-type") + const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + "/").pop() - const previews = previewArr.find(preview => relativePreviewPath == preview[currentPreviewType]) + const previews = previewArr.find((preview) => relativePreviewPath == preview[currentPreviewType]) - if(typeof previews == 'object') { + if (typeof previews == "object") { let preview = null - if (val == 'portrait') { + if (val == "portrait") { preview = previews.portrait - } - else if (val == 'landscape') { + } else if (val == "landscape") { preview = previews.landscape } - if(preview != null) { + if (preview != null) { previewImage.src = `${modifierThumbnailPath}/${preview}` - previewImage.setAttribute('preview-type', val) + previewImage.setAttribute("preview-type", val) } } }) } function resizeModifierCards(val) { - const cardSizePrefix = 'modifier-card-size_' - const modifierCardClass = 'modifier-card' + const cardSizePrefix = "modifier-card-size_" + const modifierCardClass = "modifier-card" const modifierCards = document.querySelectorAll(`.${modifierCardClass}`) - const cardSize = n => `${cardSizePrefix}${n}` + const cardSize = (n) => `${cardSizePrefix}${n}` - modifierCards.forEach(card => { + modifierCards.forEach((card) => { // remove existing size classes - const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix)) - card.className = classes.join(' ').trim() + const classes = card.className.split(" ").filter((c) => !c.startsWith(cardSizePrefix)) + card.className = classes.join(" ").trim() - if(val != 0) { + if (val != 0) { card.classList.add(cardSize(val)) } }) @@ -352,7 +372,7 @@ function resizeModifierCards(val) { modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value) previewImageField.onchange = () => changePreviewImages(previewImageField.value) -modifierSettingsBtn.addEventListener('click', function(e) { +modifierSettingsBtn.addEventListener("click", function(e) { modifierSettingsOverlay.classList.add("active") customModifiersTextBox.setSelectionRange(0, 0) customModifiersTextBox.focus() @@ -360,7 +380,7 @@ modifierSettingsBtn.addEventListener('click', function(e) { e.stopPropagation() }) -modifierSettingsOverlay.addEventListener('keydown', function(e) { +modifierSettingsOverlay.addEventListener("keydown", function(e) { switch (e.key) { case "Escape": // Escape to cancel customModifiersTextBox.value = customModifiersInitialContent // undo the changes @@ -368,7 +388,8 @@ modifierSettingsOverlay.addEventListener('keydown', function(e) { e.stopPropagation() break case "Enter": - if (e.ctrlKey) { // Ctrl+Enter to confirm + if (e.ctrlKey) { + // Ctrl+Enter to confirm modifierSettingsOverlay.classList.remove("active") e.stopPropagation() break @@ -383,7 +404,7 @@ function saveCustomModifiers() { } function loadCustomModifiers() { - PLUGINS['MODIFIERS_LOAD'].forEach(fn=>fn.loader.call()) + PLUGINS["MODIFIERS_LOAD"].forEach((fn) => fn.loader.call()) } -customModifiersTextBox.addEventListener('change', saveCustomModifiers) +customModifiersTextBox.addEventListener("change", saveCustomModifiers) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 0720b988..2f63650b 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -2,80 +2,97 @@ const MAX_INIT_IMAGE_DIMENSION = 768 const MIN_GPUS_TO_SHOW_SELECTION = 2 -const IMAGE_REGEX = new RegExp('data:image/[A-Za-z]+;base64') +const IMAGE_REGEX = new RegExp("data:image/[A-Za-z]+;base64") const htmlTaskMap = new WeakMap() const taskConfigSetup = { taskConfig: { - seed: { value: ({ seed }) => seed, label: 'Seed' }, - dimensions: { value: ({ reqBody }) => `${reqBody?.width}x${reqBody?.height}`, label: 'Dimensions' }, - sampler_name: 'Sampler', - num_inference_steps: 'Inference Steps', - guidance_scale: 'Guidance Scale', - use_stable_diffusion_model: 'Model', - use_vae_model: { label: 'VAE', visible: ({ reqBody }) => reqBody?.use_vae_model !== undefined && reqBody?.use_vae_model.trim() !== ''}, - negative_prompt: { label: 'Negative Prompt', visible: ({ reqBody }) => reqBody?.negative_prompt !== undefined && reqBody?.negative_prompt.trim() !== ''}, - prompt_strength: 'Prompt Strength', - use_face_correction: 'Fix Faces', - upscale: { value: ({ reqBody }) => `${reqBody?.use_upscale} (${reqBody?.upscale_amount || 4}x)`, label: 'Upscale', visible: ({ reqBody }) => !!reqBody?.use_upscale }, - use_hypernetwork_model: 'Hypernetwork', - hypernetwork_strength: { label: 'Hypernetwork Strength', visible: ({ reqBody }) => !!reqBody?.use_hypernetwork_model }, - use_lora_model: { label: 'Lora Model', visible: ({ reqBody }) => !!reqBody?.use_lora_model }, - lora_alpha: { label: 'Lora Strength', visible: ({ reqBody }) => !!reqBody?.use_lora_model }, - preserve_init_image_color_profile: 'Preserve Color Profile', + seed: { value: ({ seed }) => seed, label: "Seed" }, + dimensions: { value: ({ reqBody }) => `${reqBody?.width}x${reqBody?.height}`, label: "Dimensions" }, + sampler_name: "Sampler", + num_inference_steps: "Inference Steps", + guidance_scale: "Guidance Scale", + use_stable_diffusion_model: "Model", + use_vae_model: { + label: "VAE", + visible: ({ reqBody }) => reqBody?.use_vae_model !== undefined && reqBody?.use_vae_model.trim() !== "" + }, + negative_prompt: { + label: "Negative Prompt", + visible: ({ reqBody }) => reqBody?.negative_prompt !== undefined && reqBody?.negative_prompt.trim() !== "" + }, + prompt_strength: "Prompt Strength", + use_face_correction: "Fix Faces", + upscale: { + value: ({ reqBody }) => `${reqBody?.use_upscale} (${reqBody?.upscale_amount || 4}x)`, + label: "Upscale", + visible: ({ reqBody }) => !!reqBody?.use_upscale + }, + use_hypernetwork_model: "Hypernetwork", + hypernetwork_strength: { + label: "Hypernetwork Strength", + visible: ({ reqBody }) => !!reqBody?.use_hypernetwork_model + }, + use_lora_model: { label: "Lora Model", visible: ({ reqBody }) => !!reqBody?.use_lora_model }, + lora_alpha: { label: "Lora Strength", visible: ({ reqBody }) => !!reqBody?.use_lora_model }, + preserve_init_image_color_profile: "Preserve Color Profile" }, pluginTaskConfig: {}, - getCSSKey: (key) => key.split('_').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join('') + getCSSKey: (key) => + key + .split("_") + .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) + .join("") } let imageCounter = 0 let imageRequest = [] -let promptField = document.querySelector('#prompt') -let promptsFromFileSelector = document.querySelector('#prompt_from_file') -let promptsFromFileBtn = document.querySelector('#promptsFromFileBtn') -let negativePromptField = document.querySelector('#negative_prompt') -let numOutputsTotalField = document.querySelector('#num_outputs_total') -let numOutputsParallelField = document.querySelector('#num_outputs_parallel') -let numInferenceStepsField = document.querySelector('#num_inference_steps') -let guidanceScaleSlider = document.querySelector('#guidance_scale_slider') -let guidanceScaleField = document.querySelector('#guidance_scale') -let outputQualitySlider = document.querySelector('#output_quality_slider') -let outputQualityField = document.querySelector('#output_quality') -let outputQualityRow = document.querySelector('#output_quality_row') +let promptField = document.querySelector("#prompt") +let promptsFromFileSelector = document.querySelector("#prompt_from_file") +let promptsFromFileBtn = document.querySelector("#promptsFromFileBtn") +let negativePromptField = document.querySelector("#negative_prompt") +let numOutputsTotalField = document.querySelector("#num_outputs_total") +let numOutputsParallelField = document.querySelector("#num_outputs_parallel") +let numInferenceStepsField = document.querySelector("#num_inference_steps") +let guidanceScaleSlider = document.querySelector("#guidance_scale_slider") +let guidanceScaleField = document.querySelector("#guidance_scale") +let outputQualitySlider = document.querySelector("#output_quality_slider") +let outputQualityField = document.querySelector("#output_quality") +let outputQualityRow = document.querySelector("#output_quality_row") let randomSeedField = document.querySelector("#random_seed") -let seedField = document.querySelector('#seed') -let widthField = document.querySelector('#width') -let heightField = document.querySelector('#height') -let smallImageWarning = document.querySelector('#small_image_warning') +let seedField = document.querySelector("#seed") +let widthField = document.querySelector("#width") +let heightField = document.querySelector("#height") +let smallImageWarning = document.querySelector("#small_image_warning") let initImageSelector = document.querySelector("#init_image") let initImagePreview = document.querySelector("#init_image_preview") let initImageSizeBox = document.querySelector("#init_image_size_box") let maskImageSelector = document.querySelector("#mask") let maskImagePreview = document.querySelector("#mask_preview") -let applyColorCorrectionField = document.querySelector('#apply_color_correction') -let colorCorrectionSetting = document.querySelector('#apply_color_correction_setting') -let promptStrengthSlider = document.querySelector('#prompt_strength_slider') -let promptStrengthField = document.querySelector('#prompt_strength') -let samplerField = document.querySelector('#sampler_name') +let applyColorCorrectionField = document.querySelector("#apply_color_correction") +let colorCorrectionSetting = document.querySelector("#apply_color_correction_setting") +let promptStrengthSlider = document.querySelector("#prompt_strength_slider") +let promptStrengthField = document.querySelector("#prompt_strength") +let samplerField = document.querySelector("#sampler_name") let samplerSelectionContainer = document.querySelector("#samplerSelection") let useFaceCorrectionField = document.querySelector("#use_face_correction") -let gfpganModelField = new ModelDropdown(document.querySelector("#gfpgan_model"), 'gfpgan') +let gfpganModelField = new ModelDropdown(document.querySelector("#gfpgan_model"), "gfpgan") let useUpscalingField = document.querySelector("#use_upscale") let upscaleModelField = document.querySelector("#upscale_model") let upscaleAmountField = document.querySelector("#upscale_amount") -let stableDiffusionModelField = new ModelDropdown(document.querySelector('#stable_diffusion_model'), 'stable-diffusion') -let vaeModelField = new ModelDropdown(document.querySelector('#vae_model'), 'vae', 'None') -let hypernetworkModelField = new ModelDropdown(document.querySelector('#hypernetwork_model'), 'hypernetwork', 'None') -let hypernetworkStrengthSlider = document.querySelector('#hypernetwork_strength_slider') -let hypernetworkStrengthField = document.querySelector('#hypernetwork_strength') -let loraModelField = new ModelDropdown(document.querySelector('#lora_model'), 'lora', 'None') -let loraAlphaSlider = document.querySelector('#lora_alpha_slider') -let loraAlphaField = document.querySelector('#lora_alpha') -let outputFormatField = document.querySelector('#output_format') -let outputLosslessField = document.querySelector('#output_lossless') -let outputLosslessContainer = document.querySelector('#output_lossless_container') -let blockNSFWField = document.querySelector('#block_nsfw') +let stableDiffusionModelField = new ModelDropdown(document.querySelector("#stable_diffusion_model"), "stable-diffusion") +let vaeModelField = new ModelDropdown(document.querySelector("#vae_model"), "vae", "None") +let hypernetworkModelField = new ModelDropdown(document.querySelector("#hypernetwork_model"), "hypernetwork", "None") +let hypernetworkStrengthSlider = document.querySelector("#hypernetwork_strength_slider") +let hypernetworkStrengthField = document.querySelector("#hypernetwork_strength") +let loraModelField = new ModelDropdown(document.querySelector("#lora_model"), "lora", "None") +let loraAlphaSlider = document.querySelector("#lora_alpha_slider") +let loraAlphaField = document.querySelector("#lora_alpha") +let outputFormatField = document.querySelector("#output_format") +let outputLosslessField = document.querySelector("#output_lossless") +let outputLosslessContainer = document.querySelector("#output_lossless_container") +let blockNSFWField = document.querySelector("#block_nsfw") let showOnlyFilteredImageField = document.querySelector("#show_only_filtered_image") let updateBranchLabel = document.querySelector("#updateBranchLabel") let streamImageProgressField = document.querySelector("#stream_image_progress") @@ -83,16 +100,16 @@ let thumbnailSizeField = document.querySelector("#thumbnail_size-input") let autoscrollBtn = document.querySelector("#auto_scroll_btn") let autoScroll = document.querySelector("#auto_scroll") -let makeImageBtn = document.querySelector('#makeImage') -let stopImageBtn = document.querySelector('#stopImage') -let pauseBtn = document.querySelector('#pause') -let resumeBtn = document.querySelector('#resume') -let renderButtons = document.querySelector('#render-buttons') +let makeImageBtn = document.querySelector("#makeImage") +let stopImageBtn = document.querySelector("#stopImage") +let pauseBtn = document.querySelector("#pause") +let resumeBtn = document.querySelector("#resume") +let renderButtons = document.querySelector("#render-buttons") -let imagesContainer = document.querySelector('#current-images') -let initImagePreviewContainer = document.querySelector('#init_image_preview_container') -let initImageClearBtn = document.querySelector('.init_image_clear') -let promptStrengthContainer = document.querySelector('#prompt_strength_container') +let imagesContainer = document.querySelector("#current-images") +let initImagePreviewContainer = document.querySelector("#init_image_preview_container") +let initImageClearBtn = document.querySelector(".init_image_clear") +let promptStrengthContainer = document.querySelector("#prompt_strength_container") let initialText = document.querySelector("#initial-text") let previewTools = document.querySelector("#preview-tools") @@ -105,9 +122,9 @@ let saveAllTreeToggle = document.querySelector("#tree_toggle") let saveAllJSONToggle = document.querySelector("#json_toggle") let saveAllFoldersOption = document.querySelector("#download-add-folders") -let maskSetting = document.querySelector('#enable_mask') +let maskSetting = document.querySelector("#enable_mask") -const processOrder = document.querySelector('#process_order_toggle') +const processOrder = document.querySelector("#process_order_toggle") let imagePreview = document.querySelector("#preview") let imagePreviewContent = document.querySelector("#preview-content") @@ -116,8 +133,8 @@ let undoButton = document.querySelector("#undo") let undoBuffer = [] const UNDO_LIMIT = 20 -imagePreview.addEventListener('drop', function(ev) { - const data = ev.dataTransfer?.getData("text/plain"); +imagePreview.addEventListener("drop", function(ev) { + const data = ev.dataTransfer?.getData("text/plain") if (!data) { return } @@ -127,7 +144,7 @@ imagePreview.addEventListener('drop', function(ev) { } ev.preventDefault() let moveTarget = ev.target - while (moveTarget && typeof moveTarget === 'object' && moveTarget.parentNode !== imagePreviewContent) { + while (moveTarget && typeof moveTarget === "object" && moveTarget.parentNode !== imagePreviewContent) { moveTarget = moveTarget.parentNode } if (moveTarget === initialText || moveTarget === previewTools) { @@ -157,16 +174,14 @@ imagePreview.addEventListener('drop', function(ev) { } }) - - -let showConfigToggle = document.querySelector('#configToggleBtn') +let showConfigToggle = document.querySelector("#configToggleBtn") // let configBox = document.querySelector('#config') // let outputMsg = document.querySelector('#outputMsg') -let soundToggle = document.querySelector('#sound_toggle') +let soundToggle = document.querySelector("#sound_toggle") -let serverStatusColor = document.querySelector('#server-status-color') -let serverStatusMsg = document.querySelector('#server-status-msg') +let serverStatusColor = document.querySelector("#server-status-color") +let serverStatusMsg = document.querySelector("#server-status-msg") function getLocalStorageBoolItem(key, fallback) { let item = localStorage.getItem(key) @@ -174,7 +189,7 @@ function getLocalStorageBoolItem(key, fallback) { return fallback } - return (item === 'true' ? true : false) + return item === "true" ? true : false } function handleBoolSettingChange(key) { @@ -197,25 +212,24 @@ function getSavedDiskPath() { return getSetting("diskPath") } -function setStatus(statusType, msg, msgType) { -} +function setStatus(statusType, msg, msgType) {} function setServerStatus(event) { - switch(event.type) { - case 'online': - serverStatusColor.style.color = 'var(--status-green)' - serverStatusMsg.style.color = 'var(--status-green)' - serverStatusMsg.innerText = 'Stable Diffusion is ' + event.message + switch (event.type) { + case "online": + serverStatusColor.style.color = "var(--status-green)" + serverStatusMsg.style.color = "var(--status-green)" + serverStatusMsg.innerText = "Stable Diffusion is " + event.message break - case 'busy': - serverStatusColor.style.color = 'var(--status-orange)' - serverStatusMsg.style.color = 'var(--status-orange)' - serverStatusMsg.innerText = 'Stable Diffusion is ' + event.message + case "busy": + serverStatusColor.style.color = "var(--status-orange)" + serverStatusMsg.style.color = "var(--status-orange)" + serverStatusMsg.innerText = "Stable Diffusion is " + event.message break - case 'error': - serverStatusColor.style.color = 'var(--status-red)' - serverStatusMsg.style.color = 'var(--status-red)' - serverStatusMsg.innerText = 'Stable Diffusion has stopped' + case "error": + serverStatusColor.style.color = "var(--status-red)" + serverStatusMsg.style.color = "var(--status-red)" + serverStatusMsg.innerText = "Stable Diffusion has stopped" break } if (SD.serverState.devices) { @@ -229,37 +243,40 @@ function setServerStatus(event) { // 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 +// 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 || !confirmDangerousActionsField.checked) { - fn(e) + fn(e) } else { $.confirm({ - theme: 'modern', + theme: "modern", title: prompt, useBootstrap: false, animateFromElement: false, - content: 'Tip: To skip this dialog, use shift-click or disable the "Confirm dangerous actions" setting in the Settings tab.', + content: + 'Tip: To skip this dialog, use shift-click or disable the "Confirm dangerous actions" setting in the Settings tab.', buttons: { - yes: () => { fn(e) }, + yes: () => { + fn(e) + }, cancel: () => {} } - }); + }) } } function logMsg(msg, level, outputMsg) { if (outputMsg.hasChildNodes()) { - outputMsg.appendChild(document.createElement('br')) + outputMsg.appendChild(document.createElement("br")) } - if (level === 'error') { - outputMsg.innerHTML += 'Error: ' + msg + '' - } else if (level === 'warn') { - outputMsg.innerHTML += 'Warning: ' + msg + '' + if (level === "error") { + outputMsg.innerHTML += 'Error: ' + msg + "" + } else if (level === "warn") { + outputMsg.innerHTML += 'Warning: ' + msg + "" } else { outputMsg.innerText += msg } @@ -267,35 +284,45 @@ function logMsg(msg, level, outputMsg) { } function logError(msg, res, outputMsg) { - logMsg(msg, 'error', outputMsg) + logMsg(msg, "error", outputMsg) - console.log('request error', res) - setStatus('request', 'error', 'error') + console.log("request error", res) + setStatus("request", "error", "error") } function playSound() { - const audio = new Audio('/media/ding.mp3') + const audio = new Audio("/media/ding.mp3") audio.volume = 0.2 var promise = audio.play() if (promise !== undefined) { - promise.then(_ => {}).catch(error => { - console.warn("browser blocked autoplay") - }) + promise + .then((_) => {}) + .catch((error) => { + console.warn("browser blocked autoplay") + }) } } -function undoableRemove(element, doubleUndo=false) { - let data = { 'element': element, 'parent': element.parentNode, 'prev': element.previousSibling, 'next': element.nextSibling, 'doubleUndo': doubleUndo } +function undoableRemove(element, doubleUndo = false) { + let data = { + element: element, + parent: element.parentNode, + prev: element.previousSibling, + next: element.nextSibling, + doubleUndo: doubleUndo + } undoBuffer.push(data) if (undoBuffer.length > UNDO_LIMIT) { // Remove item from memory and also remove it from the data structures let item = undoBuffer.shift() htmlTaskMap.delete(item.element) - item.element.querySelectorAll('[data-imagecounter]').forEach( (img) => { delete imageRequest[img.dataset['imagecounter']] }) + item.element.querySelectorAll("[data-imagecounter]").forEach((img) => { + delete imageRequest[img.dataset["imagecounter"]] + }) } element.remove() if (undoBuffer.length != 0) { - undoButton.classList.remove('displayNone') + undoButton.classList.remove("displayNone") } } @@ -313,42 +340,44 @@ function undoRemove() { undoRemove() } if (undoBuffer.length == 0) { - undoButton.classList.add('displayNone') + undoButton.classList.add("displayNone") } updateInitialText() } -undoButton.addEventListener('click', () => { undoRemove() }) +undoButton.addEventListener("click", () => { + undoRemove() +}) -document.addEventListener('keydown', function(e) { - if ((e.ctrlKey || e.metaKey) && e.key === 'z' && e.target == document.body) { +document.addEventListener("keydown", function(e) { + if ((e.ctrlKey || e.metaKey) && e.key === "z" && e.target == document.body) { undoRemove() } }) function showImages(reqBody, res, outputContainer, livePreview) { - let imageItemElements = outputContainer.querySelectorAll('.imgItem') - if(typeof res != 'object') return + let imageItemElements = outputContainer.querySelectorAll(".imgItem") + if (typeof res != "object") return res.output.reverse() res.output.forEach((result, index) => { - const imageData = result?.data || result?.path + '?t=' + Date.now(), + const imageData = result?.data || result?.path + "?t=" + Date.now(), imageSeed = result?.seed, imagePrompt = reqBody.prompt, imageInferenceSteps = reqBody.num_inference_steps, imageGuidanceScale = reqBody.guidance_scale, imageWidth = reqBody.width, - imageHeight = reqBody.height; + imageHeight = reqBody.height - if (!imageData.includes('/')) { + if (!imageData.includes("/")) { // res contained no data for the image, stop execution - setStatus('request', 'invalid image', 'error') + setStatus("request", "invalid image", "error") return } - let imageItemElem = (index < imageItemElements.length ? imageItemElements[index] : null) - if(!imageItemElem) { - imageItemElem = document.createElement('div') - imageItemElem.className = 'imgItem' + let imageItemElem = index < imageItemElements.length ? imageItemElements[index] : null + if (!imageItemElem) { + imageItemElem = document.createElement("div") + imageItemElem.className = "imgItem" imageItemElem.innerHTML = `
@@ -362,50 +391,52 @@ function showImages(reqBody, res, outputContainer, livePreview) {
` outputContainer.appendChild(imageItemElem) - const imageRemoveBtn = imageItemElem.querySelector('.imgPreviewItemClearBtn') - let parentTaskContainer = imageRemoveBtn.closest('.imageTaskContainer') - imageRemoveBtn.addEventListener('click', (e) => { + const imageRemoveBtn = imageItemElem.querySelector(".imgPreviewItemClearBtn") + let parentTaskContainer = imageRemoveBtn.closest(".imageTaskContainer") + imageRemoveBtn.addEventListener("click", (e) => { undoableRemove(imageItemElem) - let allHidden = true; - let children = parentTaskContainer.querySelectorAll('.imgItem'); - for(let x = 0; x < children.length; x++) { - let child = children[x]; - if(child.style.display != "none") { - allHidden = false; + let allHidden = true + let children = parentTaskContainer.querySelectorAll(".imgItem") + for (let x = 0; x < children.length; x++) { + let child = children[x] + if (child.style.display != "none") { + allHidden = false } } - if(allHidden === true) { + if (allHidden === true) { const req = htmlTaskMap.get(parentTaskContainer) - if(!req.isProcessing || req.batchesDone == req.batchCount) { undoableRemove(parentTaskContainer, true) } + if (!req.isProcessing || req.batchesDone == req.batchCount) { + undoableRemove(parentTaskContainer, true) + } } }) } - const imageElem = imageItemElem.querySelector('img') + const imageElem = imageItemElem.querySelector("img") imageElem.src = imageData imageElem.width = parseInt(imageWidth) imageElem.height = parseInt(imageHeight) - imageElem.setAttribute('data-prompt', imagePrompt) - imageElem.setAttribute('data-steps', imageInferenceSteps) - imageElem.setAttribute('data-guidance', imageGuidanceScale) + imageElem.setAttribute("data-prompt", imagePrompt) + imageElem.setAttribute("data-steps", imageInferenceSteps) + imageElem.setAttribute("data-guidance", imageGuidanceScale) - imageElem.addEventListener('load', function() { - imageItemElem.querySelector('.img_bottom_label').innerText = `${this.naturalWidth} x ${this.naturalHeight}` + imageElem.addEventListener("load", function() { + imageItemElem.querySelector(".img_bottom_label").innerText = `${this.naturalWidth} x ${this.naturalHeight}` }) - const imageInfo = imageItemElem.querySelector('.imgItemInfo') - imageInfo.style.visibility = (livePreview ? 'hidden' : 'visible') + const imageInfo = imageItemElem.querySelector(".imgItemInfo") + imageInfo.style.visibility = livePreview ? "hidden" : "visible" - if ('seed' in result && !imageElem.hasAttribute('data-seed')) { - const imageExpandBtn = imageItemElem.querySelector('.imgExpandBtn') - imageExpandBtn.addEventListener('click', function() { + if ("seed" in result && !imageElem.hasAttribute("data-seed")) { + const imageExpandBtn = imageItemElem.querySelector(".imgExpandBtn") + imageExpandBtn.addEventListener("click", function() { function previousImage(img) { - const allImages = Array.from(outputContainer.parentNode.querySelectorAll('.imgItem 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 allImages = Array.from(outputContainer.parentNode.querySelectorAll(".imgItem img")) const index = allImages.indexOf(img) return allImages.slice(index + 1)[0] } @@ -417,7 +448,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { return { src: img.src, previous: previousImg ? () => imageModalParameter(previousImg) : undefined, - next: nextImg ? () => imageModalParameter(nextImg) : undefined, + next: nextImg ? () => imageModalParameter(nextImg) : undefined } } @@ -427,62 +458,68 @@ function showImages(reqBody, res, outputContainer, livePreview) { const req = Object.assign({}, reqBody, { seed: result?.seed || reqBody.seed }) - imageElem.setAttribute('data-seed', req.seed) - imageElem.setAttribute('data-imagecounter', ++imageCounter) + imageElem.setAttribute("data-seed", req.seed) + imageElem.setAttribute("data-imagecounter", ++imageCounter) imageRequest[imageCounter] = req - const imageSeedLabel = imageItemElem.querySelector('.imgSeedLabel') - imageSeedLabel.innerText = 'Seed: ' + req.seed + const imageSeedLabel = imageItemElem.querySelector(".imgSeedLabel") + imageSeedLabel.innerText = "Seed: " + req.seed let buttons = [ - { text: 'Use as Input', on_click: onUseAsInputClick }, + { text: "Use as Input", on_click: onUseAsInputClick }, [ - { html: ' Download Image', on_click: onDownloadImageClick, class: "download-img" }, - { html: ' JSON', on_click: onDownloadJSONClick, class: "download-json" } + { + html: ' Download Image', + on_click: onDownloadImageClick, + class: "download-img" + }, + { + html: ' JSON', + on_click: onDownloadJSONClick, + class: "download-json" + } ], - { text: 'Make Similar Images', on_click: onMakeSimilarClick }, - { text: 'Draw another 25 steps', on_click: onContinueDrawingClick }, + { text: "Make Similar Images", on_click: onMakeSimilarClick }, + { text: "Draw another 25 steps", on_click: onContinueDrawingClick }, [ - { text: 'Upscale', on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale }, - { text: 'Fix Faces', on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction } + { text: "Upscale", on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale }, + { text: "Fix Faces", on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction } ] ] // include the plugins - buttons = buttons.concat(PLUGINS['IMAGE_INFO_BUTTONS']) + buttons = buttons.concat(PLUGINS["IMAGE_INFO_BUTTONS"]) - const imgItemInfo = imageItemElem.querySelector('.imgItemInfo') - const img = imageItemElem.querySelector('img') + const imgItemInfo = imageItemElem.querySelector(".imgItemInfo") + const img = imageItemElem.querySelector("img") const createButton = function(btnInfo) { if (Array.isArray(btnInfo)) { - const wrapper = document.createElement('div'); - btnInfo - .map(createButton) - .forEach(buttonElement => wrapper.appendChild(buttonElement)) + const wrapper = document.createElement("div") + btnInfo.map(createButton).forEach((buttonElement) => wrapper.appendChild(buttonElement)) return wrapper } - const isLabel = btnInfo.type === 'label' + const isLabel = btnInfo.type === "label" - const newButton = document.createElement(isLabel ? 'span' : 'button') - newButton.classList.add('tasksBtns') + const newButton = document.createElement(isLabel ? "span" : "button") + newButton.classList.add("tasksBtns") if (btnInfo.html) { - const html = typeof btnInfo.html === 'function' ? btnInfo.html() : btnInfo.html + const html = typeof btnInfo.html === "function" ? btnInfo.html() : btnInfo.html if (html instanceof HTMLElement) { newButton.appendChild(html) } else { newButton.innerHTML = html } } else { - newButton.innerText = typeof btnInfo.text === 'function' ? btnInfo.text() : btnInfo.text + newButton.innerText = typeof btnInfo.text === "function" ? btnInfo.text() : btnInfo.text } if (btnInfo.on_click || !isLabel) { - newButton.addEventListener('click', function(event) { + newButton.addEventListener("click", function(event) { btnInfo.on_click(req, img, event) }) } - + if (btnInfo.class !== undefined) { if (Array.isArray(btnInfo.class)) { newButton.classList.add(...btnInfo.class) @@ -492,9 +529,9 @@ function showImages(reqBody, res, outputContainer, livePreview) { } return newButton } - buttons.forEach(btn => { + buttons.forEach((btn) => { if (Array.isArray(btn)) { - btn = btn.filter(btnInfo => !btnInfo.filter || btnInfo.filter(req, img) === true) + btn = btn.filter((btnInfo) => !btnInfo.filter || btnInfo.filter(req, img) === true) if (btn.length === 0) { return } @@ -505,7 +542,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { try { imgItemInfo.appendChild(createButton(btn)) } catch (err) { - console.error('Error creating image info button from plugin: ', btn, err) + console.error("Error creating image info button from plugin: ", btn, err) } }) } @@ -522,22 +559,22 @@ function onUseAsInputClick(req, img) { } function getDownloadFilename(img, suffix) { - const imageSeed = img.getAttribute('data-seed') - const imagePrompt = img.getAttribute('data-prompt') - const imageInferenceSteps = img.getAttribute('data-steps') - const imageGuidanceScale = img.getAttribute('data-guidance') - + const imageSeed = img.getAttribute("data-seed") + const imagePrompt = img.getAttribute("data-prompt") + const imageInferenceSteps = img.getAttribute("data-steps") + const imageGuidanceScale = img.getAttribute("data-guidance") + return createFileName(imagePrompt, imageSeed, imageInferenceSteps, imageGuidanceScale, suffix) } function onDownloadJSONClick(req, img) { - const name = getDownloadFilename(img, 'json') - const blob = new Blob([JSON.stringify(req, null, 2)], { type: 'text/plain' }) + const name = getDownloadFilename(img, "json") + const blob = new Blob([JSON.stringify(req, null, 2)], { type: "text/plain" }) saveAs(blob, name) } function onDownloadImageClick(req, img) { - const name = getDownloadFilename(img, req['output_format']) + const name = getDownloadFilename(img, req["output_format"]) const blob = dataURItoBlob(img.src) saveAs(blob, name) } @@ -572,7 +609,7 @@ function onMakeSimilarClick(req, img) { } function enqueueImageVariationTask(req, img, reqDiff) { - const imageSeed = img.getAttribute('data-seed') + const imageSeed = img.getAttribute("data-seed") const newRequestBody = { num_outputs: 1, // this can be user-configurable in the future @@ -581,10 +618,10 @@ function enqueueImageVariationTask(req, img, reqDiff) { // If the user is editing pictures, stop modifyCurrentRequest from importing // new values by setting the missing properties to undefined - if (!('init_image' in req) && !('init_image' in reqDiff)) { + if (!("init_image" in req) && !("init_image" in reqDiff)) { newRequestBody.init_image = undefined newRequestBody.mask = undefined - } else if (!('mask' in req) && !('mask' in reqDiff)) { + } else if (!("mask" in req) && !("mask" in reqDiff)) { newRequestBody.mask = undefined } @@ -614,12 +651,11 @@ function onContinueDrawingClick(req, img) { } function getUncompletedTaskEntries() { - const taskEntries = Array.from( - document.querySelectorAll('#preview .imageTaskContainer .taskStatusLabel') - ).filter((taskLabel) => taskLabel.style.display !== 'none' - ).map(function(taskLabel) { + const taskEntries = Array.from(document.querySelectorAll("#preview .imageTaskContainer .taskStatusLabel")) + .filter((taskLabel) => taskLabel.style.display !== "none") + .map(function(taskLabel) { let imageTaskContainer = taskLabel.parentNode - while(!imageTaskContainer.classList.contains('imageTaskContainer') && imageTaskContainer.parentNode) { + while (!imageTaskContainer.classList.contains("imageTaskContainer") && imageTaskContainer.parentNode) { imageTaskContainer = imageTaskContainer.parentNode } return imageTaskContainer @@ -632,34 +668,36 @@ function getUncompletedTaskEntries() { function makeImage() { if (typeof performance == "object" && performance.mark) { - performance.mark('click-makeImage') + performance.mark("click-makeImage") } if (!SD.isServerAvailable()) { - alert('The server is not available.') + alert("The server is not available.") return } - if (!randomSeedField.checked && seedField.value == '') { + if (!randomSeedField.checked && seedField.value == "") { alert('The "Seed" field must not be empty.') return } - if (numInferenceStepsField.value == '') { + if (numInferenceStepsField.value == "") { alert('The "Inference Steps" field must not be empty.') return } - if (numOutputsTotalField.value == '' || numOutputsTotalField.value == 0) { + if (numOutputsTotalField.value == "" || numOutputsTotalField.value == 0) { numOutputsTotalField.value = 1 } - if (numOutputsParallelField.value == '' || numOutputsParallelField.value == 0) { + if (numOutputsParallelField.value == "" || numOutputsParallelField.value == 0) { numOutputsParallelField.value = 1 } - if (guidanceScaleField.value == '') { + if (guidanceScaleField.value == "") { guidanceScaleField.value = guidanceScaleSlider.value / 10 } const taskTemplate = getCurrentUserRequest() - const newTaskRequests = getPrompts().map((prompt) => Object.assign({}, taskTemplate, { - reqBody: Object.assign({ prompt: prompt }, taskTemplate.reqBody) - })) + const newTaskRequests = getPrompts().map((prompt) => + Object.assign({}, taskTemplate, { + reqBody: Object.assign({ prompt: prompt }, taskTemplate.reqBody) + }) + ) newTaskRequests.forEach(createTask) updateInitialText() @@ -667,7 +705,7 @@ function makeImage() { async function onIdle() { const serverCapacity = SD.serverCapacity - if (pauseClient===true) { + if (pauseClient === true) { await resumeClient() } @@ -677,8 +715,8 @@ async function onIdle() { } const task = htmlTaskMap.get(taskEntry) if (!task) { - const taskStatusLabel = taskEntry.querySelector('.taskStatusLabel') - taskStatusLabel.style.display = 'none' + const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") + taskStatusLabel.style.display = "none" continue } await onTaskStart(task) @@ -686,8 +724,8 @@ async function onIdle() { } function getTaskUpdater(task, reqBody, outputContainer) { - const outputMsg = task['outputMsg'] - const progressBar = task['progressBar'] + const outputMsg = task["outputMsg"] + const progressBar = task["progressBar"] const progressBarInner = progressBar.querySelector("div") const batchCount = task.batchCount @@ -695,60 +733,73 @@ function getTaskUpdater(task, reqBody, outputContainer) { return async function(event) { if (this.status !== lastStatus) { lastStatus = this.status - switch(this.status) { + switch (this.status) { case SD.TaskStatus.pending: - task['taskStatusLabel'].innerText = "Pending" - task['taskStatusLabel'].classList.add('waitingTaskLabel') + task["taskStatusLabel"].innerText = "Pending" + task["taskStatusLabel"].classList.add("waitingTaskLabel") break case SD.TaskStatus.waiting: - task['taskStatusLabel'].innerText = "Waiting" - task['taskStatusLabel'].classList.add('waitingTaskLabel') - task['taskStatusLabel'].classList.remove('activeTaskLabel') + task["taskStatusLabel"].innerText = "Waiting" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + task["taskStatusLabel"].classList.remove("activeTaskLabel") break case SD.TaskStatus.processing: case SD.TaskStatus.completed: - task['taskStatusLabel'].innerText = "Processing" - task['taskStatusLabel'].classList.add('activeTaskLabel') - task['taskStatusLabel'].classList.remove('waitingTaskLabel') + task["taskStatusLabel"].innerText = "Processing" + task["taskStatusLabel"].classList.add("activeTaskLabel") + task["taskStatusLabel"].classList.remove("waitingTaskLabel") break case SD.TaskStatus.stopped: break case SD.TaskStatus.failed: if (!SD.isServerAvailable()) { - logError("Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.", event, outputMsg) - } else if (typeof event?.response === 'object') { - let msg = 'Stable Diffusion had an error reading the response:
'
+                        logError(
+                            "Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.",
+                            event,
+                            outputMsg
+                        )
+                    } else if (typeof event?.response === "object") {
+                        let msg = "Stable Diffusion had an error reading the response:
"
                         if (this.exception) {
                             msg += `Error: ${this.exception.message}
` } - try { // 'Response': body stream already read - msg += 'Read: ' + await event.response.text() - } catch(e) { - msg += 'Unexpected end of stream. ' + try { + // 'Response': body stream already read + msg += "Read: " + (await event.response.text()) + } catch (e) { + msg += "Unexpected end of stream. " } const bufferString = event.reader.bufferedString if (bufferString) { - msg += 'Buffered data: ' + bufferString + msg += "Buffered data: " + bufferString } - msg += '
' + msg += "
" logError(msg, event, outputMsg) } else { - let msg = `Unexpected Read Error:
Error:${this.exception}
EventInfo: ${JSON.stringify(event, undefined, 4)}
` + let msg = `Unexpected Read Error:
Error:${
+                            this.exception
+                        }
EventInfo: ${JSON.stringify(event, undefined, 4)}
` logError(msg, event, outputMsg) } break } } - if ('update' in event) { + if ("update" in event) { const stepUpdate = event.update - if (!('step' in stepUpdate)) { + if (!("step" in stepUpdate)) { return } // task.instances can be a mix of different tasks with uneven number of steps (Render Vs Filter Tasks) - const overallStepCount = task.instances.reduce( - (sum, instance) => sum + (instance.isPending ? Math.max(0, instance.step || stepUpdate.step) / (instance.total_steps || stepUpdate.total_steps) : 1), - 0 // Initial value - ) * stepUpdate.total_steps // Scale to current number of steps. + const overallStepCount = + task.instances.reduce( + (sum, instance) => + sum + + (instance.isPending + ? Math.max(0, instance.step || stepUpdate.step) / + (instance.total_steps || stepUpdate.total_steps) + : 1), + 0 // Initial value + ) * stepUpdate.total_steps // Scale to current number of steps. const totalSteps = task.instances.reduce( (sum, instance) => sum + (instance.total_steps || stepUpdate.total_steps), stepUpdate.total_steps * (batchCount - task.batchesDone) // Initial value at (unstarted task count * Nbr of steps) @@ -757,9 +808,9 @@ function getTaskUpdater(task, reqBody, outputContainer) { const timeTaken = stepUpdate.step_time // sec const stepsRemaining = Math.max(0, totalSteps - overallStepCount) - const timeRemaining = (timeTaken < 0 ? '' : millisecondsToStr(stepsRemaining * timeTaken * 1000)) + const timeRemaining = timeTaken < 0 ? "" : millisecondsToStr(stepsRemaining * timeTaken * 1000) outputMsg.innerHTML = `Batch ${task.batchesDone} of ${batchCount}. Generating image(s): ${percent}%. Time remaining (approx): ${timeRemaining}` - outputMsg.style.display = 'block' + outputMsg.style.display = "block" progressBarInner.style.width = `${percent}%` if (stepUpdate.output) { @@ -775,8 +826,8 @@ function abortTask(task) { } task.isProcessing = false task.progressBar.classList.remove("active") - task['taskStatusLabel'].style.display = 'none' - task['stopTask'].innerHTML = ' Remove' + task["taskStatusLabel"].style.display = "none" + task["stopTask"].innerHTML = ' Remove' if (!task.instances?.some((r) => r.isPending)) { return } @@ -793,24 +844,32 @@ function onTaskErrorHandler(task, reqBody, instance, reason) { if (!task.isProcessing) { return } - console.log('Render request %o, Instance: %o, Error: %s', reqBody, instance, reason) + console.log("Render request %o, Instance: %o, Error: %s", reqBody, instance, reason) abortTask(task) - const outputMsg = task['outputMsg'] - logError('Stable Diffusion had an error. Please check the logs in the command-line window.

' + reason + '
' + reason.stack + '
', task, outputMsg) - setStatus('request', 'error', 'error') + const outputMsg = task["outputMsg"] + logError( + "Stable Diffusion had an error. Please check the logs in the command-line window.

" + + reason + + "
" +
+            reason.stack +
+            "
", + task, + outputMsg + ) + setStatus("request", "error", "error") } function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { - if (typeof stepUpdate === 'object') { - if (stepUpdate.status === 'succeeded') { + if (typeof stepUpdate === "object") { + if (stepUpdate.status === "succeeded") { showImages(reqBody, stepUpdate, outputContainer, false) } else { task.isProcessing = false - const outputMsg = task['outputMsg'] - let msg = '' - if ('detail' in stepUpdate && typeof stepUpdate.detail === 'string' && stepUpdate.detail.length > 0) { + const outputMsg = task["outputMsg"] + let msg = "" + if ("detail" in stepUpdate && typeof stepUpdate.detail === "string" && stepUpdate.detail.length > 0) { msg = stepUpdate.detail - if (msg.toLowerCase().includes('out of memory')) { + if (msg.toLowerCase().includes("out of memory")) { msg += `

Suggestions:
@@ -825,29 +884,29 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { } } if (task.isProcessing && task.batchesDone < task.batchCount) { - task['taskStatusLabel'].innerText = "Pending" - task['taskStatusLabel'].classList.add('waitingTaskLabel') - task['taskStatusLabel'].classList.remove('activeTaskLabel') + task["taskStatusLabel"].innerText = "Pending" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + task["taskStatusLabel"].classList.remove("activeTaskLabel") return } - if ('instances' in task && task.instances.some((ins) => ins != instance && ins.isPending)) { + if ("instances" in task && task.instances.some((ins) => ins != instance && ins.isPending)) { return } task.isProcessing = false - task['stopTask'].innerHTML = ' Remove' - task['taskStatusLabel'].style.display = 'none' + task["stopTask"].innerHTML = ' Remove' + task["taskStatusLabel"].style.display = "none" - let time = millisecondsToStr( Date.now() - task.startTime ) + let time = millisecondsToStr(Date.now() - task.startTime) if (task.batchesDone == task.batchCount) { - if (!task.outputMsg.innerText.toLowerCase().includes('error')) { + if (!task.outputMsg.innerText.toLowerCase().includes("error")) { task.outputMsg.innerText = `Processed ${task.numOutputsTotal} images in ${time}` } task.progressBar.style.height = "0px" task.progressBar.style.border = "0px solid var(--background-color3)" task.progressBar.classList.remove("active") - setStatus('request', 'done', 'success') + setStatus("request", "done", "success") } else { task.outputMsg.innerText += `. Task ended after ${time}` } @@ -864,10 +923,10 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { return } - if (pauseClient) { - resumeBtn.click() + if (pauseClient) { + resumeBtn.click() } - renderButtons.style.display = 'none' + renderButtons.style.display = "none" renameMakeImageButton() if (isSoundEnabled()) { @@ -875,39 +934,40 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { } } - async function onTaskStart(task) { if (!task.isProcessing || task.batchesDone >= task.batchCount) { return } - if (typeof task.startTime !== 'number') { + if (typeof task.startTime !== "number") { task.startTime = Date.now() } - if (!('instances' in task)) { - task['instances'] = [] + if (!("instances" in task)) { + task["instances"] = [] } - task['stopTask'].innerHTML = ' Stop' - task['taskStatusLabel'].innerText = "Starting" - task['taskStatusLabel'].classList.add('waitingTaskLabel') + task["stopTask"].innerHTML = ' Stop' + task["taskStatusLabel"].innerText = "Starting" + task["taskStatusLabel"].classList.add("waitingTaskLabel") let newTaskReqBody = task.reqBody if (task.batchCount > 1) { // Each output render batch needs it's own task reqBody instance to avoid altering the other runs after they are completed. newTaskReqBody = Object.assign({}, task.reqBody) - if (task.batchesDone == task.batchCount-1) { + if (task.batchesDone == task.batchCount - 1) { // Last batch of the task // If the number of parallel jobs is no factor of the total number of images, the last batch must create less than "parallel jobs count" images // E.g. with numOutputsTotal = 6 and num_outputs = 5, the last batch shall only generate 1 image. - newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount-1) + newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount - 1) } } const startSeed = task.seed || newTaskReqBody.seed - const genSeeds = Boolean(typeof newTaskReqBody.seed !== 'number' || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1)) + const genSeeds = Boolean( + typeof newTaskReqBody.seed !== "number" || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1) + ) if (genSeeds) { - newTaskReqBody.seed = parseInt(startSeed) + (task.batchesDone * task.reqBody.num_outputs) + newTaskReqBody.seed = parseInt(startSeed) + task.batchesDone * task.reqBody.num_outputs } // Update the seed *before* starting the processing so it's retained if user stops the task @@ -915,15 +975,15 @@ async function onTaskStart(task) { seedField.value = task.seed } - const outputContainer = document.createElement('div') - outputContainer.className = 'img-batch' + const outputContainer = document.createElement("div") + outputContainer.className = "img-batch" task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild) - const eventInfo = {reqBody:newTaskReqBody} - const callbacksPromises = PLUGINS['TASK_CREATE'].map((hook) => { - if (typeof hook !== 'function') { - console.error('The provided TASK_CREATE hook is not a function. Hook: %o', hook) - return Promise.reject(new Error('hook is not a function.')) + const eventInfo = { reqBody: newTaskReqBody } + const callbacksPromises = PLUGINS["TASK_CREATE"].map((hook) => { + if (typeof hook !== "function") { + console.error("The provided TASK_CREATE hook is not a function. Hook: %o", hook) + return Promise.reject(new Error("hook is not a function.")) } try { return Promise.resolve(hook.call(task, eventInfo)) @@ -940,12 +1000,16 @@ async function onTaskStart(task) { instance = await Promise.resolve(factory(eventInfo.reqBody || newTaskReqBody)) } if (!instance) { - console.error(`${factory ? "Factory " + String(factory) : 'No factory defined'} for output format ${eventInfo.reqBody?.output_format || newTaskReqBody.output_format}. Instance is ${instance || 'undefined'}. Using default renderer.`) + console.error( + `${factory ? "Factory " + String(factory) : "No factory defined"} for output format ${eventInfo.reqBody + ?.output_format || newTaskReqBody.output_format}. Instance is ${instance || + "undefined"}. Using default renderer.` + ) instance = new SD.RenderTask(eventInfo.reqBody || newTaskReqBody) } } - task['instances'].push(instance) + task["instances"].push(instance) task.batchesDone++ instance.enqueue(getTaskUpdater(task, newTaskReqBody, outputContainer)).then( @@ -957,49 +1021,49 @@ async function onTaskStart(task) { } ) - setStatus('request', 'fetching..') - renderButtons.style.display = 'flex' + setStatus("request", "fetching..") + renderButtons.style.display = "flex" renameMakeImageButton() updateInitialText() } /* Hover effect for the init image in the task list */ function createInitImageHover(taskEntry) { - var $tooltip = $( taskEntry.querySelector('.task-fs-initimage') ) - var img = document.createElement('img') - img.src = taskEntry.querySelector('div.task-initimg > img').src + var $tooltip = $(taskEntry.querySelector(".task-fs-initimage")) + var img = document.createElement("img") + img.src = taskEntry.querySelector("div.task-initimg > img").src $tooltip.append(img) $tooltip.append(`
`) - $tooltip.find('button').on('click', (e) => { + $tooltip.find("button").on("click", (e) => { e.stopPropagation() - onUseAsInputClick(null,img) + onUseAsInputClick(null, img) }) } -let startX, startY; +let startX, startY function onTaskEntryDragOver(event) { - imagePreview.querySelectorAll(".imageTaskContainer").forEach(itc => { - if(itc != event.target.closest(".imageTaskContainer")){ - itc.classList.remove('dropTargetBefore','dropTargetAfter'); + imagePreview.querySelectorAll(".imageTaskContainer").forEach((itc) => { + if (itc != event.target.closest(".imageTaskContainer")) { + itc.classList.remove("dropTargetBefore", "dropTargetAfter") } - }); - if(event.target.closest(".imageTaskContainer")){ - if(startX && startY){ - if(event.target.closest(".imageTaskContainer").offsetTop > startY){ - event.target.closest(".imageTaskContainer").classList.add('dropTargetAfter'); - }else if(event.target.closest(".imageTaskContainer").offsetTop < startY){ - event.target.closest(".imageTaskContainer").classList.add('dropTargetBefore'); - }else if (event.target.closest(".imageTaskContainer").offsetLeft > startX){ - event.target.closest(".imageTaskContainer").classList.add('dropTargetAfter'); - }else if (event.target.closest(".imageTaskContainer").offsetLeft < startX){ - event.target.closest(".imageTaskContainer").classList.add('dropTargetBefore'); + }) + if (event.target.closest(".imageTaskContainer")) { + if (startX && startY) { + if (event.target.closest(".imageTaskContainer").offsetTop > startY) { + event.target.closest(".imageTaskContainer").classList.add("dropTargetAfter") + } else if (event.target.closest(".imageTaskContainer").offsetTop < startY) { + event.target.closest(".imageTaskContainer").classList.add("dropTargetBefore") + } else if (event.target.closest(".imageTaskContainer").offsetLeft > startX) { + event.target.closest(".imageTaskContainer").classList.add("dropTargetAfter") + } else if (event.target.closest(".imageTaskContainer").offsetLeft < startX) { + event.target.closest(".imageTaskContainer").classList.add("dropTargetBefore") } } } } function generateConfig({ label, value, visible, cssKey }) { - if (!visible) return null; + if (!visible) return null return `
${label}: ${value}` } @@ -1014,27 +1078,27 @@ function getVisibleConfig(config, task) { return { label, visible, value, cssKey } }) .map((obj) => generateConfig(obj)) - .filter(obj => obj) + .filter((obj) => obj) } function createTaskConfig(task) { - return getVisibleConfig(taskConfigSetup, task).join('
') + return getVisibleConfig(taskConfigSetup, task).join(", 
") } function createTask(task) { - let taskConfig = '' + let taskConfig = "" if (task.reqBody.init_image !== undefined) { let h = 80 - let w = task.reqBody.width * h / task.reqBody.height >>0 + let w = ((task.reqBody.width * h) / task.reqBody.height) >> 0 taskConfig += `
` } - taskConfig += `
${createTaskConfig(task)}
`; + taskConfig += `
${createTaskConfig(task)}
` - let taskEntry = document.createElement('div') + let taskEntry = document.createElement("div") taskEntry.id = `imageTaskContainer-${Date.now()}` - taskEntry.className = 'imageTaskContainer' + taskEntry.className = "imageTaskContainer" taskEntry.innerHTML = `
Enqueued
@@ -1051,46 +1115,49 @@ function createTask(task) { createCollapsibles(taskEntry) - let draghandle = taskEntry.querySelector('.drag-handle') - draghandle.addEventListener('mousedown', (e) => { - taskEntry.setAttribute('draggable', true) + let draghandle = taskEntry.querySelector(".drag-handle") + draghandle.addEventListener("mousedown", (e) => { + taskEntry.setAttribute("draggable", true) }) // Add a debounce delay to allow mobile to bouble tap. - draghandle.addEventListener('mouseup', debounce((e) => { - taskEntry.setAttribute('draggable', false) - }, 2000)) - draghandle.addEventListener('click', (e) => { + draghandle.addEventListener( + "mouseup", + debounce((e) => { + taskEntry.setAttribute("draggable", false) + }, 2000) + ) + draghandle.addEventListener("click", (e) => { e.preventDefault() // Don't allow the results to be collapsed... }) - taskEntry.addEventListener('dragend', (e) => { - taskEntry.setAttribute('draggable', false); - imagePreview.querySelectorAll(".imageTaskContainer").forEach(itc => { - itc.classList.remove('dropTargetBefore','dropTargetAfter'); - }); - imagePreview.removeEventListener("dragover", onTaskEntryDragOver ); + taskEntry.addEventListener("dragend", (e) => { + taskEntry.setAttribute("draggable", false) + imagePreview.querySelectorAll(".imageTaskContainer").forEach((itc) => { + itc.classList.remove("dropTargetBefore", "dropTargetAfter") + }) + imagePreview.removeEventListener("dragover", onTaskEntryDragOver) }) - taskEntry.addEventListener('dragstart', function(e) { - imagePreview.addEventListener("dragover", onTaskEntryDragOver ); - e.dataTransfer.setData("text/plain", taskEntry.id); - startX = e.target.closest(".imageTaskContainer").offsetLeft; - startY = e.target.closest(".imageTaskContainer").offsetTop; + taskEntry.addEventListener("dragstart", function(e) { + imagePreview.addEventListener("dragover", onTaskEntryDragOver) + e.dataTransfer.setData("text/plain", taskEntry.id) + startX = e.target.closest(".imageTaskContainer").offsetLeft + startY = e.target.closest(".imageTaskContainer").offsetTop }) if (task.reqBody.init_image !== undefined) { createInitImageHover(taskEntry) } - task['taskStatusLabel'] = taskEntry.querySelector('.taskStatusLabel') - task['outputContainer'] = taskEntry.querySelector('.img-preview') - task['outputMsg'] = taskEntry.querySelector('.outputMsg') - task['previewPrompt'] = taskEntry.querySelector('.preview-prompt') - task['progressBar'] = taskEntry.querySelector('.progress-bar') - task['stopTask'] = taskEntry.querySelector('.stopTask') + task["taskStatusLabel"] = taskEntry.querySelector(".taskStatusLabel") + task["outputContainer"] = taskEntry.querySelector(".img-preview") + task["outputMsg"] = taskEntry.querySelector(".outputMsg") + task["previewPrompt"] = taskEntry.querySelector(".preview-prompt") + task["progressBar"] = taskEntry.querySelector(".progress-bar") + task["stopTask"] = taskEntry.querySelector(".stopTask") - task['stopTask'].addEventListener('click', (e) => { + task["stopTask"].addEventListener("click", (e) => { e.stopPropagation() - if (task['isProcessing']) { + if (task["isProcessing"]) { shiftOrConfirm(e, "Stop this task?", async function(e) { if (task.batchesDone <= 0 || !task.isProcessing) { removeTask(taskEntry) @@ -1102,8 +1169,8 @@ function createTask(task) { } }) - task['useSettings'] = taskEntry.querySelector('.useSettings') - task['useSettings'].addEventListener('click', function(e) { + task["useSettings"] = taskEntry.querySelector(".useSettings") + task["useSettings"].addEventListener("click", function(e) { e.stopPropagation() restoreTaskToUI(task, TASK_REQ_NO_EXPORT) }) @@ -1113,8 +1180,8 @@ function createTask(task) { htmlTaskMap.set(taskEntry, task) task.previewPrompt.innerText = task.reqBody.prompt - if (task.previewPrompt.innerText.trim() === '') { - task.previewPrompt.innerHTML = ' ' // allows the results to be collapsed + if (task.previewPrompt.innerText.trim() === "") { + task.previewPrompt.innerHTML = " " // allows the results to be collapsed } return taskEntry.id } @@ -1122,7 +1189,7 @@ function createTask(task) { function getCurrentUserRequest() { const numOutputsTotal = parseInt(numOutputsTotalField.value) const numOutputsParallel = parseInt(numOutputsParallelField.value) - const seed = (randomSeedField.checked ? Math.floor(Math.random() * (2**32 - 1)) : parseInt(seedField.value)) + const seed = randomSeedField.checked ? Math.floor(Math.random() * (2 ** 32 - 1)) : parseInt(seedField.value) const newTask = { batchesDone: 0, @@ -1145,7 +1212,7 @@ function getCurrentUserRequest() { use_stable_diffusion_model: stableDiffusionModelField.value, use_vae_model: vaeModelField.value, stream_progress_updates: true, - stream_image_progress: (numOutputsTotal > 50 ? false : streamImageProgressField.checked), + stream_image_progress: numOutputsTotal > 50 ? false : streamImageProgressField.checked, show_only_filtered_image: showOnlyFilteredImageField.checked, block_nsfw: blockNSFWField.checked, output_format: outputFormatField.value, @@ -1153,8 +1220,8 @@ function getCurrentUserRequest() { output_lossless: outputLosslessField.checked, metadata_output_format: metadataOutputFormatField.value, original_prompt: promptField.value, - active_tags: (activeTags.map(x => x.name)), - inactive_tags: (activeTags.filter(tag => tag.inactive === true).map(x => x.name)) + active_tags: activeTags.map((x) => x.name), + inactive_tags: activeTags.filter((tag) => tag.inactive === true).map((x) => x.name) } } if (IMAGE_REGEX.test(initImagePreview.src)) { @@ -1168,10 +1235,10 @@ function getCurrentUserRequest() { } newTask.reqBody.preserve_init_image_color_profile = applyColorCorrectionField.checked if (!testDiffusers.checked) { - newTask.reqBody.sampler_name = 'ddim' + newTask.reqBody.sampler_name = "ddim" } } - if (saveToDiskField.checked && diskPathField.value.trim() !== '') { + if (saveToDiskField.checked && diskPathField.value.trim() !== "") { newTask.reqBody.save_to_disk_path = diskPathField.value.trim() } if (useFaceCorrectionField.checked) { @@ -1193,30 +1260,28 @@ function getCurrentUserRequest() { } function getPrompts(prompts) { - if (typeof prompts === 'undefined') { + if (typeof prompts === "undefined") { prompts = promptField.value } - if (prompts.trim() === '' && activeTags.length === 0) { - return [''] + if (prompts.trim() === "" && activeTags.length === 0) { + return [""] } let promptsToMake = [] - if (prompts.trim() !== '') { - prompts = prompts.split('\n') - prompts = prompts.map(prompt => prompt.trim()) - prompts = prompts.filter(prompt => prompt !== '') - + if (prompts.trim() !== "") { + prompts = prompts.split("\n") + prompts = prompts.map((prompt) => prompt.trim()) + prompts = prompts.filter((prompt) => prompt !== "") + promptsToMake = applyPermuteOperator(prompts) promptsToMake = applySetOperator(promptsToMake) } - const newTags = activeTags.filter(tag => tag.inactive === undefined || tag.inactive === false) + const newTags = activeTags.filter((tag) => tag.inactive === undefined || tag.inactive === false) if (newTags.length > 0) { - const promptTags = newTags.map(x => x.name).join(", ") + const promptTags = newTags.map((x) => x.name).join(", ") if (promptsToMake.length > 0) { promptsToMake = promptsToMake.map((prompt) => `${prompt}, ${promptTags}`) - } - else - { + } else { promptsToMake.push(promptTags) } } @@ -1224,7 +1289,9 @@ function getPrompts(prompts) { promptsToMake = applyPermuteOperator(promptsToMake) promptsToMake = applySetOperator(promptsToMake) - PLUGINS['GET_PROMPTS_HOOK'].forEach(fn => { promptsToMake = fn(promptsToMake) }) + PLUGINS["GET_PROMPTS_HOOK"].forEach((fn) => { + promptsToMake = fn(promptsToMake) + }) return promptsToMake } @@ -1232,7 +1299,7 @@ function getPrompts(prompts) { function applySetOperator(prompts) { let promptsToMake = [] let braceExpander = new BraceExpander() - prompts.forEach(prompt => { + prompts.forEach((prompt) => { let expandedPrompts = braceExpander.expand(prompt) promptsToMake = promptsToMake.concat(expandedPrompts) }) @@ -1242,13 +1309,13 @@ function applySetOperator(prompts) { function applyPermuteOperator(prompts) { let promptsToMake = [] - prompts.forEach(prompt => { - let promptMatrix = prompt.split('|') + prompts.forEach((prompt) => { + let promptMatrix = prompt.split("|") prompt = promptMatrix.shift().trim() promptsToMake.push(prompt) - promptMatrix = promptMatrix.map(p => p.trim()) - promptMatrix = promptMatrix.filter(p => p !== '') + promptMatrix = promptMatrix.map((p) => p.trim()) + promptMatrix = promptMatrix.filter((p) => p !== "") if (promptMatrix.length > 0) { let promptPermutations = permutePrompts(prompt, promptMatrix) @@ -1262,16 +1329,16 @@ function applyPermuteOperator(prompts) { function permutePrompts(promptBase, promptMatrix) { let prompts = [] let permutations = permute(promptMatrix) - permutations.forEach(perm => { + permutations.forEach((perm) => { let prompt = promptBase if (perm.length > 0) { - let promptAddition = perm.join(', ') - if (promptAddition.trim() === '') { + let promptAddition = perm.join(", ") + if (promptAddition.trim() === "") { return } - prompt += ', ' + promptAddition + prompt += ", " + promptAddition } prompts.push(prompt) @@ -1283,9 +1350,8 @@ function permutePrompts(promptBase, promptMatrix) { // create a file name with embedded prompt and metadata // for easier cateloging and comparison function createFileName(prompt, seed, steps, guidance, outputFormat) { - // Most important information is the prompt - let underscoreName = prompt.replace(/[^a-zA-Z0-9]/g, '_') + let underscoreName = prompt.replace(/[^a-zA-Z0-9]/g, "_") underscoreName = underscoreName.substring(0, 70) // name and the top level metadata @@ -1296,9 +1362,9 @@ function createFileName(prompt, seed, steps, guidance, outputFormat) { async function stopAllTasks() { getUncompletedTaskEntries().forEach((taskEntry) => { - const taskStatusLabel = taskEntry.querySelector('.taskStatusLabel') + const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") if (taskStatusLabel) { - taskStatusLabel.style.display = 'none' + taskStatusLabel.style.display = "none" } const task = htmlTaskMap.get(taskEntry) if (!task) { @@ -1309,16 +1375,16 @@ async function stopAllTasks() { } function updateInitialText() { - if (document.querySelector('.imageTaskContainer') === null) { + if (document.querySelector(".imageTaskContainer") === null) { if (undoBuffer.length > 0) { initialText.prepend(undoButton) } - previewTools.classList.add('displayNone') - initialText.classList.remove('displayNone') + previewTools.classList.add("displayNone") + initialText.classList.remove("displayNone") } else { - initialText.classList.add('displayNone') - previewTools.classList.remove('displayNone') - document.querySelector('div.display-settings').prepend(undoButton) + initialText.classList.add("displayNone") + previewTools.classList.remove("displayNone") + document.querySelector("div.display-settings").prepend(undoButton) } } @@ -1327,30 +1393,37 @@ function removeTask(taskToRemove) { updateInitialText() } -clearAllPreviewsBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Clear all the results and tasks in this window?", async function() { - await stopAllTasks() +clearAllPreviewsBtn.addEventListener("click", (e) => { + shiftOrConfirm(e, "Clear all the results and tasks in this window?", async function() { + await stopAllTasks() - let taskEntries = document.querySelectorAll('.imageTaskContainer') - taskEntries.forEach(removeTask) -})}) + let taskEntries = document.querySelectorAll(".imageTaskContainer") + taskEntries.forEach(removeTask) + }) +}) /* Download images popup */ -showDownloadPopupBtn.addEventListener("click", (e) => { saveAllImagesPopup.classList.add("active") }) +showDownloadPopupBtn.addEventListener("click", (e) => { + saveAllImagesPopup.classList.add("active") +}) -saveAllZipToggle.addEventListener('change', (e) => { +saveAllZipToggle.addEventListener("change", (e) => { if (saveAllZipToggle.checked) { - saveAllFoldersOption.classList.remove('displayNone') + saveAllFoldersOption.classList.remove("displayNone") } else { - saveAllFoldersOption.classList.add('displayNone') + saveAllFoldersOption.classList.add("displayNone") } }) // convert base64 to raw binary data held in a string function dataURItoBlob(dataURI) { - var byteString = atob(dataURI.split(',')[1]) + var byteString = atob(dataURI.split(",")[1]) // separate out the mime component - var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] + var mimeString = dataURI + .split(",")[0] + .split(":")[1] + .split(";")[0] // write the bytes of the string to an ArrayBuffer var ab = new ArrayBuffer(byteString.length) @@ -1364,97 +1437,112 @@ function dataURItoBlob(dataURI) { } // write the ArrayBuffer to a blob, and you're done - return new Blob([ab], {type: mimeString}) + return new Blob([ab], { type: mimeString }) } function downloadAllImages() { let i = 0 - let optZIP = saveAllZipToggle.checked + let optZIP = saveAllZipToggle.checked let optTree = optZIP && saveAllTreeToggle.checked let optJSON = saveAllJSONToggle.checked - + let zip = new JSZip() let folder = zip - document.querySelectorAll(".imageTaskContainer").forEach(container => { + document.querySelectorAll(".imageTaskContainer").forEach((container) => { if (optTree) { - let name = ++i + '-' + container.querySelector('.preview-prompt').textContent.replace(/[^a-zA-Z0-9]/g, '_').substring(0,25) + let name = + ++i + + "-" + + container + .querySelector(".preview-prompt") + .textContent.replace(/[^a-zA-Z0-9]/g, "_") + .substring(0, 25) folder = zip.folder(name) } - container.querySelectorAll(".imgContainer img").forEach(img => { - let imgItem = img.closest('.imgItem') + container.querySelectorAll(".imgContainer img").forEach((img) => { + let imgItem = img.closest(".imgItem") - if (imgItem.style.display === 'none') { + if (imgItem.style.display === "none") { return } - let req = imageRequest[img.dataset['imagecounter']] + let req = imageRequest[img.dataset["imagecounter"]] if (optZIP) { - let suffix = img.dataset['imagecounter'] + '.' + req['output_format'] + let suffix = img.dataset["imagecounter"] + "." + req["output_format"] folder.file(getDownloadFilename(img, suffix), dataURItoBlob(img.src)) if (optJSON) { - suffix = img.dataset['imagecounter'] + '.json' + suffix = img.dataset["imagecounter"] + ".json" folder.file(getDownloadFilename(img, suffix), JSON.stringify(req, null, 2)) } } else { - setTimeout(() => {imgItem.querySelector('.download-img').click()}, i*200) - i = i+1 + setTimeout(() => { + imgItem.querySelector(".download-img").click() + }, i * 200) + i = i + 1 if (optJSON) { - setTimeout(() => {imgItem.querySelector('.download-json').click()}, i*200) - i = i+1 + setTimeout(() => { + imgItem.querySelector(".download-json").click() + }, i * 200) + i = i + 1 } } }) }) if (optZIP) { - let now = Date.now().toString(36).toUpperCase() - zip.generateAsync({type:"blob"}).then(function (blob) { - saveAs(blob, `EasyDiffusion-Images-${now}.zip`); + let now = Date.now() + .toString(36) + .toUpperCase() + zip.generateAsync({ type: "blob" }).then(function(blob) { + saveAs(blob, `EasyDiffusion-Images-${now}.zip`) }) } +} -} +saveAllImagesBtn.addEventListener("click", (e) => { + downloadAllImages() +}) -saveAllImagesBtn.addEventListener('click', (e) => { downloadAllImages() }) +stopImageBtn.addEventListener("click", (e) => { + shiftOrConfirm(e, "Stop all the tasks?", async function(e) { + await stopAllTasks() + }) +}) -stopImageBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Stop all the tasks?", async function(e) { - await stopAllTasks() -})}) - -widthField.addEventListener('change', onDimensionChange) -heightField.addEventListener('change', onDimensionChange) +widthField.addEventListener("change", onDimensionChange) +heightField.addEventListener("change", onDimensionChange) function renameMakeImageButton() { - let totalImages = Math.max(parseInt(numOutputsTotalField.value), parseInt(numOutputsParallelField.value)) * getPrompts().length - let imageLabel = 'Image' + let totalImages = + Math.max(parseInt(numOutputsTotalField.value), parseInt(numOutputsParallelField.value)) * getPrompts().length + let imageLabel = "Image" if (totalImages > 1) { - imageLabel = totalImages + ' Images' + imageLabel = totalImages + " Images" } if (SD.activeTasks.size == 0) { - makeImageBtn.innerText = 'Make ' + imageLabel + makeImageBtn.innerText = "Make " + imageLabel } else { - makeImageBtn.innerText = 'Enqueue Next ' + imageLabel + makeImageBtn.innerText = "Enqueue Next " + imageLabel } } -numOutputsTotalField.addEventListener('change', renameMakeImageButton) -numOutputsTotalField.addEventListener('keyup', debounce(renameMakeImageButton, 300)) -numOutputsParallelField.addEventListener('change', renameMakeImageButton) -numOutputsParallelField.addEventListener('keyup', debounce(renameMakeImageButton, 300)) +numOutputsTotalField.addEventListener("change", renameMakeImageButton) +numOutputsTotalField.addEventListener("keyup", debounce(renameMakeImageButton, 300)) +numOutputsParallelField.addEventListener("change", renameMakeImageButton) +numOutputsParallelField.addEventListener("keyup", debounce(renameMakeImageButton, 300)) function onDimensionChange() { let widthValue = parseInt(widthField.value) let heightValue = parseInt(heightField.value) if (!initImagePreviewContainer.classList.contains("has-image")) { imageEditor.setImage(null, widthValue, heightValue) - } - else { + } else { imageInpainter.setImage(initImagePreview.src, widthValue, heightValue) } - if ( widthValue < 512 && heightValue < 512 ) { - smallImageWarning.classList.remove('displayNone') + if (widthValue < 512 && heightValue < 512) { + smallImageWarning.classList.remove("displayNone") } else { - smallImageWarning.classList.add('displayNone') + smallImageWarning.classList.add("displayNone") } } @@ -1462,21 +1550,21 @@ diskPathField.disabled = !saveToDiskField.checked metadataOutputFormatField.disabled = !saveToDiskField.checked gfpganModelField.disabled = !useFaceCorrectionField.checked -useFaceCorrectionField.addEventListener('change', function(e) { +useFaceCorrectionField.addEventListener("change", function(e) { gfpganModelField.disabled = !this.checked }) upscaleModelField.disabled = !useUpscalingField.checked upscaleAmountField.disabled = !useUpscalingField.checked -useUpscalingField.addEventListener('change', function(e) { +useUpscalingField.addEventListener("change", function(e) { upscaleModelField.disabled = !this.checked upscaleAmountField.disabled = !this.checked }) -makeImageBtn.addEventListener('click', makeImage) +makeImageBtn.addEventListener("click", makeImage) document.onkeydown = function(e) { - if (e.ctrlKey && e.code === 'Enter') { + if (e.ctrlKey && e.code === "Enter") { makeImage() e.preventDefault() } @@ -1499,8 +1587,8 @@ function updateGuidanceScaleSlider() { guidanceScaleSlider.dispatchEvent(new Event("change")) } -guidanceScaleSlider.addEventListener('input', updateGuidanceScale) -guidanceScaleField.addEventListener('input', updateGuidanceScaleSlider) +guidanceScaleSlider.addEventListener("input", updateGuidanceScale) +guidanceScaleField.addEventListener("input", updateGuidanceScaleSlider) updateGuidanceScale() /********************* Prompt Strength *******************/ @@ -1520,8 +1608,8 @@ function updatePromptStrengthSlider() { promptStrengthSlider.dispatchEvent(new Event("change")) } -promptStrengthSlider.addEventListener('input', updatePromptStrength) -promptStrengthField.addEventListener('input', updatePromptStrengthSlider) +promptStrengthSlider.addEventListener("input", updatePromptStrength) +promptStrengthField.addEventListener("input", updatePromptStrengthSlider) updatePromptStrength() /********************* Hypernetwork Strength **********************/ @@ -1541,14 +1629,15 @@ function updateHypernetworkStrengthSlider() { hypernetworkStrengthSlider.dispatchEvent(new Event("change")) } -hypernetworkStrengthSlider.addEventListener('input', updateHypernetworkStrength) -hypernetworkStrengthField.addEventListener('input', updateHypernetworkStrengthSlider) +hypernetworkStrengthSlider.addEventListener("input", updateHypernetworkStrength) +hypernetworkStrengthField.addEventListener("input", updateHypernetworkStrengthSlider) updateHypernetworkStrength() function updateHypernetworkStrengthContainer() { - document.querySelector("#hypernetwork_strength_container").style.display = (hypernetworkModelField.value === "" ? 'none' : '') + document.querySelector("#hypernetwork_strength_container").style.display = + hypernetworkModelField.value === "" ? "none" : "" } -hypernetworkModelField.addEventListener('change', updateHypernetworkStrengthContainer) +hypernetworkModelField.addEventListener("change", updateHypernetworkStrengthContainer) updateHypernetworkStrengthContainer() /********************* LoRA alpha **********************/ @@ -1568,19 +1657,19 @@ function updateLoraAlphaSlider() { loraAlphaSlider.dispatchEvent(new Event("change")) } -loraAlphaSlider.addEventListener('input', updateLoraAlpha) -loraAlphaField.addEventListener('input', updateLoraAlphaSlider) +loraAlphaSlider.addEventListener("input", updateLoraAlpha) +loraAlphaField.addEventListener("input", updateLoraAlphaSlider) updateLoraAlpha() function updateLoraAlphaContainer() { - document.querySelector("#lora_alpha_container").style.display = (loraModelField.value === "" ? 'none' : '') + document.querySelector("#lora_alpha_container").style.display = loraModelField.value === "" ? "none" : "" } -loraModelField.addEventListener('change', updateLoraAlphaContainer) +loraModelField.addEventListener("change", updateLoraAlphaContainer) updateLoraAlphaContainer() /********************* JPEG/WEBP Quality **********************/ function updateOutputQuality() { - outputQualityField.value = 0 | outputQualitySlider.value + outputQualityField.value = 0 | outputQualitySlider.value outputQualityField.dispatchEvent(new Event("change")) } @@ -1591,45 +1680,44 @@ function updateOutputQualitySlider() { outputQualityField.value = 95 } - outputQualitySlider.value = 0 | outputQualityField.value + outputQualitySlider.value = 0 | outputQualityField.value outputQualitySlider.dispatchEvent(new Event("change")) } -outputQualitySlider.addEventListener('input', updateOutputQuality) -outputQualityField.addEventListener('input', debounce(updateOutputQualitySlider, 1500)) +outputQualitySlider.addEventListener("input", updateOutputQuality) +outputQualityField.addEventListener("input", debounce(updateOutputQualitySlider, 1500)) updateOutputQuality() function updateOutputQualityVisibility() { - if (outputFormatField.value === 'webp') { - outputLosslessContainer.classList.remove('displayNone') + if (outputFormatField.value === "webp") { + outputLosslessContainer.classList.remove("displayNone") if (outputLosslessField.checked) { - outputQualityRow.classList.add('displayNone') + outputQualityRow.classList.add("displayNone") } else { - outputQualityRow.classList.remove('displayNone') + outputQualityRow.classList.remove("displayNone") } - } - else if (outputFormatField.value === 'png') { - outputQualityRow.classList.add('displayNone') - outputLosslessContainer.classList.add('displayNone') + } else if (outputFormatField.value === "png") { + outputQualityRow.classList.add("displayNone") + outputLosslessContainer.classList.add("displayNone") } else { - outputQualityRow.classList.remove('displayNone') - outputLosslessContainer.classList.add('displayNone') + outputQualityRow.classList.remove("displayNone") + outputLosslessContainer.classList.add("displayNone") } } -outputFormatField.addEventListener('change', updateOutputQualityVisibility) -outputLosslessField.addEventListener('change', updateOutputQualityVisibility) +outputFormatField.addEventListener("change", updateOutputQualityVisibility) +outputLosslessField.addEventListener("change", updateOutputQualityVisibility) /********************* Zoom Slider **********************/ -thumbnailSizeField.addEventListener('change', () => { - (function (s) { - for (var j =0; j < document.styleSheets.length; j++) { +thumbnailSizeField.addEventListener("change", () => { + ;(function(s) { + for (var j = 0; j < document.styleSheets.length; j++) { let cssSheet = document.styleSheets[j] for (var i = 0; i < cssSheet.cssRules.length; i++) { - var rule = cssSheet.cssRules[i]; + var rule = cssSheet.cssRules[i] if (rule.selectorText == "div.img-preview img") { - rule.style['max-height'] = s+'vh'; - rule.style['max-width'] = s+'vw'; - return; + rule.style["max-height"] = s + "vh" + rule.style["max-width"] = s + "vw" + return } } } @@ -1638,18 +1726,18 @@ thumbnailSizeField.addEventListener('change', () => { function onAutoScrollUpdate() { if (autoScroll.checked) { - autoscrollBtn.classList.add('pressed') + autoscrollBtn.classList.add("pressed") } else { - autoscrollBtn.classList.remove('pressed') + autoscrollBtn.classList.remove("pressed") } - autoscrollBtn.querySelector(".state").innerHTML = (autoScroll.checked ? "ON" : "OFF") + autoscrollBtn.querySelector(".state").innerHTML = autoScroll.checked ? "ON" : "OFF" } -autoscrollBtn.addEventListener('click', function() { +autoscrollBtn.addEventListener("click", function() { autoScroll.checked = !autoScroll.checked autoScroll.dispatchEvent(new Event("change")) onAutoScrollUpdate() }) -autoScroll.addEventListener('change', onAutoScrollUpdate) +autoScroll.addEventListener("change", onAutoScrollUpdate) function checkRandomSeed() { if (randomSeedField.checked) { @@ -1659,7 +1747,7 @@ function checkRandomSeed() { seedField.disabled = false } } -randomSeedField.addEventListener('input', checkRandomSeed) +randomSeedField.addEventListener("input", checkRandomSeed) checkRandomSeed() function loadImg2ImgFromFile() { @@ -1670,7 +1758,7 @@ function loadImg2ImgFromFile() { let reader = new FileReader() let file = initImageSelector.files[0] - reader.addEventListener('load', function(event) { + reader.addEventListener("load", function(event) { initImagePreview.src = reader.result }) @@ -1678,16 +1766,16 @@ function loadImg2ImgFromFile() { reader.readAsDataURL(file) } } -initImageSelector.addEventListener('change', loadImg2ImgFromFile) +initImageSelector.addEventListener("change", loadImg2ImgFromFile) loadImg2ImgFromFile() function img2imgLoad() { - promptStrengthContainer.style.display = 'table-row' + promptStrengthContainer.style.display = "table-row" if (!testDiffusers.checked) { samplerSelectionContainer.style.display = "none" } initImagePreviewContainer.classList.add("has-image") - colorCorrectionSetting.style.display = '' + colorCorrectionSetting.style.display = "" initImageSizeBox.textContent = initImagePreview.naturalWidth + " x " + initImagePreview.naturalHeight imageEditor.setImage(this.src, initImagePreview.naturalWidth, initImagePreview.naturalHeight) @@ -1696,7 +1784,7 @@ function img2imgLoad() { function img2imgUnload() { initImageSelector.value = null - initImagePreview.src = '' + initImagePreview.src = "" maskSetting.checked = false promptStrengthContainer.style.display = "none" @@ -1704,22 +1792,21 @@ function img2imgUnload() { samplerSelectionContainer.style.display = "" } initImagePreviewContainer.classList.remove("has-image") - colorCorrectionSetting.style.display = 'none' + colorCorrectionSetting.style.display = "none" imageEditor.setImage(null, parseInt(widthField.value), parseInt(heightField.value)) - } -initImagePreview.addEventListener('load', img2imgLoad) -initImageClearBtn.addEventListener('click', img2imgUnload) +initImagePreview.addEventListener("load", img2imgLoad) +initImageClearBtn.addEventListener("click", img2imgUnload) -maskSetting.addEventListener('click', function() { +maskSetting.addEventListener("click", function() { onDimensionChange() }) -promptsFromFileBtn.addEventListener('click', function() { +promptsFromFileBtn.addEventListener("click", function() { promptsFromFileSelector.click() }) -promptsFromFileSelector.addEventListener('change', async function() { +promptsFromFileSelector.addEventListener("change", async function() { if (promptsFromFileSelector.files.length === 0) { return } @@ -1727,7 +1814,7 @@ promptsFromFileSelector.addEventListener('change', async function() { let reader = new FileReader() let file = promptsFromFileSelector.files[0] - reader.addEventListener('load', async function() { + reader.addEventListener("load", async function() { await parseContent(reader.result) }) @@ -1737,15 +1824,15 @@ promptsFromFileSelector.addEventListener('change', async function() { }) /* setup popup handlers */ -document.querySelectorAll('.popup').forEach(popup => { - popup.addEventListener('click', event => { +document.querySelectorAll(".popup").forEach((popup) => { + popup.addEventListener("click", (event) => { if (event.target == popup) { popup.classList.remove("active") } }) var closeButton = popup.querySelector(".close-button") if (closeButton) { - closeButton.addEventListener('click', () => { + closeButton.addEventListener("click", () => { popup.classList.remove("active") }) } @@ -1753,9 +1840,9 @@ document.querySelectorAll('.popup').forEach(popup => { var tabElements = [] function selectTab(tab_id) { - let tabInfo = tabElements.find(t => t.tab.id == tab_id) + let tabInfo = tabElements.find((t) => t.tab.id == tab_id) if (!tabInfo.tab.classList.contains("active")) { - tabElements.forEach(info => { + tabElements.forEach((info) => { if (info.tab.classList.contains("active") && info.tab.parentNode === tabInfo.tab.parentNode) { info.tab.classList.toggle("active") info.content.classList.toggle("active") @@ -1764,7 +1851,7 @@ function selectTab(tab_id) { tabInfo.tab.classList.toggle("active") tabInfo.content.classList.toggle("active") } - document.dispatchEvent(new CustomEvent('tabClick', { detail: tabInfo })) + document.dispatchEvent(new CustomEvent("tabClick", { detail: tabInfo })) } function linkTabContents(tab) { var name = tab.id.replace("tab-", "") @@ -1775,7 +1862,7 @@ function linkTabContents(tab) { content: content }) - tab.addEventListener("click", event => selectTab(tab.id)) + tab.addEventListener("click", (event) => selectTab(tab.id)) } function isTabActive(tab) { return tab.classList.contains("active") @@ -1785,54 +1872,53 @@ let pauseClient = false function resumeClient() { if (pauseClient) { - document.body.classList.remove('wait-pause') - document.body.classList.add('pause') + document.body.classList.remove("wait-pause") + document.body.classList.add("pause") } - return new Promise(resolve => { - let playbuttonclick = function () { - resumeBtn.removeEventListener("click", playbuttonclick); - resolve("resolved"); + return new Promise((resolve) => { + let playbuttonclick = function() { + resumeBtn.removeEventListener("click", playbuttonclick) + resolve("resolved") } resumeBtn.addEventListener("click", playbuttonclick) }) } -promptField.addEventListener("input", debounce( renameMakeImageButton, 1000) ) +promptField.addEventListener("input", debounce(renameMakeImageButton, 1000)) - -pauseBtn.addEventListener("click", function () { +pauseBtn.addEventListener("click", function() { pauseClient = true - pauseBtn.style.display="none" + pauseBtn.style.display = "none" resumeBtn.style.display = "inline" - document.body.classList.add('wait-pause') + document.body.classList.add("wait-pause") }) -resumeBtn.addEventListener("click", function () { +resumeBtn.addEventListener("click", function() { pauseClient = false resumeBtn.style.display = "none" pauseBtn.style.display = "inline" - document.body.classList.remove('pause') - document.body.classList.remove('wait-pause') + document.body.classList.remove("pause") + document.body.classList.remove("wait-pause") }) /* Pause function */ document.querySelectorAll(".tab").forEach(linkTabContents) window.addEventListener("beforeunload", function(e) { - const msg = "Unsaved pictures will be lost!"; + const msg = "Unsaved pictures will be lost!" - let elementList = document.getElementsByClassName("imageTaskContainer"); + let elementList = document.getElementsByClassName("imageTaskContainer") if (elementList.length != 0) { - e.preventDefault(); - (e || window.event).returnValue = msg; - return msg; + e.preventDefault() + ;(e || window.event).returnValue = msg + return msg } else { - return true; + return true } -}); +}) createCollapsibles() -prettifyInputs(document); +prettifyInputs(document) // set the textbox as focused on start promptField.focus() diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index af399205..7a263315 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -8,8 +8,8 @@ var ParameterType = { select: "select", select_multiple: "select_multiple", slider: "slider", - custom: "custom", -}; + custom: "custom" +} /** * JSDoc style @@ -24,7 +24,6 @@ var ParameterType = { * @property {boolean?} saveInAppConfig */ - /** @type {Array.} */ var PARAMETERS = [ { @@ -33,7 +32,8 @@ var PARAMETERS = [ label: "Theme", default: "theme-default", note: "customize the look and feel of the ui", - options: [ // Note: options expanded dynamically + options: [ + // Note: options expanded dynamically { value: "theme-default", label: "Default" @@ -47,7 +47,7 @@ var PARAMETERS = [ label: "Auto-Save Images", note: "automatically saves images to the specified location", icon: "fa-download", - default: false, + default: false }, { id: "diskPath", @@ -82,13 +82,13 @@ var PARAMETERS = [ }, { value: "embed,txt", - label: "embed & txt", + label: "embed & txt" }, { value: "embed,json", - label: "embed & json", - }, - ], + label: "embed & json" + } + ] }, { id: "block_nsfw", @@ -96,7 +96,7 @@ var PARAMETERS = [ label: "Block NSFW images", note: "blurs out NSFW images", icon: "fa-land-mine-on", - default: false, + default: false }, { id: "sound_toggle", @@ -104,7 +104,7 @@ var PARAMETERS = [ label: "Enable Sound", note: "plays a sound on task completion", icon: "fa-volume-low", - default: true, + default: true }, { id: "process_order_toggle", @@ -112,7 +112,7 @@ var PARAMETERS = [ label: "Process newest jobs first", note: "reverse the normal processing order", icon: "fa-arrow-down-short-wide", - default: false, + default: false }, { id: "ui_open_browser_on_start", @@ -121,23 +121,24 @@ var PARAMETERS = [ note: "starts the default browser on startup", icon: "fa-window-restore", default: true, - saveInAppConfig: true, + saveInAppConfig: true }, { id: "vram_usage_level", type: ParameterType.select, label: "GPU Memory Usage", - note: "Faster performance requires more GPU memory (VRAM)

" + - "Balanced: nearly as fast as High, much lower VRAM usage
" + - "High: fastest, maximum GPU memory usage
" + - "Low: slowest, recommended for GPUs with 3 to 4 GB memory", + note: + "Faster performance requires more GPU memory (VRAM)

" + + "Balanced: nearly as fast as High, much lower VRAM usage
" + + "High: fastest, maximum GPU memory usage
" + + "Low: slowest, recommended for GPUs with 3 to 4 GB memory", icon: "fa-forward", default: "balanced", options: [ - {value: "balanced", label: "Balanced"}, - {value: "high", label: "High"}, - {value: "low", label: "Low"} - ], + { value: "balanced", label: "Balanced" }, + { value: "high", label: "High" }, + { value: "low", label: "Low" } + ] }, { id: "use_cpu", @@ -145,20 +146,20 @@ var PARAMETERS = [ label: "Use CPU (not GPU)", note: "warning: this will be *very* slow", icon: "fa-microchip", - default: false, + default: false }, { id: "auto_pick_gpus", type: ParameterType.checkbox, label: "Automatically pick the GPUs (experimental)", - default: false, + default: false }, { id: "use_gpus", type: ParameterType.select_multiple, label: "GPUs to use (experimental)", note: "to process in parallel", - default: false, + default: false }, { id: "auto_save_settings", @@ -166,15 +167,16 @@ var PARAMETERS = [ label: "Auto-Save Settings", note: "restores settings on browser load", icon: "fa-gear", - default: true, + 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", + 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, + default: true }, { id: "listen_to_network", @@ -183,7 +185,7 @@ var PARAMETERS = [ note: "Other devices on your network can access this web page", icon: "fa-network-wired", default: true, - saveInAppConfig: true, + saveInAppConfig: true }, { id: "listen_port", @@ -194,29 +196,31 @@ var PARAMETERS = [ render: (parameter) => { return `` }, - saveInAppConfig: true, + saveInAppConfig: true }, { 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.", + note: + "Get the latest features immediately (but could be less stable). Please restart the program after changing this.", icon: "fa-fire", - default: false, + default: false }, { id: "test_diffusers", type: ParameterType.checkbox, label: "Test Diffusers", - note: "Experimental! Can have bugs! Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.", + 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, - }, -]; + saveInAppConfig: true + } +] function getParameterSettingsEntry(id) { - let parameter = PARAMETERS.filter(p => p.id === id) + let parameter = PARAMETERS.filter((p) => p.id === id) if (parameter.length === 0) { return } @@ -224,37 +228,39 @@ function getParameterSettingsEntry(id) { } function sliderUpdate(event) { - if (event.srcElement.id.endsWith('-input')) { - let slider = document.getElementById(event.srcElement.id.slice(0,-6)) + if (event.srcElement.id.endsWith("-input")) { + let slider = document.getElementById(event.srcElement.id.slice(0, -6)) slider.value = event.srcElement.value slider.dispatchEvent(new Event("change")) } else { - let field = document.getElementById(event.srcElement.id+'-input') + let field = document.getElementById(event.srcElement.id + "-input") field.value = event.srcElement.value field.dispatchEvent(new Event("change")) } } /** - * @param {Parameter} parameter + * @param {Parameter} parameter * @returns {string | HTMLElement} */ function getParameterElement(parameter) { switch (parameter.type) { case ParameterType.checkbox: - var is_checked = parameter.default ? " checked" : ""; + 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' : '') + var options = (parameter.options || []) + .map((option) => ``) + .join("") + var multiple = parameter.type == ParameterType.select_multiple ? "multiple" : "" return `` case ParameterType.slider: return `  ${parameter.slider_unit}` case ParameterType.custom: return parameter.render(parameter) default: - console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`); + console.error(`Invalid type ${parameter.type} for parameter ${parameter.id}`) return "ERROR: Invalid Type" } } @@ -265,31 +271,31 @@ let parametersTable = document.querySelector("#system-settings .parameters-table * @param {Array | undefined} parameters * */ function initParameters(parameters) { - parameters.forEach(parameter => { + parameters.forEach((parameter) => { const element = getParameterElement(parameter) - const elementWrapper = createElement('div') + 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 note = typeof parameter.note === "function" ? parameter.note(parameter) : parameter.note const noteElements = [] if (note) { - const noteElement = createElement('small') + const noteElement = createElement("small") if (note instanceof Node) { noteElement.appendChild(note) } else { - noteElement.innerHTML = note || '' + noteElement.innerHTML = note || "" } noteElements.push(noteElement) } - const icon = parameter.icon ? [createElement('i', undefined, ['fa', parameter.icon])] : [] + 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 }) + 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 { @@ -297,13 +303,13 @@ function initParameters(parameters) { } const newrow = createElement( - 'div', - { 'data-setting-id': parameter.id, 'data-save-in-app-config': parameter.saveInAppConfig }, + "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, + createElement("div", undefined, undefined, icon), + createElement("div", undefined, undefined, [labelElement, ...noteElements]), + elementWrapper ] ) parametersTable.appendChild(newrow) @@ -314,22 +320,25 @@ function initParameters(parameters) { initParameters(PARAMETERS) // listen to parameters from plugins -PARAMETERS.addEventListener('push', (...items) => { +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)) + + 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') -let autoPickGPUsField = document.querySelector('#auto_pick_gpus') -let useGPUsField = document.querySelector('#use_gpus') -let saveToDiskField = document.querySelector('#save_to_disk') -let diskPathField = document.querySelector('#diskPath') -let metadataOutputFormatField = document.querySelector('#metadata_output_format') +let vramUsageLevelField = document.querySelector("#vram_usage_level") +let useCPUField = document.querySelector("#use_cpu") +let autoPickGPUsField = document.querySelector("#auto_pick_gpus") +let useGPUsField = document.querySelector("#use_gpus") +let saveToDiskField = document.querySelector("#save_to_disk") +let diskPathField = document.querySelector("#diskPath") +let metadataOutputFormatField = document.querySelector("#metadata_output_format") let listenToNetworkField = document.querySelector("#listen_to_network") let listenPortField = document.querySelector("#listen_port") let useBetaChannelField = document.querySelector("#use_beta_channel") @@ -337,35 +346,34 @@ let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_star let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions") let testDiffusers = document.querySelector("#test_diffusers") -let saveSettingsBtn = document.querySelector('#save-system-settings-btn') - +let saveSettingsBtn = document.querySelector("#save-system-settings-btn") async function changeAppConfig(configDelta) { try { - let res = await fetch('/app_config', { - method: 'POST', + let res = await fetch("/app_config", { + method: "POST", headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json" }, body: JSON.stringify(configDelta) }) res = await res.json() - console.log('set config status response', res) + console.log("set config status response", res) } catch (e) { - console.log('set config status error', e) + console.log("set config status error", e) } } async function getAppConfig() { try { - let res = await fetch('/get/app_config') + let res = await fetch("/get/app_config") const config = await res.json() applySettingsFromConfig(config) // custom overrides - if (config.update_branch === 'beta') { + if (config.update_branch === "beta") { useBetaChannelField.checked = true document.querySelector("#updateBranchLabel").innerText = "(beta)" } else { @@ -380,45 +388,48 @@ async function getAppConfig() { if (config.net && config.net.listen_port !== undefined) { listenPortField.value = config.net.listen_port } - if (config.test_diffusers === undefined || config.update_branch === 'main') { + if (config.test_diffusers === undefined || config.update_branch === "main") { testDiffusers.checked = false - document.querySelector("#lora_model_container").style.display = 'none' - document.querySelector("#lora_alpha_container").style.display = 'none' + 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 !== 'main' - document.querySelector("#lora_model_container").style.display = (testDiffusers.checked ? '' : 'none') - document.querySelector("#lora_alpha_container").style.display = (testDiffusers.checked && loraModelField.value !== "" ? '' : 'none') + 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" } - console.log('get config status response', config) + console.log("get config status response", config) return config } catch (e) { - console.log('get config status error', e) + console.log("get config status error", e) return {} } } function applySettingsFromConfig(config) { - Array.from(parametersTable.children).forEach(parameterRow => { - if (parameterRow.dataset.settingId in config && parameterRow.dataset.saveInAppConfig === 'true') { + 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') + const parameterElement = + document.getElementById(parameterRow.dataset.settingId) || + parameterRow.querySelector("input") || + parameterRow.querySelector("select") switch (parameterElement?.tagName) { - case 'INPUT': - if (parameterElement.type === 'checkbox') { + case "INPUT": + if (parameterElement.type === "checkbox") { parameterElement.checked = configValue } else { parameterElement.value = configValue } - parameterElement.dispatchEvent(new Event('change')) + parameterElement.dispatchEvent(new Event("change")) break - case 'SELECT': + case "SELECT": if (Array.isArray(configValue)) { - Array.from(parameterElement.options).forEach(option => { + Array.from(parameterElement.options).forEach((option) => { if (configValue.includes(option.value || option.text)) { option.selected = true } @@ -426,82 +437,85 @@ function applySettingsFromConfig(config) { } else { parameterElement.value = configValue } - parameterElement.dispatchEvent(new Event('change')) + parameterElement.dispatchEvent(new Event("change")) break } } }) } -saveToDiskField.addEventListener('change', function(e) { +saveToDiskField.addEventListener("change", function(e) { diskPathField.disabled = !this.checked metadataOutputFormatField.disabled = !this.checked }) function getCurrentRenderDeviceSelection() { - let selectedGPUs = $('#use_gpus').val() + let selectedGPUs = $("#use_gpus").val() if (useCPUField.checked && !autoPickGPUsField.checked) { - return 'cpu' + return "cpu" } if (autoPickGPUsField.checked || selectedGPUs.length == 0) { - return 'auto' + return "auto" } - return selectedGPUs.join(',') + return selectedGPUs.join(",") } -useCPUField.addEventListener('click', function() { - let gpuSettingEntry = getParameterSettingsEntry('use_gpus') - let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus') +useCPUField.addEventListener("click", function() { + let gpuSettingEntry = getParameterSettingsEntry("use_gpus") + let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus") if (this.checked) { - gpuSettingEntry.style.display = 'none' - autoPickGPUSettingEntry.style.display = 'none' - autoPickGPUsField.setAttribute('data-old-value', autoPickGPUsField.checked) + gpuSettingEntry.style.display = "none" + autoPickGPUSettingEntry.style.display = "none" + autoPickGPUsField.setAttribute("data-old-value", autoPickGPUsField.checked) autoPickGPUsField.checked = false } else if (useGPUsField.options.length >= MIN_GPUS_TO_SHOW_SELECTION) { - gpuSettingEntry.style.display = '' - autoPickGPUSettingEntry.style.display = '' - let oldVal = autoPickGPUsField.getAttribute('data-old-value') - if (oldVal === null || oldVal === undefined) { // the UI started with CPU selected by default + gpuSettingEntry.style.display = "" + autoPickGPUSettingEntry.style.display = "" + let oldVal = autoPickGPUsField.getAttribute("data-old-value") + if (oldVal === null || oldVal === undefined) { + // the UI started with CPU selected by default autoPickGPUsField.checked = true } else { - autoPickGPUsField.checked = (oldVal === 'true') + autoPickGPUsField.checked = oldVal === "true" } - gpuSettingEntry.style.display = (autoPickGPUsField.checked ? 'none' : '') + gpuSettingEntry.style.display = autoPickGPUsField.checked ? "none" : "" } }) -useGPUsField.addEventListener('click', function() { - let selectedGPUs = $('#use_gpus').val() - autoPickGPUsField.checked = (selectedGPUs.length === 0) +useGPUsField.addEventListener("click", function() { + let selectedGPUs = $("#use_gpus").val() + autoPickGPUsField.checked = selectedGPUs.length === 0 }) -autoPickGPUsField.addEventListener('click', function() { +autoPickGPUsField.addEventListener("click", function() { if (this.checked) { - $('#use_gpus').val([]) + $("#use_gpus").val([]) } - let gpuSettingEntry = getParameterSettingsEntry('use_gpus') - gpuSettingEntry.style.display = (this.checked ? 'none' : '') + let gpuSettingEntry = getParameterSettingsEntry("use_gpus") + gpuSettingEntry.style.display = this.checked ? "none" : "" }) -async function setDiskPath(defaultDiskPath, force=false) { +async function setDiskPath(defaultDiskPath, force = false) { var diskPath = getSetting("diskPath") - if (force || diskPath == '' || diskPath == undefined || diskPath == "undefined") { + if (force || diskPath == "" || diskPath == undefined || diskPath == "undefined") { setSetting("diskPath", defaultDiskPath) } } function setDeviceInfo(devices) { let cpu = devices.all.cpu.name - let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu') + 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)` + 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)` } @@ -510,35 +524,35 @@ function setDeviceInfo(devices) { 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('
') + 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('') + hosts = hosts.map((addr) => `http://${addr}:${port}/`).map((url) => ``) + document.querySelector("#system-info-server-hosts").innerHTML = hosts.join("") } async function getSystemInfo() { try { const res = await SD.getSystemInfo() - let devices = res['devices'] + let devices = res["devices"] - let allDeviceIds = Object.keys(devices['all']).filter(d => d !== 'cpu') - let activeDeviceIds = Object.keys(devices['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 } if (allDeviceIds.length < MIN_GPUS_TO_SHOW_SELECTION || useCPUField.checked) { - let gpuSettingEntry = getParameterSettingsEntry('use_gpus') - gpuSettingEntry.style.display = 'none' - let autoPickGPUSettingEntry = getParameterSettingsEntry('auto_pick_gpus') - autoPickGPUSettingEntry.style.display = 'none' + let gpuSettingEntry = getParameterSettingsEntry("use_gpus") + gpuSettingEntry.style.display = "none" + let autoPickGPUSettingEntry = getParameterSettingsEntry("auto_pick_gpus") + autoPickGPUSettingEntry.style.display = "none" } if (allDeviceIds.length === 0) { @@ -546,86 +560,90 @@ async function getSystemInfo() { useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory } - autoPickGPUsField.checked = (devices['config'] === 'auto') + autoPickGPUsField.checked = devices["config"] === "auto" - useGPUsField.innerHTML = '' - allDeviceIds.forEach(device => { - let deviceName = devices['all'][device]['name'] + useGPUsField.innerHTML = "" + allDeviceIds.forEach((device) => { + let deviceName = devices["all"][device]["name"] let deviceOption = `` - useGPUsField.insertAdjacentHTML('beforeend', deviceOption) + useGPUsField.insertAdjacentHTML("beforeend", deviceOption) }) if (autoPickGPUsField.checked) { - let gpuSettingEntry = getParameterSettingsEntry('use_gpus') - gpuSettingEntry.style.display = 'none' + let gpuSettingEntry = getParameterSettingsEntry("use_gpus") + gpuSettingEntry.style.display = "none" } else { - $('#use_gpus').val(activeDeviceIds) + $("#use_gpus").val(activeDeviceIds) } setDeviceInfo(devices) - setHostInfo(res['hosts']) + setHostInfo(res["hosts"]) let force = false - if (res['enforce_output_dir'] !== undefined) { - force = res['enforce_output_dir'] + if (res["enforce_output_dir"] !== undefined) { + force = res["enforce_output_dir"] if (force == true) { - saveToDiskField.checked = true - metadataOutputFormatField.disabled = false + saveToDiskField.checked = true + metadataOutputFormatField.disabled = false } saveToDiskField.disabled = force diskPathField.disabled = force } - setDiskPath(res['default_output_dir'], force) + setDiskPath(res["default_output_dir"], force) } catch (e) { - console.log('error fetching devices', e) + console.log("error fetching devices", e) } } -saveSettingsBtn.addEventListener('click', function() { - if (listenPortField.value == '') { - alert('The network port field must not be empty.') +saveSettingsBtn.addEventListener("click", function() { + if (listenPortField.value == "") { + alert("The network port field must not be empty.") return } if (listenPortField.value < 1 || listenPortField.value > 65535) { - alert('The network port must be a number from 1 to 65535') + alert("The network port must be a number from 1 to 65535") return } - const updateBranch = (useBetaChannelField.checked ? 'beta' : 'main') + const updateBranch = useBetaChannelField.checked ? "beta" : "main" const updateAppConfigRequest = { - 'render_devices': getCurrentRenderDeviceSelection(), - 'update_branch': updateBranch, + render_devices: getCurrentRenderDeviceSelection(), + update_branch: updateBranch } - Array.from(parametersTable.children).forEach(parameterRow => { - if (parameterRow.dataset.saveInAppConfig === 'true') { - const parameterElement = document.getElementById(parameterRow.dataset.settingId) || - parameterRow.querySelector('input') || parameterRow.querySelector('select') + 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') { + case "INPUT": + if (parameterElement.type === "checkbox") { updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.checked } else { updateAppConfigRequest[parameterRow.dataset.settingId] = parameterElement.value } break - case 'SELECT': + case "SELECT": if (parameterElement.multiple) { updateAppConfigRequest[parameterRow.dataset.settingId] = Array.from(parameterElement.options) - .filter(option => option.selected) - .map(option => option.value || option.text) + .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 or a ` - } + }, }, { id: "metadata_output_format", @@ -66,29 +66,29 @@ var PARAMETERS = [ options: [ { value: "none", - label: "none" + label: "none", }, { value: "txt", - label: "txt" + label: "txt", }, { value: "json", - label: "json" + label: "json", }, { value: "embed", - label: "embed" + label: "embed", }, { value: "embed,txt", - label: "embed & txt" + label: "embed & txt", }, { value: "embed,json", - label: "embed & json" - } - ] + label: "embed & json", + }, + ], }, { id: "block_nsfw", @@ -96,7 +96,7 @@ var PARAMETERS = [ label: "Block NSFW images", note: "blurs out NSFW images", icon: "fa-land-mine-on", - default: false + default: false, }, { id: "sound_toggle", @@ -104,7 +104,7 @@ var PARAMETERS = [ label: "Enable Sound", note: "plays a sound on task completion", icon: "fa-volume-low", - default: true + default: true, }, { id: "process_order_toggle", @@ -112,7 +112,7 @@ var PARAMETERS = [ label: "Process newest jobs first", note: "reverse the normal processing order", icon: "fa-arrow-down-short-wide", - default: false + default: false, }, { id: "ui_open_browser_on_start", @@ -121,7 +121,7 @@ var PARAMETERS = [ note: "starts the default browser on startup", icon: "fa-window-restore", default: true, - saveInAppConfig: true + saveInAppConfig: true, }, { id: "vram_usage_level", @@ -137,8 +137,8 @@ var PARAMETERS = [ options: [ { value: "balanced", label: "Balanced" }, { value: "high", label: "High" }, - { value: "low", label: "Low" } - ] + { value: "low", label: "Low" }, + ], }, { id: "use_cpu", @@ -146,20 +146,20 @@ var PARAMETERS = [ label: "Use CPU (not GPU)", note: "warning: this will be *very* slow", icon: "fa-microchip", - default: false + default: false, }, { id: "auto_pick_gpus", type: ParameterType.checkbox, label: "Automatically pick the GPUs (experimental)", - default: false + default: false, }, { id: "use_gpus", type: ParameterType.select_multiple, label: "GPUs to use (experimental)", note: "to process in parallel", - default: false + default: false, }, { id: "auto_save_settings", @@ -167,7 +167,7 @@ var PARAMETERS = [ label: "Auto-Save Settings", note: "restores settings on browser load", icon: "fa-gear", - default: true + default: true, }, { id: "confirm_dangerous_actions", @@ -176,7 +176,7 @@ var PARAMETERS = [ 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 + default: true, }, { id: "listen_to_network", @@ -185,7 +185,7 @@ var PARAMETERS = [ note: "Other devices on your network can access this web page", icon: "fa-network-wired", default: true, - saveInAppConfig: true + saveInAppConfig: true, }, { id: "listen_port", @@ -196,7 +196,7 @@ var PARAMETERS = [ render: (parameter) => { return `` }, - saveInAppConfig: true + saveInAppConfig: true, }, { id: "use_beta_channel", @@ -205,7 +205,7 @@ var PARAMETERS = [ note: "Get the latest features immediately (but could be less stable). Please restart the program after changing this.", icon: "fa-fire", - default: false + default: false, }, { id: "test_diffusers", @@ -215,8 +215,8 @@ var PARAMETERS = [ "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 - } + saveInAppConfig: true, + }, ] function getParameterSettingsEntry(id) { @@ -309,7 +309,7 @@ function initParameters(parameters) { [ createElement("div", undefined, undefined, icon), createElement("div", undefined, undefined, [labelElement, ...noteElements]), - elementWrapper + elementWrapper, ] ) parametersTable.appendChild(newrow) @@ -353,9 +353,9 @@ async function changeAppConfig(configDelta) { let res = await fetch("/app_config", { method: "POST", headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", }, - body: JSON.stringify(configDelta) + body: JSON.stringify(configDelta), }) res = await res.json() @@ -607,7 +607,7 @@ saveSettingsBtn.addEventListener("click", function() { const updateAppConfigRequest = { render_devices: getCurrentRenderDeviceSelection(), - update_branch: updateBranch + update_branch: updateBranch, } Array.from(parametersTable.children).forEach((parameterRow) => { diff --git a/ui/media/js/plugins.js b/ui/media/js/plugins.js index 85f2e9d9..85cc48d4 100644 --- a/ui/media/js/plugins.js +++ b/ui/media/js/plugins.js @@ -38,7 +38,7 @@ const PLUGINS = { function webp() { return (reqBody) => new SD.RenderTask(reqBody) } - ) + ), } PLUGINS.OUTPUTS_FORMATS.register = function(...args) { const service = ServiceContainer.prototype.register.apply(this, args) diff --git a/ui/media/js/searchable-models.js b/ui/media/js/searchable-models.js index 936f4c57..9a723d15 100644 --- a/ui/media/js/searchable-models.js +++ b/ui/media/js/searchable-models.js @@ -483,7 +483,7 @@ class ModelDropdown { createElement("i", { id: `${this.modelFilter.id}-model-filter-arrow` }, [ "model-selector-arrow", "fa-solid", - "fa-angle-down" + "fa-angle-down", ]) ) this.modelFilter.classList.add("model-selector") @@ -547,7 +547,7 @@ class ModelDropdown { model, createElement("li", { "data-path": fullPath }, classes, [ createElement("i", undefined, ["fa-regular", "fa-file", "icon"]), - model + model, ]) ) } @@ -596,7 +596,7 @@ class ModelDropdown { if (modelTree.length > 0) { const containerListItem = createElement("li", { id: `${this.modelFilter.id}-model-result` }, [ - "model-result" + "model-result", ]) //console.log(containerListItem) containerListItem.appendChild(this.createModelNodeList(undefined, modelTree, true)) diff --git a/ui/media/js/themes.js b/ui/media/js/themes.js index 33c8fca0..a541eb44 100644 --- a/ui/media/js/themes.js +++ b/ui/media/js/themes.js @@ -30,14 +30,14 @@ function initTheme() { THEMES.push({ key: theme_key, name: getThemeName(theme_key), - rule: rule + rule: rule, }) } if (selector && selector == ":root") { DEFAULT_THEME = { key: "theme-default", name: "Default", - rule: rule + rule: rule, } } }) diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 6bc5a65d..6558f7d2 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -111,7 +111,7 @@ function tryLoadOldCollapsibles() { const old_map = { advancedPanelOpen: "editor-settings", modifiersPanelOpen: "editor-modifiers", - negativePromptPanelOpen: "editor-inputs-prompt" + negativePromptPanelOpen: "editor-inputs-prompt", } if (localStorage.getItem(Object.keys(old_map)[0])) { let result = {} @@ -195,7 +195,7 @@ function BraceExpander() { ? bracePair(tkns, iPosn + 1, n, lst) : { close: iPosn, - commas: lst + commas: lst, } } @@ -207,7 +207,7 @@ function BraceExpander() { ? dctSofar : { fn: and, - args: [] + args: [], }, head = tkns[0], tail = head ? tkns.slice(1) : [], @@ -217,7 +217,7 @@ function BraceExpander() { return andTree( { fn: and, - args: dctParse.args.concat(lstOR ? orTree(dctParse, lstOR[0], dctBrace.commas) : head) + args: dctParse.args.concat(lstOR ? orTree(dctParse, lstOR[0], dctBrace.commas) : head), }, lstOR ? lstOR[1] : tail ) @@ -238,7 +238,7 @@ function BraceExpander() { }) .map(function(ts) { return ts.length > 1 ? andTree(null, ts)[0] : ts[0] - }) + }), } } @@ -344,11 +344,11 @@ function PromiseSource() { const srcPromise = new Promise((resolve, reject) => { Object.defineProperties(this, { resolve: { value: resolve, writable: false }, - reject: { value: reject, writable: false } + reject: { value: reject, writable: false }, }) }) Object.defineProperties(this, { - promise: { value: makeQuerablePromise(srcPromise), writable: false } + promise: { value: makeQuerablePromise(srcPromise), writable: false }, }) } @@ -471,20 +471,20 @@ function makeQuerablePromise(promise) { ) Object.defineProperties(qurPro, { isResolved: { - get: () => isResolved + get: () => isResolved, }, resolvedValue: { - get: () => resolvedValue + get: () => resolvedValue, }, isPending: { - get: () => isPending + get: () => isPending, }, isRejected: { - get: () => isRejected + get: () => isRejected, }, rejectReason: { - get: () => rejectReason - } + get: () => rejectReason, + }, }) return qurPro } @@ -790,9 +790,9 @@ function createTab(request) { createElement("i", { style: "margin-right: 0.25em" }, [ "fa-solid", `${request.icon.startsWith("fa-") ? "" : "fa-"}${request.icon}`, - "icon" + "icon", ]), - labelElement + labelElement, ]) ) @@ -831,7 +831,7 @@ function createTab(request) { contentElement: wrapper, labelElement, timesOpened, - firstOpen: timesOpened === 1 + firstOpen: timesOpened === 1, }, e ) diff --git a/ui/plugins/ui/Autoscroll.plugin.js b/ui/plugins/ui/Autoscroll.plugin.js index 364e702d..26969365 100644 --- a/ui/plugins/ui/Autoscroll.plugin.js +++ b/ui/plugins/ui/Autoscroll.plugin.js @@ -14,7 +14,7 @@ observer.observe(document.getElementById("preview"), { childList: true, - subtree: true + subtree: true, }) function Autoscroll(target) { diff --git a/ui/plugins/ui/Modifiers-dnd.plugin.js b/ui/plugins/ui/Modifiers-dnd.plugin.js index 075a073f..d38307cf 100644 --- a/ui/plugins/ui/Modifiers-dnd.plugin.js +++ b/ui/plugins/ui/Modifiers-dnd.plugin.js @@ -25,7 +25,7 @@ }) observer.observe(editorModifierTagsList, { - childList: true + childList: true, }) let current diff --git a/ui/plugins/ui/Modifiers-wheel.plugin.js b/ui/plugins/ui/Modifiers-wheel.plugin.js index afb65bd0..df3a684f 100644 --- a/ui/plugins/ui/Modifiers-wheel.plugin.js +++ b/ui/plugins/ui/Modifiers-wheel.plugin.js @@ -18,7 +18,7 @@ }) observer.observe(editorModifierTagsList, { - childList: true + childList: true, }) function ModifierMouseWheel(target) { diff --git a/ui/plugins/ui/custom-modifiers.plugin.js b/ui/plugins/ui/custom-modifiers.plugin.js index e30930b6..91d2acc4 100644 --- a/ui/plugins/ui/custom-modifiers.plugin.js +++ b/ui/plugins/ui/custom-modifiers.plugin.js @@ -13,19 +13,19 @@ customModifiers = customModifiers.filter((m) => m.trim() !== "") customModifiers = customModifiers.map(function(m) { return { - modifier: m + modifier: m, } }) let customGroup = { category: "Custom Modifiers", - modifiers: customModifiers + modifiers: customModifiers, } customModifiersGroupElement = createModifierGroup(customGroup, true) createCollapsibles(customModifiersGroupElement) } - } + }, }) })() diff --git a/ui/plugins/ui/jasmine/boot1.js b/ui/plugins/ui/jasmine/boot1.js index 54ccb7d7..39bfe0fa 100644 --- a/ui/plugins/ui/jasmine/boot1.js +++ b/ui/plugins/ui/jasmine/boot1.js @@ -45,7 +45,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. const queryString = new jasmine.QueryString({ getWindowLocation: function() { return window.location - } + }, }) const filterSpecs = !!queryString.getParam("spec") @@ -53,7 +53,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. const config = { stopOnSpecFailure: queryString.getParam("stopOnSpecFailure"), stopSpecOnExpectationFailure: queryString.getParam("stopSpecOnExpectationFailure"), - hideDisabled: queryString.getParam("hideDisabled") + hideDisabled: queryString.getParam("hideDisabled"), } const random = queryString.getParam("random") @@ -89,7 +89,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. return document.createTextNode.apply(document, arguments) }, timer: new jasmine.Timer(), - filterSpecs: filterSpecs + filterSpecs: filterSpecs, }) /** @@ -104,7 +104,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. const specFilter = new jasmine.HtmlSpecFilter({ filterString: function() { return queryString.getParam("spec") - } + }, }) config.specFilter = function(spec) { diff --git a/ui/plugins/ui/jasmine/jasmine-html.js b/ui/plugins/ui/jasmine/jasmine-html.js index 24157bd2..2d09f498 100644 --- a/ui/plugins/ui/jasmine/jasmine-html.js +++ b/ui/plugins/ui/jasmine/jasmine-html.js @@ -106,7 +106,7 @@ jasmineRequire.HtmlReporter = function(j$) { createDom("a", { className: "jasmine-title", href: "http://jasmine.github.io/", - target: "_blank" + target: "_blank", }), createDom("span", { className: "jasmine-version" }, j$.version) ), @@ -163,7 +163,7 @@ jasmineRequire.HtmlReporter = function(j$) { createDom("li", { className: this.displaySpecInCorrectFormat(result), id: "spec_" + result.id, - title: result.fullName + title: result.fullName, }) ) @@ -246,7 +246,7 @@ jasmineRequire.HtmlReporter = function(j$) { "a", { title: "randomized with seed " + order.seed, - href: seedHref(order.seed) + href: seedHref(order.seed), }, order.seed ) @@ -428,7 +428,7 @@ jasmineRequire.HtmlReporter = function(j$) { createDom( "li", { - className: "jasmine-suite-detail jasmine-" + resultNode.result.status + className: "jasmine-suite-detail jasmine-" + resultNode.result.status, }, createDom("a", { href: specHref(resultNode.result) }, resultNode.result.description) ) @@ -454,7 +454,7 @@ jasmineRequire.HtmlReporter = function(j$) { "li", { className: "jasmine-" + resultNode.result.status, - id: "spec-" + resultNode.result.id + id: "spec-" + resultNode.result.id, }, createDom("a", { href: specHref(resultNode.result) }, specDescription) ) @@ -477,7 +477,7 @@ jasmineRequire.HtmlReporter = function(j$) { createDom("input", { className: "jasmine-fail-fast", id: "jasmine-fail-fast", - type: "checkbox" + type: "checkbox", }), createDom( "label", @@ -491,7 +491,7 @@ jasmineRequire.HtmlReporter = function(j$) { createDom("input", { className: "jasmine-throw", id: "jasmine-throw-failures", - type: "checkbox" + type: "checkbox", }), createDom( "label", @@ -505,7 +505,7 @@ jasmineRequire.HtmlReporter = function(j$) { createDom("input", { className: "jasmine-random", id: "jasmine-random-order", - type: "checkbox" + type: "checkbox", }), createDom( "label", @@ -519,7 +519,7 @@ jasmineRequire.HtmlReporter = function(j$) { createDom("input", { className: "jasmine-disabled", id: "jasmine-hide-disabled", - type: "checkbox" + type: "checkbox", }), createDom( "label", @@ -608,7 +608,7 @@ jasmineRequire.HtmlReporter = function(j$) { message: warning, stack: result.deprecationWarnings[i].stack, runnableName: result.fullName, - runnableType: runnableType + runnableType: runnableType, }) } } diff --git a/ui/plugins/ui/jasmine/jasmine.js b/ui/plugins/ui/jasmine/jasmine.js index 470b7503..5bbcadfc 100644 --- a/ui/plugins/ui/jasmine/jasmine.js +++ b/ui/plugins/ui/jasmine/jasmine.js @@ -152,7 +152,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { "toMatch", "toThrow", "toThrowError", - "toThrowMatching" + "toThrowMatching", ], matchers = {} @@ -212,7 +212,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { set: function(newValue) { j$.util.validateTimeout(newValue, "jasmine.DEFAULT_TIMEOUT_INTERVAL") DEFAULT_TIMEOUT_INTERVAL = newValue - } + }, }) j$.getGlobal = function() { @@ -763,7 +763,7 @@ getJasmineRequireObj().Spec = function(j$) { pendingReason: "", duration: null, properties: null, - debugLogs: null + debugLogs: null, } this.reportedDone = false @@ -810,7 +810,7 @@ getJasmineRequireObj().Spec = function(j$) { fn: (done) => { this.timer.start() this.onStart(this, done) - } + }, } const complete = { @@ -827,7 +827,7 @@ getJasmineRequireObj().Spec = function(j$) { this.resultCallback(this.result, done) }, - type: "specCleanup" + type: "specCleanup", } const fns = this.beforeAndAfterFns() @@ -857,7 +857,7 @@ getJasmineRequireObj().Spec = function(j$) { } }, userContext: this.userContext(), - runnableName: this.getFullName.bind(this) + runnableName: this.getFullName.bind(this), } if (this.markedPending || excluded === true) { @@ -881,7 +881,7 @@ getJasmineRequireObj().Spec = function(j$) { pendingReason: this.excludeMessage, duration: null, properties: null, - debugLogs: null + debugLogs: null, } this.markedPending = this.markedExcluding this.reportedDone = false @@ -904,7 +904,7 @@ getJasmineRequireObj().Spec = function(j$) { passed: false, expected: "", actual: "", - error: e + error: e, }, true ) @@ -983,7 +983,7 @@ getJasmineRequireObj().Spec = function(j$) { */ this.result.debugLogs.push({ message: msg, - timestamp: this.timer.elapsed() + timestamp: this.timer.elapsed(), }) } @@ -1035,12 +1035,12 @@ getJasmineRequireObj().Spec = function(j$) { * @returns {string} * @since 2.0.0 */ - getFullName: this.getFullName.bind(this) + getFullName: this.getFullName.bind(this), } } return this.metadata_ - } + }, }) return Spec @@ -1132,7 +1132,7 @@ getJasmineRequireObj().Env = function(j$) { const r = runner.currentRunable() return r ? r.id : null }, - globalErrors + globalErrors, }) let reporter @@ -1236,7 +1236,7 @@ getJasmineRequireObj().Env = function(j$) { * @type Boolean * @default false */ - verboseDeprecations: false + verboseDeprecations: false, } if (!options.suppressLoadErrors) { @@ -1248,7 +1248,7 @@ getJasmineRequireObj().Env = function(j$) { message: message, stack: err && err.stack, filename: filename, - lineno: lineno + lineno: lineno, }) }) } @@ -1267,7 +1267,7 @@ getJasmineRequireObj().Env = function(j$) { "hideDisabled", "stopOnSpecFailure", "stopSpecOnExpectationFailure", - "autoCleanClosures" + "autoCleanClosures", ] booleanProps.forEach(function(prop) { @@ -1337,7 +1337,7 @@ getJasmineRequireObj().Env = function(j$) { matchersUtil: runableResources.makeMatchersUtil(), customMatchers: runableResources.customMatchers(), actual: actual, - addExpectationResult: addExpectationResult + addExpectationResult: addExpectationResult, }) function addExpectationResult(passed, result) { @@ -1356,7 +1356,7 @@ getJasmineRequireObj().Env = function(j$) { passed: false, matcherName: "", expected: "", - actual: "" + actual: "", }) routeLateFailure(result) } @@ -1413,7 +1413,7 @@ getJasmineRequireObj().Env = function(j$) { matchersUtil: runableResources.makeMatchersUtil(), customAsyncMatchers: runableResources.customAsyncMatchers(), actual: actual, - addExpectationResult: addExpectationResult + addExpectationResult: addExpectationResult, }) function addExpectationResult(passed, result) { @@ -1455,7 +1455,7 @@ getJasmineRequireObj().Env = function(j$) { options.clearStack = options.clearStack || clearStack options.timeout = { setTimeout: realSetTimeout, - clearTimeout: realClearTimeout + clearTimeout: realClearTimeout, } options.fail = self.fail options.globalErrors = globalErrors @@ -1476,7 +1476,7 @@ getJasmineRequireObj().Env = function(j$) { onLateError: recordLateError, specResultCallback, specStarted, - queueRunnerFactory + queueRunnerFactory, }) topSuite = suiteBuilder.topSuite const deprecator = new j$.Deprecator(topSuite) @@ -1563,7 +1563,7 @@ getJasmineRequireObj().Env = function(j$) { * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion. * @see async */ - "specDone" + "specDone", ], function(options) { options.SkipPolicy = j$.NeverSkipPolicy @@ -1580,7 +1580,7 @@ getJasmineRequireObj().Env = function(j$) { reporter, queueRunnerFactory, getConfig: () => config, - reportSpecDone + reportSpecDone, }) /** @@ -1709,7 +1709,7 @@ getJasmineRequireObj().Env = function(j$) { expected: "", actual: "", message, - error: null + error: null, }) } @@ -1908,7 +1908,7 @@ getJasmineRequireObj().Env = function(j$) { expected: "", actual: "", message: message, - error: error && error.message ? error : null + error: error && error.message ? error : null, }) if (config.stopSpecOnExpectationFailure) { @@ -2342,7 +2342,7 @@ getJasmineRequireObj().ObjectContaining = function(j$) { if (!j$.isObject_(other)) { return { self: this.jasmineToString(pp), - other: other + other: other, } } @@ -2355,7 +2355,7 @@ getJasmineRequireObj().ObjectContaining = function(j$) { return { self: this.sample, - other: filteredOther + other: filteredOther, } } @@ -2485,7 +2485,7 @@ getJasmineRequireObj().buildExpectationResult = function(j$) { matcherName: options.matcherName, message: message(), stack: options.omitStackTrace ? "" : stack(), - passed: options.passed + passed: options.passed, } if (!result.passed) { @@ -2785,13 +2785,13 @@ getJasmineRequireObj().Clock = function() { setTimeout: global.setTimeout, clearTimeout: global.clearTimeout, setInterval: global.setInterval, - clearInterval: global.clearInterval + clearInterval: global.clearInterval, } const fakeTimingFunctions = { setTimeout: setTimeout, clearTimeout: clearTimeout, setInterval: setInterval, - clearInterval: clearInterval + clearInterval: clearInterval, } let installed = false let delayedFunctionScheduler @@ -3045,7 +3045,7 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { recurring: recurring, params: params, timeoutKey: timeoutKey, - millis: millis + millis: millis, } if (runAtMillis in this.scheduledFunctions_) { @@ -3257,7 +3257,7 @@ getJasmineRequireObj().Deprecator = function(j$) { runnable.addDeprecationWarning({ message: deprecation, - omitStackTrace: options.omitStackTrace || false + omitStackTrace: options.omitStackTrace || false, }) } @@ -3271,7 +3271,7 @@ getJasmineRequireObj().errors = function() { ExpectationFailed.prototype.constructor = ExpectationFailed return { - ExpectationFailed: ExpectationFailed + ExpectationFailed: ExpectationFailed, } } @@ -3286,7 +3286,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) { "lineNumber", "column", "description", - "jasmineMessage" + "jasmineMessage", ] function ExceptionFormatter(options) { @@ -3321,7 +3321,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) { } const lines = this.stack_(error, { - messageHandling: omitMessage ? "omit" : undefined + messageHandling: omitMessage ? "omit" : undefined, }) return lines.join("\n") } @@ -3345,7 +3345,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) { if (error.cause) { const substack = this.stack_(error.cause, { - messageHandling: "require" + messageHandling: "require", }) substack[0] = "Caused by: " + substack[0] lines = lines.concat(substack) @@ -3434,7 +3434,7 @@ getJasmineRequireObj().Expectation = function(j$) { Object.defineProperty(Expectation.prototype, "not", { get: function() { return addFilter(this, syncNegatingFilter) - } + }, }) /** @@ -3503,7 +3503,7 @@ getJasmineRequireObj().Expectation = function(j$) { Object.defineProperty(AsyncExpectation.prototype, "not", { get: function() { return addFilter(this, asyncNegatingFilter) - } + }, }) /** @@ -3521,7 +3521,7 @@ getJasmineRequireObj().Expectation = function(j$) { Object.defineProperty(AsyncExpectation.prototype, "already", { get: function() { return addFilter(this, expectSettledPromiseFilter) - } + }, }) function wrapSyncCompare(name, matcherFactory) { @@ -3584,7 +3584,7 @@ getJasmineRequireObj().Expectation = function(j$) { return matcher.negativeCompare || defaultNegativeCompare }, - buildFailureMessage: negatedFailureMessage + buildFailureMessage: negatedFailureMessage, } const asyncNegatingFilter = { @@ -3595,7 +3595,7 @@ getJasmineRequireObj().Expectation = function(j$) { return matcher.negativeCompare || defaultNegativeCompare }, - buildFailureMessage: negatedFailureMessage + buildFailureMessage: negatedFailureMessage, } const expectSettledPromiseFilter = { @@ -3609,14 +3609,14 @@ getJasmineRequireObj().Expectation = function(j$) { pass: false, message: "Expected a promise to be settled (via " + - "expectAsync(...).already) but it was pending." + "expectAsync(...).already) but it was pending.", } } else { return matcher.compare.apply(null, matcherArgs) } }) } - } + }, } function ContextAddingFilter(message) { @@ -3647,7 +3647,7 @@ getJasmineRequireObj().Expectation = function(j$) { }, addAsyncCoreMatchers: function(matchers) { addCoreMatchers(AsyncExpectation.prototype, matchers, wrapAsyncCompare) - } + }, } } @@ -3686,7 +3686,7 @@ getJasmineRequireObj().ExpectationFilterChain = function() { if (this.filter_ && this.filter_[fname]) { return { found: true, - result: this.filter_[fname].apply(this.filter_, args) + result: this.filter_[fname].apply(this.filter_, args), } } @@ -3699,7 +3699,7 @@ getJasmineRequireObj().ExpectationFilterChain = function() { getJasmineRequireObj().Expector = function(j$) { function Expector(options) { this.matchersUtil = options.matchersUtil || { - buildFailureMessage: function() {} + buildFailureMessage: function() {}, } this.actual = options.actual this.addExpectationResult = options.addExpectationResult || function() {} @@ -3772,7 +3772,7 @@ getJasmineRequireObj().Expector = function(j$) { error: errorForStack ? undefined : result.error, errorForStack: errorForStack || undefined, actual: this.actual, - expected: this.expected // TODO: this may need to be arrayified/sliced + expected: this.expected, // TODO: this may need to be arrayified/sliced }) } @@ -3960,7 +3960,7 @@ getJasmineRequireObj().toBePending = function(j$) { return { pass: false } } ) - } + }, } } } @@ -3991,7 +3991,7 @@ getJasmineRequireObj().toBeRejected = function(j$) { return { pass: true } } ) - } + }, } } } @@ -4029,25 +4029,25 @@ getJasmineRequireObj().toBeRejectedWith = function(j$) { function() { return { pass: false, - message: prefix(false) + " but it was resolved." + message: prefix(false) + " but it was resolved.", } }, function(actualValue) { if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, - message: prefix(true) + "." + message: prefix(true) + ".", } } else { return { pass: false, message: - prefix(false) + " but it was rejected with " + matchersUtil.pp(actualValue) + "." + prefix(false) + " but it was rejected with " + matchersUtil.pp(actualValue) + ".", } } } ) - } + }, } } } @@ -4081,14 +4081,14 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { function() { return { pass: false, - message: "Expected a promise to be rejected but it was resolved." + message: "Expected a promise to be rejected but it was resolved.", } }, function(actualValue) { return matchError(actualValue, expected, matchersUtil) } ) - } + }, } } @@ -4117,14 +4117,14 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { function pass(expected) { return { pass: true, - message: "Expected a promise not to be rejected with " + expected.printValue + ", but it was." + message: "Expected a promise not to be rejected with " + expected.printValue + ", but it was.", } } function fail(expected, message) { return { pass: false, - message: "Expected a promise to be rejected with " + expected.printValue + " but it was " + message + "." + message: "Expected a promise to be rejected with " + expected.printValue + " but it was " + message + ".", } } @@ -4142,7 +4142,7 @@ getJasmineRequireObj().toBeRejectedWithError = function(j$) { return { error: error, message: message, - printValue: j$.fnNameFor(error) + (typeof message === "undefined" ? "" : ": " + matchersUtil.pp(message)) + printValue: j$.fnNameFor(error) + (typeof message === "undefined" ? "" : ": " + matchersUtil.pp(message)), } } @@ -4181,11 +4181,11 @@ getJasmineRequireObj().toBeResolved = function(j$) { "Expected a promise to be resolved but it was " + "rejected with " + matchersUtil.pp(e) + - "." + ".", } } ) - } + }, } } } @@ -4224,23 +4224,24 @@ getJasmineRequireObj().toBeResolvedTo = function(j$) { if (matchersUtil.equals(actualValue, expectedValue)) { return { pass: true, - message: prefix(true) + "." + message: prefix(true) + ".", } } else { return { pass: false, - message: prefix(false) + " but it was resolved to " + matchersUtil.pp(actualValue) + "." + message: + prefix(false) + " but it was resolved to " + matchersUtil.pp(actualValue) + ".", } } }, function(e) { return { pass: false, - message: prefix(false) + " but it was rejected with " + matchersUtil.pp(e) + "." + message: prefix(false) + " but it was rejected with " + matchersUtil.pp(e) + ".", } } ) - } + }, } } } @@ -4718,11 +4719,11 @@ getJasmineRequireObj().MatchersUtil = function(j$) { // For both sets, check they are all contained in the other set const setPairs = [ [valuesA, valuesB], - [valuesB, valuesA] + [valuesB, valuesA], ] const stackPairs = [ [aStack, bStack], - [bStack, aStack] + [bStack, aStack], ] for (let i = 0; result && i < setPairs.length; i++) { const baseValues = setPairs[i][0] @@ -5032,9 +5033,9 @@ getJasmineRequireObj().nothing = function() { return { compare: function() { return { - pass: true + pass: true, } - } + }, } } @@ -5048,7 +5049,7 @@ getJasmineRequireObj().NullDiffBuilder = function(j$) { block() }, setRoots: function() {}, - recordMismatch: function() {} + recordMismatch: function() {}, } } } @@ -5106,7 +5107,7 @@ getJasmineRequireObj().requireAsyncMatchers = function(jRequire, j$) { "toBeRejected", "toBeResolvedTo", "toBeRejectedWith", - "toBeRejectedWithError" + "toBeRejectedWithError", ], matchers = {} @@ -5133,7 +5134,7 @@ getJasmineRequireObj().toBe = function(j$) { return { compare: function(actual, expected) { const result = { - pass: actual === expected + pass: actual === expected, } if (typeof expected === "object") { @@ -5141,7 +5142,7 @@ getJasmineRequireObj().toBe = function(j$) { } return result - } + }, } } @@ -5181,7 +5182,7 @@ getJasmineRequireObj().toBeCloseTo = function() { // regardless of the precision. if (expected === Infinity || expected === -Infinity) { return { - pass: actual === expected + pass: actual === expected, } } @@ -5190,9 +5191,9 @@ getJasmineRequireObj().toBeCloseTo = function() { const maxDelta = Math.pow(10, -precision) / 2 return { - pass: Math.round(delta * pow) <= maxDelta * pow + pass: Math.round(delta * pow) <= maxDelta * pow, } - } + }, } } @@ -5212,9 +5213,9 @@ getJasmineRequireObj().toBeDefined = function() { return { compare: function(actual) { return { - pass: void 0 !== actual + pass: void 0 !== actual, } - } + }, } } @@ -5234,9 +5235,9 @@ getJasmineRequireObj().toBeFalse = function() { return { compare: function(actual) { return { - pass: actual === false + pass: actual === false, } - } + }, } } @@ -5256,9 +5257,9 @@ getJasmineRequireObj().toBeFalsy = function() { return { compare: function(actual) { return { - pass: !actual + pass: !actual, } - } + }, } } @@ -5279,9 +5280,9 @@ getJasmineRequireObj().toBeGreaterThan = function() { return { compare: function(actual, expected) { return { - pass: actual > expected + pass: actual > expected, } - } + }, } } @@ -5302,9 +5303,9 @@ getJasmineRequireObj().toBeGreaterThanOrEqual = function() { return { compare: function(actual, expected) { return { - pass: actual >= expected + pass: actual >= expected, } - } + }, } } @@ -5344,15 +5345,15 @@ getJasmineRequireObj().toBeInstanceOf = function(j$) { if (pass) { return { pass: true, - message: "Expected instance of " + actualType + " not to be an instance of " + expectedType + message: "Expected instance of " + actualType + " not to be an instance of " + expectedType, } } else { return { pass: false, - message: "Expected instance of " + actualType + " to be an instance of " + expectedType + message: "Expected instance of " + actualType + " to be an instance of " + expectedType, } } - } + }, } } @@ -5373,9 +5374,9 @@ getJasmineRequireObj().toBeLessThan = function() { return { compare: function(actual, expected) { return { - pass: actual < expected + pass: actual < expected, } - } + }, } } @@ -5396,9 +5397,9 @@ getJasmineRequireObj().toBeLessThanOrEqual = function() { return { compare: function(actual, expected) { return { - pass: actual <= expected + pass: actual <= expected, } - } + }, } } @@ -5418,7 +5419,7 @@ getJasmineRequireObj().toBeNaN = function(j$) { return { compare: function(actual) { const result = { - pass: actual !== actual + pass: actual !== actual, } if (result.pass) { @@ -5430,7 +5431,7 @@ getJasmineRequireObj().toBeNaN = function(j$) { } return result - } + }, } } @@ -5450,7 +5451,7 @@ getJasmineRequireObj().toBeNegativeInfinity = function(j$) { return { compare: function(actual) { const result = { - pass: actual === Number.NEGATIVE_INFINITY + pass: actual === Number.NEGATIVE_INFINITY, } if (result.pass) { @@ -5462,7 +5463,7 @@ getJasmineRequireObj().toBeNegativeInfinity = function(j$) { } return result - } + }, } } @@ -5482,9 +5483,9 @@ getJasmineRequireObj().toBeNull = function() { return { compare: function(actual) { return { - pass: actual === null + pass: actual === null, } - } + }, } } @@ -5504,7 +5505,7 @@ getJasmineRequireObj().toBePositiveInfinity = function(j$) { return { compare: function(actual) { const result = { - pass: actual === Number.POSITIVE_INFINITY + pass: actual === Number.POSITIVE_INFINITY, } if (result.pass) { @@ -5516,7 +5517,7 @@ getJasmineRequireObj().toBePositiveInfinity = function(j$) { } return result - } + }, } } @@ -5536,9 +5537,9 @@ getJasmineRequireObj().toBeTrue = function() { return { compare: function(actual) { return { - pass: actual === true + pass: actual === true, } - } + }, } } @@ -5558,9 +5559,9 @@ getJasmineRequireObj().toBeTruthy = function() { return { compare: function(actual) { return { - pass: !!actual + pass: !!actual, } - } + }, } } @@ -5580,9 +5581,9 @@ getJasmineRequireObj().toBeUndefined = function() { return { compare: function(actual) { return { - pass: void 0 === actual + pass: void 0 === actual, } - } + }, } } @@ -5604,9 +5605,9 @@ getJasmineRequireObj().toContain = function() { return { compare: function(actual, expected) { return { - pass: matchersUtil.contains(actual, expected) + pass: matchersUtil.contains(actual, expected), } - } + }, } } @@ -5627,7 +5628,7 @@ getJasmineRequireObj().toEqual = function(j$) { return { compare: function(actual, expected) { const result = { - pass: false + pass: false, }, diffBuilder = new j$.DiffBuilder({ prettyPrinter: matchersUtil.pp }) @@ -5637,7 +5638,7 @@ getJasmineRequireObj().toEqual = function(j$) { result.message = diffBuilder.getMessage() return result - } + }, } } @@ -5676,7 +5677,7 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { : "Expected spy " + actual.and.identity + " to have been called." return result - } + }, } } @@ -5759,7 +5760,7 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { } return result - } + }, } } @@ -5808,7 +5809,7 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) { "\n" + "But the actual call was:\n" + prettyPrintedCalls.join(",\n") + - ".\n\n" + ".\n\n", } } @@ -5846,9 +5847,9 @@ getJasmineRequireObj().toHaveBeenCalledOnceWith = function(j$) { " " + util.pp(expectedArgs) + "\n" + - butString() + butString(), } - } + }, } } @@ -5903,7 +5904,7 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { calls + " times." return result - } + }, } } @@ -5992,7 +5993,7 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { } return result - } + }, } } @@ -6019,9 +6020,9 @@ getJasmineRequireObj().toHaveClass = function(j$) { } return { - pass: actual.classList.contains(expected) + pass: actual.classList.contains(expected), } - } + }, } } @@ -6047,7 +6048,7 @@ getJasmineRequireObj().toHaveSize = function(j$) { return { compare: function(actual, expected) { const result = { - pass: false + pass: false, } if (j$.isA_("WeakSet", actual) || j$.isWeakMap(actual) || j$.isDataView(actual)) { @@ -6063,7 +6064,7 @@ getJasmineRequireObj().toHaveSize = function(j$) { } return result - } + }, } } @@ -6132,7 +6133,7 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) { result.message = resultMessage return result - } + }, } } @@ -6162,9 +6163,9 @@ getJasmineRequireObj().toMatch = function(j$) { const regexp = new RegExp(expected) return { - pass: regexp.test(actual) + pass: regexp.test(actual), } - } + }, } } @@ -6234,7 +6235,7 @@ getJasmineRequireObj().toThrow = function(j$) { } return result - } + }, } } @@ -6286,7 +6287,7 @@ getJasmineRequireObj().toThrowError = function(j$) { } return errorMatcher.match(thrown) - } + }, } function getMatcher() { @@ -6317,7 +6318,7 @@ getJasmineRequireObj().toThrowError = function(j$) { return { match: function(error) { return pass("Expected function not to throw an Error, but it threw " + j$.fnNameFor(error) + ".") - } + }, } } @@ -6386,7 +6387,7 @@ getJasmineRequireObj().toThrowError = function(j$) { ) }) } - } + }, } } @@ -6408,14 +6409,14 @@ getJasmineRequireObj().toThrowError = function(j$) { function pass(message) { return { pass: true, - message: message + message: message, } } function fail(message) { return { pass: false, - message: message + message: message, } } @@ -6469,7 +6470,7 @@ getJasmineRequireObj().toThrowMatching = function(j$) { ) }) } - } + }, } function thrownDescription(thrown) { @@ -6484,14 +6485,14 @@ getJasmineRequireObj().toThrowMatching = function(j$) { function pass(message) { return { pass: true, - message: message + message: message, } } function fail(message) { return { pass: false, - message: message + message: message, } } @@ -6999,12 +7000,12 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.userContext = attrs.userContext || new j$.UserContext() this.timeout = attrs.timeout || { setTimeout: setTimeout, - clearTimeout: clearTimeout + clearTimeout: clearTimeout, } this.fail = attrs.fail || emptyFn this.globalErrors = attrs.globalErrors || { pushListener: emptyFn, - popListener: emptyFn + popListener: emptyFn, } const SkipPolicy = attrs.SkipPolicy || j$.NeverSkipPolicy @@ -7277,7 +7278,7 @@ getJasmineRequireObj().ReportDispatcher = function(j$) { "An asynchronous reporter callback called its 'done' callback " + "more than once." ) ) - } + }, }) }) } @@ -7293,13 +7294,13 @@ getJasmineRequireObj().ReportDispatcher = function(j$) { fns.push({ fn: function() { return fn.apply(reporter, thisArgs) - } + }, }) } else { fns.push({ fn: function(done) { return fn.apply(reporter, thisArgs.concat([done])) - } + }, }) } } @@ -7603,13 +7604,13 @@ getJasmineRequireObj().interface = function(jasmine, env) { }, jsApiReporter: new jasmine.JsApiReporter({ - timer: new jasmine.Timer() + timer: new jasmine.Timer(), }), /** * @namespace jasmine */ - jasmine: jasmine + jasmine: jasmine, } /** @@ -7760,7 +7761,7 @@ getJasmineRequireObj().RunableResources = function(j$) { this.spyRegistry = new j$.SpyRegistry({ currentSpies: () => this.spies(), - createSpy: (name, originalFn) => this.spyFactory.createSpy(name, originalFn) + createSpy: (name, originalFn) => this.spyFactory.createSpy(name, originalFn), }) } @@ -7772,7 +7773,7 @@ getJasmineRequireObj().RunableResources = function(j$) { customSpyStrategies: {}, customObjectFormatters: [], defaultSpyStrategy: undefined, - spies: [] + spies: [], }) const parentRes = this.byRunableId_[parentId] @@ -7784,7 +7785,7 @@ getJasmineRequireObj().RunableResources = function(j$) { "customMatchers", "customAsyncMatchers", "customObjectFormatters", - "customSpyStrategies" + "customSpyStrategies", ] for (const k of toClone) { @@ -7865,7 +7866,7 @@ getJasmineRequireObj().RunableResources = function(j$) { if (this.getCurrentRunableId_()) { return new j$.MatchersUtil({ customTesters: this.customEqualityTesters(), - pp: this.makePrettyPrinter() + pp: this.makePrettyPrinter(), }) } else { return new j$.MatchersUtil({ pp: j$.basicPrettyPrinter_ }) @@ -7936,7 +7937,7 @@ getJasmineRequireObj().Runner = function(j$) { const order = new j$.Order({ random: config.random, - seed: j$.isNumber_(config.seed) ? config.seed + "" : config.seed + seed: j$.isNumber_(config.seed) ? config.seed + "" : config.seed, }) const processor = new j$.TreeProcessor({ @@ -7990,7 +7991,7 @@ getJasmineRequireObj().Runner = function(j$) { }, excludeNode: function(spec) { return !config.specFilter(spec) - } + }, }) if (!processor.processTree().valid) { @@ -8016,7 +8017,7 @@ getJasmineRequireObj().Runner = function(j$) { */ await this.reporter_.jasmineStarted({ totalSpecsDefined, - order: order + order: order, }) this.currentlyExecutingSuites_.push(this.topSuite_) @@ -8059,7 +8060,7 @@ getJasmineRequireObj().Runner = function(j$) { incompleteReason: incompleteReason, order: order, failedExpectations: this.topSuite_.result.failedExpectations, - deprecationWarnings: this.topSuite_.result.deprecationWarnings + deprecationWarnings: this.topSuite_.result.deprecationWarnings, } this.topSuite_.reportedDone = true await this.reporter_.jasmineDone(jasmineDoneInfo) @@ -8093,7 +8094,7 @@ getJasmineRequireObj().Runner = function(j$) { message: "Not run because a beforeAll function failed. The " + "beforeAll failure will be reported on the suite that " + - "caused it." + "caused it.", }, true ) @@ -8172,7 +8173,7 @@ getJasmineRequireObj().Spy = function(j$) { const callData = { object: context, invocationOrder: nextOrder(), - args: Array.prototype.slice.apply(args) + args: Array.prototype.slice.apply(args), } callTracker.track(callData) @@ -8194,7 +8195,7 @@ getJasmineRequireObj().Spy = function(j$) { getSpy: function() { return wrapper }, - customStrategies: customStrategies + customStrategies: customStrategies, }, matchersUtil ), @@ -8339,7 +8340,7 @@ getJasmineRequireObj().Spy = function(j$) { strategy = this.strategyFactory() this.strategies.push({ args: args, - strategy: strategy + strategy: strategy, }) } @@ -8368,7 +8369,7 @@ getJasmineRequireObj().SpyFactory = function(j$) { return j$.Spy(name, getMatchersUtil(), { originalFn, customStrategies: getCustomStrategies(), - defaultStrategyFn: getDefaultStrategyFn() + defaultStrategyFn: getDefaultStrategyFn(), }) } @@ -8396,7 +8397,7 @@ getJasmineRequireObj().SpyFactory = function(j$) { const descriptor = { enumerable: true, get: this.createSpy(baseName + "." + properties[i][0] + ".get"), - set: this.createSpy(baseName + "." + properties[i][0] + ".set") + set: this.createSpy(baseName + "." + properties[i][0] + ".set"), } if (properties[i].length > 1) { descriptor.get.and.returnValue(properties[i][1]) @@ -8496,7 +8497,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } currentSpies().push({ - restoreObjectToOriginalState: restoreStrategy + restoreObjectToOriginalState: restoreStrategy, }) obj[methodName] = spiedMethod @@ -8554,7 +8555,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } currentSpies().push({ - restoreObjectToOriginalState: restoreStrategy + restoreObjectToOriginalState: restoreStrategy, }) descriptor[accessType] = spy @@ -8837,7 +8838,7 @@ getJasmineRequireObj().StackTrace = function(j$) { re: /^\s*at ([^\)]+) \(([^\)]+)\)$/, fnIx: 1, fileLineColIx: 2, - style: "v8" + style: "v8", }, // NodeJS alternate form, often mixed in with the Chrome style @@ -8851,8 +8852,8 @@ getJasmineRequireObj().StackTrace = function(j$) { re: /^(?:(([^@\s]+)@)|@)?([^\s]+)$/, fnIx: 2, fileLineColIx: 3, - style: "webkit" - } + style: "webkit", + }, ] // regexes should capture the function name (if any) as group 1 @@ -8876,7 +8877,7 @@ getJasmineRequireObj().StackTrace = function(j$) { raw: line, file: fileLineColMatch[1], line: parseInt(fileLineColMatch[2], 10), - func: overallMatch[pattern.fnIx] + func: overallMatch[pattern.fnIx], } }) @@ -8885,7 +8886,7 @@ getJasmineRequireObj().StackTrace = function(j$) { return { style: style, - frames: frames + frames: frames, } } @@ -8905,7 +8906,7 @@ getJasmineRequireObj().StackTrace = function(j$) { if (len > 0) { return { message: stackLines.slice(0, len).join("\n"), - remainder: stackLines.slice(len) + remainder: stackLines.slice(len), } } } @@ -9049,7 +9050,7 @@ getJasmineRequireObj().Suite = function(j$) { failedExpectations: [], deprecationWarnings: [], duration: null, - properties: null + properties: null, } this.markedPending = this.markedExcluding this.children.forEach(function(child) { @@ -9105,7 +9106,7 @@ getJasmineRequireObj().Suite = function(j$) { passed: false, expected: "", actual: "", - error: arguments[0] + error: arguments[0], } const failedExpectation = j$.buildExpectationResult(data) @@ -9176,7 +9177,7 @@ getJasmineRequireObj().Suite = function(j$) { } return this.metadata_ - } + }, }) /** @@ -9233,7 +9234,7 @@ getJasmineRequireObj().Suite = function(j$) { Object.defineProperty(SuiteMetadata.prototype, "children", { get: function() { return this.suite_.children.map((child) => child.metadata) - } + }, }) function isFailure(args) { @@ -9346,7 +9347,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { this.currentDeclarationSuite_.beforeEach({ fn: beforeEachFunction, - timeout: timeout || 0 + timeout: timeout || 0, }) } @@ -9359,7 +9360,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { this.currentDeclarationSuite_.beforeAll({ fn: beforeAllFunction, - timeout: timeout || 0 + timeout: timeout || 0, }) } @@ -9373,7 +9374,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { afterEachFunction.isCleanup = true this.currentDeclarationSuite_.afterEach({ fn: afterEachFunction, - timeout: timeout || 0 + timeout: timeout || 0, }) } @@ -9386,7 +9387,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { this.currentDeclarationSuite_.afterAll({ fn: afterAllFunction, - timeout: timeout || 0 + timeout: timeout || 0, }) } @@ -9415,7 +9416,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { asyncExpectationFactory: this.suiteAsyncExpectationFactory_, throwOnExpectationFailure: config.stopSpecOnExpectationFailure, autoCleanClosures: config.autoCleanClosures, - onLateError: this.onLateError_ + onLateError: this.onLateError_, }) } @@ -9462,11 +9463,11 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { }, queueableFn: { fn: fn, - timeout: timeout || 0 + timeout: timeout || 0, }, throwOnExpectationFailure: config.stopSpecOnExpectationFailure, autoCleanClosures: config.autoCleanClosures, - timer: new j$.Timer() + timer: new j$.Timer(), }) return spec } @@ -9523,7 +9524,7 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { return { befores: befores.reverse(), - afters: afters + afters: afters, } } } @@ -9614,7 +9615,7 @@ getJasmineRequireObj().TreeProcessor = function() { tree.handleException.apply(tree, arguments) }, onComplete: resolve, - onMultipleDone: tree.onMultipleDone ? tree.onMultipleDone.bind(tree) : null + onMultipleDone: tree.onMultipleDone ? tree.onMultipleDone.bind(tree) : null, }) }) } @@ -9645,9 +9646,9 @@ getJasmineRequireObj().TreeProcessor = function() { owner: node, nodes: [node], min: startingMin(executableIndex), - max: startingMax(executableIndex) - } - ] + max: startingMax(executableIndex), + }, + ], } } else { let hasExecutableChild = false @@ -9670,7 +9671,7 @@ getJasmineRequireObj().TreeProcessor = function() { stats[node.id] = { excluded: parentExcluded, - willExecute: hasExecutableChild + willExecute: hasExecutableChild, } segmentChildren(node, orderedChildren, stats[node.id], executableIndex) @@ -9695,7 +9696,7 @@ getJasmineRequireObj().TreeProcessor = function() { owner: node, nodes: [], min: startingMin(executableIndex), - max: startingMax(executableIndex) + max: startingMax(executableIndex), }, result = [currentSegment], lastMax = defaultMax, @@ -9716,7 +9717,7 @@ getJasmineRequireObj().TreeProcessor = function() { owner: node, nodes: [], min: defaultMin, - max: defaultMax + max: defaultMax, } result.push(currentSegment) } @@ -9763,7 +9764,7 @@ getJasmineRequireObj().TreeProcessor = function() { const onStart = { fn: function(next) { nodeStart(node, next) - } + }, } queueRunnerFactory({ @@ -9779,15 +9780,15 @@ getJasmineRequireObj().TreeProcessor = function() { onException: function() { node.handleException.apply(node, arguments) }, - onMultipleDone: node.onMultipleDone ? node.onMultipleDone.bind(node) : null + onMultipleDone: node.onMultipleDone ? node.onMultipleDone.bind(node) : null, }) - } + }, } } else { return { fn: function(done) { node.execute(queueRunnerFactory, done, stats[node.id].excluded, failSpecWithNoExpectations) - } + }, } } } diff --git a/ui/plugins/ui/jasmineSpec.js b/ui/plugins/ui/jasmineSpec.js index a0d50fe2..9637e9ac 100644 --- a/ui/plugins/ui/jasmineSpec.js +++ b/ui/plugins/ui/jasmineSpec.js @@ -9,11 +9,11 @@ beforeEach(function() { return { compare: function(actual, expected) { return { - pass: expected.includes(actual) + pass: expected.includes(actual), } - } + }, } - } + }, }) }) describe("stable-diffusion-ui", function() { @@ -116,7 +116,7 @@ describe("stable-diffusion-ui", function() { value["foo"] = "bar" } return { value, done } - } + }, }) const gen2 = SD.Task.asGenerator({ generator: gen1, @@ -128,7 +128,7 @@ describe("stable-diffusion-ui", function() { value.test = 2 * value.test } return { value, done } - } + }, }) expect(await SD.Task.enqueue(gen2)).toEqual({ test: 32, foo: "bar" }) }) @@ -153,7 +153,7 @@ describe("stable-diffusion-ui", function() { expect(typeof missing).toBe("undefined") return { foo: "bar" } }, - dependencies: ["ctx", "missing", "one", "foo"] + dependencies: ["ctx", "missing", "one", "foo"], } ) const fooObj = cont.get("foo") @@ -180,7 +180,7 @@ describe("stable-diffusion-ui", function() { let res = await fetch("/render", { method: "POST", headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", }, body: JSON.stringify({ prompt: "a photograph of an astronaut riding a horse", @@ -199,8 +199,8 @@ describe("stable-diffusion-ui", function() { show_only_filtered_image: true, output_format: "jpeg", - session_id: JASMINE_SESSION_ID - }) + session_id: JASMINE_SESSION_ID, + }), }) expect(res.ok).toBeTruthy() const renderRequest = await res.json() @@ -283,7 +283,7 @@ describe("stable-diffusion-ui", function() { show_only_filtered_image: false, //"use_face_correction": 'GFPGANv1.3', use_upscale: "RealESRGAN_x4plus", - session_id: JASMINE_SESSION_ID + session_id: JASMINE_SESSION_ID, }, function(event) { console.log(this, event) @@ -314,7 +314,7 @@ describe("stable-diffusion-ui", function() { height: 128, seed: SD.MAX_SEED_VALUE, num_inference_steps: 10, - session_id: JASMINE_SESSION_ID + session_id: JASMINE_SESSION_ID, }) expect(renderTask.status).toBe(SD.TaskStatus.init) @@ -326,7 +326,7 @@ describe("stable-diffusion-ui", function() { await renderTask.waitUntil({ state: SD.TaskStatus.processing, - callback: () => console.log("Waiting for render task to start...") + callback: () => console.log("Waiting for render task to start..."), }) expect(renderTask.status).toBe(SD.TaskStatus.processing) @@ -362,7 +362,7 @@ describe("stable-diffusion-ui", function() { show_only_filtered_image: false, //"use_face_correction": 'GFPGANv1.3', use_upscale: "RealESRGAN_x4plus", - session_id: JASMINE_SESSION_ID + session_id: JASMINE_SESSION_ID, }) await renderTask.enqueue(function(event) { console.log(this, event) diff --git a/ui/plugins/ui/merge.plugin.js b/ui/plugins/ui/merge.plugin.js index 1364d0a3..5ce97b2d 100644 --- a/ui/plugins/ui/merge.plugin.js +++ b/ui/plugins/ui/merge.plugin.js @@ -424,7 +424,7 @@ let res = await fetch("/model/merge", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify(request) + body: JSON.stringify(request), }) const data = await res.json() addLogMessage(JSON.stringify(data)) @@ -445,6 +445,6 @@ hypernetworkModelField.innerHTML = "" await getModels() }) - } + }, }) })() diff --git a/ui/plugins/ui/modifiers-toggle.plugin.js b/ui/plugins/ui/modifiers-toggle.plugin.js index 0d026bd3..74b3666e 100644 --- a/ui/plugins/ui/modifiers-toggle.plugin.js +++ b/ui/plugins/ui/modifiers-toggle.plugin.js @@ -21,7 +21,7 @@ }) observer.observe(editorModifierTagsList, { - childList: true + childList: true, }) function ModifierToggle() { diff --git a/ui/plugins/ui/release-notes.plugin.js b/ui/plugins/ui/release-notes.plugin.js index 418a18d6..fdd37eb4 100644 --- a/ui/plugins/ui/release-notes.plugin.js +++ b/ui/plugins/ui/release-notes.plugin.js @@ -48,6 +48,6 @@ return marked.parse(releaseNotes) } - } + }, }) })() diff --git a/ui/plugins/ui/selftest.plugin.js b/ui/plugins/ui/selftest.plugin.js index 23eefdf1..b615852d 100644 --- a/ui/plugins/ui/selftest.plugin.js +++ b/ui/plugins/ui/selftest.plugin.js @@ -16,7 +16,7 @@ stopSpecOnExpectationFailure: "true", stopOnSpecFailure: "false", random: "false", - hideDisabled: "false" + hideDisabled: "false", } const optStr = Object.entries(options) .map(([key, val]) => `${key}=${val}`) From a46ff731d8b9d849c03a3acd393d8250a228d05e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 16:03:35 +0530 Subject: [PATCH 27/92] sdkit 1.0.82 - VAE slicing for pytorch 2.0, don't fail to hash files smaller than 3 MB --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 38545715..8ff1ffdb 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.81", + "sdkit": "1.0.82", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 07f52c38ef1ec87cf0b1a598d9c8fdefcf33a6ae Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 16:35:30 +0530 Subject: [PATCH 28/92] sdkit 1.0.83 - formatting --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 8ff1ffdb..c8955e0a 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.82", + "sdkit": "1.0.83", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From d18cefc519640c3cdafac8f1066c8e574e39a37d Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 16:38:55 +0530 Subject: [PATCH 29/92] Formatting --- ui/easydiffusion/app.py | 101 +++++++++++++++++++-------- ui/easydiffusion/device_manager.py | 17 +++-- ui/easydiffusion/model_manager.py | 34 ++++++--- ui/easydiffusion/renderer.py | 44 ++++++++---- ui/easydiffusion/server.py | 38 ++++++---- ui/easydiffusion/task_manager.py | 24 ++++--- ui/easydiffusion/types.py | 3 +- ui/easydiffusion/utils/save_utils.py | 57 +++++++++------ 8 files changed, 213 insertions(+), 105 deletions(-) diff --git a/ui/easydiffusion/app.py b/ui/easydiffusion/app.py index 658de841..da755c21 100644 --- a/ui/easydiffusion/app.py +++ b/ui/easydiffusion/app.py @@ -1,17 +1,15 @@ +import json +import logging import os import socket import sys -import json import traceback -import logging -import shlex import urllib -from rich.logging import RichHandler - -from sdkit.utils import log as sdkit_log # hack, so we can overwrite the log config from easydiffusion import task_manager from easydiffusion.utils import log +from rich.logging import RichHandler +from sdkit.utils import log as sdkit_log # hack, so we can overwrite the log config # Remove all handlers associated with the root logger object. for handler in logging.root.handlers[:]: @@ -55,10 +53,34 @@ APP_CONFIG_DEFAULTS = { }, } -IMAGE_EXTENSIONS = [".png", ".apng", ".jpg", ".jpeg", ".jfif", ".pjpeg", ".pjp", ".jxl", ".gif", ".webp", ".avif", ".svg"] +IMAGE_EXTENSIONS = [ + ".png", + ".apng", + ".jpg", + ".jpeg", + ".jfif", + ".pjpeg", + ".pjp", + ".jxl", + ".gif", + ".webp", + ".avif", + ".svg", +] CUSTOM_MODIFIERS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "modifiers")) -CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS=[".portrait", "_portrait", " portrait", "-portrait"] -CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS=[".landscape", "_landscape", " landscape", "-landscape"] +CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS = [ + ".portrait", + "_portrait", + " portrait", + "-portrait", +] +CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS = [ + ".landscape", + "_landscape", + " landscape", + "-landscape", +] + def init(): os.makedirs(USER_UI_PLUGINS_DIR, exist_ok=True) @@ -84,7 +106,7 @@ def getConfig(default_val=APP_CONFIG_DEFAULTS): if os.getenv("SD_UI_BIND_IP") is not None: config["net"]["listen_to_network"] = os.getenv("SD_UI_BIND_IP") == "0.0.0.0" return config - except Exception as e: + except Exception: log.warn(traceback.format_exc()) return default_val @@ -97,6 +119,7 @@ def setConfig(config): except: log.error(traceback.format_exc()) + def save_to_config(ckpt_model_name, vae_model_name, hypernetwork_model_name, vram_usage_level): config = getConfig() if "model" not in config: @@ -191,11 +214,12 @@ def open_browser(): webbrowser.open(f"http://localhost:{port}") + def get_image_modifiers(): modifiers_json_path = os.path.join(SD_UI_DIR, "modifiers.json") modifier_categories = {} - original_category_order=[] + original_category_order = [] with open(modifiers_json_path, "r", encoding="utf-8") as f: modifiers_file = json.load(f) @@ -205,14 +229,14 @@ def get_image_modifiers(): # convert modifiers from a list of objects to a dict of dicts for category_item in modifiers_file: - category_name = category_item['category'] + category_name = category_item["category"] original_category_order.append(category_name) category = {} - for modifier_item in category_item['modifiers']: + for modifier_item in category_item["modifiers"]: modifier = {} - for preview_item in modifier_item['previews']: - modifier[preview_item['name']] = preview_item['path'] - category[modifier_item['modifier']] = modifier + for preview_item in modifier_item["previews"]: + modifier[preview_item["name"]] = preview_item["path"] + category[modifier_item["modifier"]] = modifier modifier_categories[category_name] = category def scan_directory(directory_path: str, category_name="Modifiers"): @@ -225,12 +249,27 @@ def get_image_modifiers(): modifier_name = entry.name[: -len(file_extension[0])] modifier_path = f"custom/{entry.path[len(CUSTOM_MODIFIERS_DIR) + 1:]}" # URL encode path segments - modifier_path = "/".join(map(lambda segment: urllib.parse.quote(segment), modifier_path.split("/"))) + modifier_path = "/".join( + map( + lambda segment: urllib.parse.quote(segment), + modifier_path.split("/"), + ) + ) is_portrait = True is_landscape = True - portrait_extension = list(filter(lambda e: modifier_name.lower().endswith(e), CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS)) - landscape_extension = list(filter(lambda e: modifier_name.lower().endswith(e), CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS)) + portrait_extension = list( + filter( + lambda e: modifier_name.lower().endswith(e), + CUSTOM_MODIFIERS_PORTRAIT_EXTENSIONS, + ) + ) + landscape_extension = list( + filter( + lambda e: modifier_name.lower().endswith(e), + CUSTOM_MODIFIERS_LANDSCAPE_EXTENSIONS, + ) + ) if len(portrait_extension) > 0: is_landscape = False @@ -238,24 +277,24 @@ def get_image_modifiers(): elif len(landscape_extension) > 0: is_portrait = False modifier_name = modifier_name[: -len(landscape_extension[0])] - - if (category_name not in modifier_categories): + + if category_name not in modifier_categories: modifier_categories[category_name] = {} - + category = modifier_categories[category_name] - if (modifier_name not in category): + if modifier_name not in category: category[modifier_name] = {} - if (is_portrait or "portrait" not in category[modifier_name]): + if is_portrait or "portrait" not in category[modifier_name]: category[modifier_name]["portrait"] = modifier_path - - if (is_landscape or "landscape" not in category[modifier_name]): + + if is_landscape or "landscape" not in category[modifier_name]: category[modifier_name]["landscape"] = modifier_path elif entry.is_dir(): scan_directory( entry.path, - entry.name if directory_path==CUSTOM_MODIFIERS_DIR else f"{category_name}/{entry.name}", + entry.name if directory_path == CUSTOM_MODIFIERS_DIR else f"{category_name}/{entry.name}", ) scan_directory(CUSTOM_MODIFIERS_DIR) @@ -268,12 +307,12 @@ def get_image_modifiers(): # convert the modifiers back into a list of objects modifier_categories_list = [] for category_name in [*original_category_order, *custom_categories]: - category = { 'category': category_name, 'modifiers': [] } + category = {"category": category_name, "modifiers": []} for modifier_name in sorted(modifier_categories[category_name].keys(), key=str.casefold): - modifier = { 'modifier': modifier_name, 'previews': [] } + modifier = {"modifier": modifier_name, "previews": []} for preview_name, preview_path in modifier_categories[category_name][modifier_name].items(): - modifier['previews'].append({ 'name': preview_name, 'path': preview_path }) - category['modifiers'].append(modifier) + modifier["previews"].append({"name": preview_name, "path": preview_path}) + category["modifiers"].append(modifier) modifier_categories_list.append(category) return modifier_categories_list diff --git a/ui/easydiffusion/device_manager.py b/ui/easydiffusion/device_manager.py index 18069a82..59c07ea3 100644 --- a/ui/easydiffusion/device_manager.py +++ b/ui/easydiffusion/device_manager.py @@ -1,9 +1,9 @@ import os import platform -import torch -import traceback import re +import traceback +import torch from easydiffusion.utils import log """ @@ -98,8 +98,8 @@ def auto_pick_devices(currently_active_devices): continue mem_free, mem_total = torch.cuda.mem_get_info(device) - mem_free /= float(10**9) - mem_total /= float(10**9) + mem_free /= float(10 ** 9) + mem_total /= float(10 ** 9) device_name = torch.cuda.get_device_name(device) log.debug( f"{device} detected: {device_name} - Memory (free/total): {round(mem_free, 2)}Gb / {round(mem_total, 2)}Gb" @@ -118,7 +118,10 @@ def auto_pick_devices(currently_active_devices): # These already-running devices probably aren't terrible, since they were picked in the past. # Worst case, the user can restart the program and that'll get rid of them. devices = list( - filter((lambda x: x["mem_free"] > mem_free_threshold or x["device"] in currently_active_devices), devices) + filter( + (lambda x: x["mem_free"] > mem_free_threshold or x["device"] in currently_active_devices), + devices, + ) ) devices = list(map(lambda x: x["device"], devices)) return devices @@ -178,7 +181,7 @@ def get_max_vram_usage_level(device): else: return "high" - mem_total /= float(10**9) + mem_total /= float(10 ** 9) if mem_total < 4.5: return "low" elif mem_total < 6.5: @@ -220,7 +223,7 @@ def is_device_compatible(device): # Memory check try: _, mem_total = torch.cuda.mem_get_info(device) - mem_total /= float(10**9) + mem_total /= float(10 ** 9) if mem_total < 3.0: if is_device_compatible.history.get(device) == None: log.warn(f"GPU {device} with less than 3 GB of VRAM is not compatible with Stable Diffusion") diff --git a/ui/easydiffusion/model_manager.py b/ui/easydiffusion/model_manager.py index dc727eeb..7bf56575 100644 --- a/ui/easydiffusion/model_manager.py +++ b/ui/easydiffusion/model_manager.py @@ -3,11 +3,17 @@ import os from easydiffusion import app from easydiffusion.types import TaskData from easydiffusion.utils import log - from sdkit import Context -from sdkit.models import load_model, unload_model, scan_model +from sdkit.models import load_model, scan_model, unload_model -KNOWN_MODEL_TYPES = ["stable-diffusion", "vae", "hypernetwork", "gfpgan", "realesrgan", "lora"] +KNOWN_MODEL_TYPES = [ + "stable-diffusion", + "vae", + "hypernetwork", + "gfpgan", + "realesrgan", + "lora", +] MODEL_EXTENSIONS = { "stable-diffusion": [".ckpt", ".safetensors"], "vae": [".vae.pt", ".ckpt", ".safetensors"], @@ -44,13 +50,15 @@ def load_default_models(context: Context): load_model( context, model_type, - scan_model = context.model_paths[model_type] != None and not context.model_paths[model_type].endswith('.safetensors') + scan_model=context.model_paths[model_type] != None + and not context.model_paths[model_type].endswith(".safetensors"), ) except Exception as e: log.error(f"[red]Error while loading {model_type} model: {context.model_paths[model_type]}[/red]") log.exception(e) del context.model_paths[model_type] + def unload_all(context: Context): for model_type in KNOWN_MODEL_TYPES: unload_model(context, model_type) @@ -170,13 +178,23 @@ def is_malicious_model(file_path): if scan_result.issues_count > 0 or scan_result.infected_files > 0: log.warn( ":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) + % ( + file_path, + scan_result.scanned_files, + scan_result.issues_count, + scan_result.infected_files, + ) ) return True else: log.debug( "Scan %s: [green]%d scanned, %d issue, %d infected.[/green]" - % (file_path, scan_result.scanned_files, scan_result.issues_count, scan_result.infected_files) + % ( + file_path, + scan_result.scanned_files, + scan_result.issues_count, + scan_result.infected_files, + ) ) return False except Exception as e: @@ -204,13 +222,13 @@ def getModels(): class MaliciousModelException(Exception): "Raised when picklescan reports a problem with a model" - pass def scan_directory(directory, suffixes, directoriesFirst: bool = True): nonlocal models_scanned tree = [] for entry in sorted( - os.scandir(directory), key=lambda entry: (entry.is_file() == directoriesFirst, entry.name.lower()) + os.scandir(directory), + key=lambda entry: (entry.is_file() == directoriesFirst, entry.name.lower()), ): if entry.is_file(): matching_suffix = list(filter(lambda s: entry.name.endswith(s), suffixes)) diff --git a/ui/easydiffusion/renderer.py b/ui/easydiffusion/renderer.py index 8a155f82..6c10dca1 100644 --- a/ui/easydiffusion/renderer.py +++ b/ui/easydiffusion/renderer.py @@ -1,21 +1,22 @@ -import queue -import time import json import pprint +import queue +import time from easydiffusion import device_manager -from easydiffusion.types import TaskData, Response, Image as ResponseImage, UserInitiatedStop, GenerateImageRequest -from easydiffusion.utils import get_printable_request, save_images_to_disk, log - +from easydiffusion.types import GenerateImageRequest +from easydiffusion.types import Image as ResponseImage +from easydiffusion.types import Response, TaskData, UserInitiatedStop +from easydiffusion.utils import get_printable_request, log, save_images_to_disk from sdkit import Context -from sdkit.generate import generate_images from sdkit.filter import apply_filters +from sdkit.generate import generate_images from sdkit.utils import ( - img_to_buffer, - img_to_base64_str, - latent_samples_to_images, diffusers_latent_samples_to_images, gc, + img_to_base64_str, + img_to_buffer, + latent_samples_to_images, ) context = Context() # thread-local @@ -43,14 +44,22 @@ def init(device): def make_images( - req: GenerateImageRequest, task_data: TaskData, data_queue: queue.Queue, task_temp_images: list, step_callback + req: GenerateImageRequest, + task_data: TaskData, + data_queue: queue.Queue, + task_temp_images: list, + step_callback, ): context.stop_processing = False print_task_info(req, task_data) images, seeds = make_images_internal(req, task_data, data_queue, task_temp_images, step_callback) - res = Response(req, task_data, images=construct_response(images, seeds, task_data, base_seed=req.seed)) + res = Response( + req, + task_data, + images=construct_response(images, seeds, task_data, base_seed=req.seed), + ) res = res.json() data_queue.put(json.dumps(res)) log.info("Task completed") @@ -66,7 +75,11 @@ def print_task_info(req: GenerateImageRequest, task_data: TaskData): def make_images_internal( - req: GenerateImageRequest, task_data: TaskData, data_queue: queue.Queue, task_temp_images: list, step_callback + req: GenerateImageRequest, + task_data: TaskData, + data_queue: queue.Queue, + task_temp_images: list, + step_callback, ): images, user_stopped = generate_images_internal( req, @@ -155,7 +168,12 @@ def filter_images(task_data: TaskData, images: list, user_stopped): def construct_response(images: list, seeds: list, task_data: TaskData, base_seed: int): return [ ResponseImage( - data=img_to_base64_str(img, task_data.output_format, task_data.output_quality, task_data.output_lossless), + data=img_to_base64_str( + img, + task_data.output_format, + task_data.output_quality, + task_data.output_lossless, + ), seed=seed, ) for img, seed in zip(images, seeds) diff --git a/ui/easydiffusion/server.py b/ui/easydiffusion/server.py index 92453917..a1aab6c0 100644 --- a/ui/easydiffusion/server.py +++ b/ui/easydiffusion/server.py @@ -2,28 +2,30 @@ Notes: async endpoints always run on the main thread. Without they run on the thread pool. """ +import datetime +import mimetypes import os import traceback -import datetime from typing import List, Union +from easydiffusion import app, model_manager, task_manager +from easydiffusion.types import GenerateImageRequest, MergeRequest, TaskData +from easydiffusion.utils import log from fastapi import FastAPI, HTTPException from fastapi.staticfiles import StaticFiles -from starlette.responses import FileResponse, JSONResponse, StreamingResponse from pydantic import BaseModel, Extra - -from easydiffusion import app, model_manager, task_manager -from easydiffusion.types import TaskData, GenerateImageRequest, MergeRequest -from easydiffusion.utils import log - -import mimetypes +from starlette.responses import FileResponse, JSONResponse, StreamingResponse log.info(f"started in {app.SD_DIR}") log.info(f"started at {datetime.datetime.now():%x %X}") server_api = FastAPI() -NOCACHE_HEADERS = {"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"} +NOCACHE_HEADERS = { + "Cache-Control": "no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + "Expires": "0", +} class NoCacheStaticFiles(StaticFiles): @@ -65,11 +67,17 @@ def init(): name="custom-thumbnails", ) - server_api.mount("/media", NoCacheStaticFiles(directory=os.path.join(app.SD_UI_DIR, "media")), name="media") + server_api.mount( + "/media", + NoCacheStaticFiles(directory=os.path.join(app.SD_UI_DIR, "media")), + name="media", + ) for plugins_dir, dir_prefix in app.UI_PLUGINS_SOURCES: server_api.mount( - f"/plugins/{dir_prefix}", NoCacheStaticFiles(directory=plugins_dir), name=f"plugins-{dir_prefix}" + f"/plugins/{dir_prefix}", + NoCacheStaticFiles(directory=plugins_dir), + name=f"plugins-{dir_prefix}", ) @server_api.post("/app_config") @@ -246,8 +254,8 @@ def render_internal(req: dict): def model_merge_internal(req: dict): try: - from sdkit.train import merge_models from easydiffusion.utils.save_utils import filename_regex + from sdkit.train import merge_models mergeReq: MergeRequest = MergeRequest.parse_obj(req) @@ -255,7 +263,11 @@ def model_merge_internal(req: dict): model_manager.resolve_model_to_use(mergeReq.model0, "stable-diffusion"), model_manager.resolve_model_to_use(mergeReq.model1, "stable-diffusion"), mergeReq.ratio, - os.path.join(app.MODELS_DIR, "stable-diffusion", filename_regex.sub("_", mergeReq.out_path)), + os.path.join( + app.MODELS_DIR, + "stable-diffusion", + filename_regex.sub("_", mergeReq.out_path), + ), mergeReq.use_fp16, ) return JSONResponse({"status": "OK"}, headers=NOCACHE_HEADERS) diff --git a/ui/easydiffusion/task_manager.py b/ui/easydiffusion/task_manager.py index 91adc04b..450636cf 100644 --- a/ui/easydiffusion/task_manager.py +++ b/ui/easydiffusion/task_manager.py @@ -9,14 +9,16 @@ import traceback TASK_TTL = 15 * 60 # seconds, Discard last session's task timeout -import torch -import queue, threading, time, weakref +import queue +import threading +import time +import weakref from typing import Any, Hashable +import torch from easydiffusion import device_manager -from easydiffusion.types import TaskData, GenerateImageRequest +from easydiffusion.types import GenerateImageRequest, TaskData from easydiffusion.utils import log - from sdkit.utils import gc THREAD_NAME_PREFIX = "" @@ -167,7 +169,7 @@ class DataCache: raise Exception("DataCache.put" + ERR_LOCK_FAILED) try: self._base[key] = (self._get_ttl_time(ttl), value) - except Exception as e: + except Exception: log.error(traceback.format_exc()) return False else: @@ -264,7 +266,7 @@ def thread_get_next_task(): def thread_render(device): global current_state, current_state_error - from easydiffusion import renderer, model_manager + from easydiffusion import model_manager, renderer try: renderer.init(device) @@ -337,7 +339,11 @@ def thread_render(device): current_state = ServerStates.Rendering task.response = renderer.make_images( - task.render_request, task.task_data, task.buffer_queue, task.temp_images, step_callback + task.render_request, + task.task_data, + task.buffer_queue, + task.temp_images, + step_callback, ) # Before looping back to the generator, mark cache as still alive. task_cache.keep(id(task), TASK_TTL) @@ -392,8 +398,8 @@ def get_devices(): return {"name": device_manager.get_processor_name()} mem_free, mem_total = torch.cuda.mem_get_info(device) - mem_free /= float(10**9) - mem_total /= float(10**9) + mem_free /= float(10 ** 9) + mem_total /= float(10 ** 9) return { "name": torch.cuda.get_device_name(device), diff --git a/ui/easydiffusion/types.py b/ui/easydiffusion/types.py index bbec0afa..7462355f 100644 --- a/ui/easydiffusion/types.py +++ b/ui/easydiffusion/types.py @@ -1,6 +1,7 @@ -from pydantic import BaseModel from typing import Any +from pydantic import BaseModel + class GenerateImageRequest(BaseModel): prompt: str = "" diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index 384794d1..a7043f27 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -1,14 +1,13 @@ import os -import time import re +import time +from datetime import datetime +from functools import reduce 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 easydiffusion.types import GenerateImageRequest, TaskData from numpy import base_repr +from sdkit.utils import save_dicts, save_images filename_regex = re.compile("[^a-zA-Z0-9._-]") img_number_regex = re.compile("([0-9]{5,})") @@ -50,6 +49,7 @@ other_placeholders = { "$s": lambda req, task_data: str(req.seed), } + class ImageNumber: _factory = None _evaluated = False @@ -57,12 +57,14 @@ class ImageNumber: 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): + +def format_placeholders(format: str, req: GenerateImageRequest, task_data: TaskData, now=None): if now is None: now = time.time() @@ -75,10 +77,12 @@ def format_placeholders(format: str, req: GenerateImageRequest, task_data: TaskD 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, @@ -88,19 +92,22 @@ def format_file_name( 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 + 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() app_config = app.getConfig() @@ -126,7 +133,7 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR output_lossless=task_data.output_lossless, ) if task_data.metadata_output_format: - for metadata_output_format in task_data.metadata_output_format.split(','): + for metadata_output_format in task_data.metadata_output_format.split(","): if metadata_output_format.lower() in ["json", "txt", "embed"]: save_dicts( metadata_entries, @@ -142,7 +149,8 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR task_data, file_number, now=now, - suffix="filtered") + suffix="filtered", + ) save_images( images, @@ -233,27 +241,28 @@ def make_filename_callback( 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') - + + 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): @@ -267,13 +276,15 @@ def _calculate_img_number(save_dir_path: str, task_data: TaskData): _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)) From 9af511e457cf9a126af0020513632d35e5a577e7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 16:42:38 +0530 Subject: [PATCH 30/92] Check prettier style in a github action --- .github/workflows/prettier.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/prettier.yml diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 00000000..c76c6648 --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,22 @@ +name: Check JavaScript for conformance with Prettier + +on: + push: + pull_request: + +jobs: + prettier: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v2 + - uses: actions/cache@v2 + name: Configure npm caching + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/workflows/prettier.yml') }} + restore-keys: | + ${{ runner.os }}-npm- + - name: Run prettier + run: |- + npx prettier --check "./**/*.js" ui \ No newline at end of file From 1381be16ad7a669797fa6df460e9543327eb0c24 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 17:55:03 +0530 Subject: [PATCH 31/92] Fix formatting --- ui/index.html | 2 +- ui/media/css/searchable-models.css | 2 +- ui/media/css/themes.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/index.html b/ui/index.html index 3ba84e1a..da84242d 100644 --- a/ui/index.html +++ b/ui/index.html @@ -139,7 +139,7 @@ --> - + Click to learn more about VAEs diff --git a/ui/media/css/searchable-models.css b/ui/media/css/searchable-models.css index 06d24acb..5eecd1e0 100644 --- a/ui/media/css/searchable-models.css +++ b/ui/media/css/searchable-models.css @@ -58,7 +58,7 @@ font-size: 10pt; font-weight: normal; transition: none; - transition:property: none; + transition-property: none; cursor: default; } diff --git a/ui/media/css/themes.css b/ui/media/css/themes.css index 053199f8..e9eca84f 100644 --- a/ui/media/css/themes.css +++ b/ui/media/css/themes.css @@ -33,7 +33,7 @@ --input-height: 18px; --tertiary-background-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (2 * var(--value-step)))); --tertiary-border-color: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (3 * var(--value-step)))); - --tertiary-color: var(--input-text-color) + --tertiary-color: var(--input-text-color); /* Main theme color, hex color fallback. */ --theme-color-fallback: #673AB6; From 039395f221a12f7bdc2878f083393708d8b42cf3 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 17:57:26 +0530 Subject: [PATCH 32/92] Fix formatting --- ui/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/index.html b/ui/index.html index da84242d..869d4c97 100644 --- a/ui/index.html +++ b/ui/index.html @@ -135,7 +135,7 @@ Click to learn more about custom models - @@ -217,14 +217,14 @@
- +
- + From 03fedfd0d5e08b8e8e1b9c5d8289b86fdeca1fc5 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 18:07:43 +0530 Subject: [PATCH 33/92] fix formatting --- ui/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 869d4c97..3db86593 100644 --- a/ui/index.html +++ b/ui/index.html @@ -238,7 +238,7 @@ - + From 2e362d57eb777314d998ace14bd4a32a5bf72255 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 18:10:58 +0530 Subject: [PATCH 34/92] Adjust prettier config --- .github/workflows/prettier.yml | 2 +- .prettierignore | 8 ++------ package.json | 3 ++- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index c76c6648..2ea9aab9 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -19,4 +19,4 @@ jobs: ${{ runner.os }}-npm- - name: Run prettier run: |- - npx prettier --check "./**/*.js" ui \ No newline at end of file + npm run prettier-check \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 1f28e901..60ba6412 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,8 +1,4 @@ *.min.* *.py -/* -!/ui -/ui/easydiffusion -/ui/hotfix -!/ui/plugins -!/ui/media \ No newline at end of file +*.json +*.html diff --git a/package.json b/package.json index c9c03893..388b29c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "scripts": { - "prettier": "prettier --write \"./**/*.js\"" + "prettier-fix": "npx prettier --write ui/**", + "prettier-check": "npx prettier --check ui/**" }, "devDependencies": { "prettier": "^1.19.1" From da3f894ed47adf31508bcca257ec6c44413fb017 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 18:13:38 +0530 Subject: [PATCH 35/92] another --- .prettierignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.prettierignore b/.prettierignore index 60ba6412..b0f8227f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,8 @@ *.py *.json *.html +/* +!/ui +/ui/easydiffusion +!/ui/plugins +!/ui/media \ No newline at end of file From 70a37fda573ae22030eee447e8b5a7fcf2abb7d9 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Apr 2023 18:15:57 +0530 Subject: [PATCH 36/92] prettier --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 388b29c8..cedf812b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "scripts": { - "prettier-fix": "npx prettier --write ui/**", - "prettier-check": "npx prettier --check ui/**" + "prettier-fix": "npx prettier --write -c ui", + "prettier-check": "npx prettier --check -c ui" }, "devDependencies": { "prettier": "^1.19.1" From ae52d9ef2274f734b18bcb70d31da849f61ab147 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Fri, 28 Apr 2023 22:52:21 +0200 Subject: [PATCH 37/92] Disable "TypedStorage is deprecated" user warnings These warnings clog up the logfiles and worry users. --- ui/easydiffusion/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/easydiffusion/app.py b/ui/easydiffusion/app.py index da755c21..b6318f01 100644 --- a/ui/easydiffusion/app.py +++ b/ui/easydiffusion/app.py @@ -5,6 +5,7 @@ import socket import sys import traceback import urllib +import warnings from easydiffusion import task_manager from easydiffusion.utils import log @@ -86,6 +87,9 @@ def init(): os.makedirs(USER_UI_PLUGINS_DIR, exist_ok=True) os.makedirs(USER_SERVER_PLUGINS_DIR, exist_ok=True) + # https://pytorch.org/docs/stable/storage.html + warnings.filterwarnings('ignore', category=UserWarning, message='TypedStorage is deprecated') + load_server_plugins() update_render_threads() From 3100fae11866a6f6203a00486b8d68348b662012 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:05:50 +0530 Subject: [PATCH 38/92] Delete prettier.yml --- .github/workflows/prettier.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/prettier.yml diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml deleted file mode 100644 index 2ea9aab9..00000000 --- a/.github/workflows/prettier.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Check JavaScript for conformance with Prettier - -on: - push: - pull_request: - -jobs: - prettier: - runs-on: ubuntu-latest - steps: - - name: Check out repo - uses: actions/checkout@v2 - - uses: actions/cache@v2 - name: Configure npm caching - with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/workflows/prettier.yml') }} - restore-keys: | - ${{ runner.os }}-npm- - - name: Run prettier - run: |- - npm run prettier-check \ No newline at end of file From 0ebad7708391126fde6f2b2689ead398734398d4 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:09:57 +0530 Subject: [PATCH 39/92] One more try --- .github/workflows/prettier.yml | 22 ++++++++++++++++++++++ package.json | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/prettier.yml diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 00000000..2ea9aab9 --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,22 @@ +name: Check JavaScript for conformance with Prettier + +on: + push: + pull_request: + +jobs: + prettier: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v2 + - uses: actions/cache@v2 + name: Configure npm caching + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/workflows/prettier.yml') }} + restore-keys: | + ${{ runner.os }}-npm- + - name: Run prettier + run: |- + npm run prettier-check \ No newline at end of file diff --git a/package.json b/package.json index cedf812b..fbf1dadb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "scripts": { - "prettier-fix": "npx prettier --write -c ui", - "prettier-check": "npx prettier --check -c ui" + "prettier-fix": "npx prettier --write \"./**/*.js\"", + "prettier-check": "npx prettier --check \"./**/*.js\"" }, "devDependencies": { "prettier": "^1.19.1" From e550b150947b4a5725909b1f0adce8a7d7251e6c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:14:42 +0530 Subject: [PATCH 40/92] Use the prettier config file --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fbf1dadb..3c071b84 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "scripts": { - "prettier-fix": "npx prettier --write \"./**/*.js\"", - "prettier-check": "npx prettier --check \"./**/*.js\"" + "prettier-fix": "npx prettier --write \"./**/*.js\" --config .prettierrc.json", + "prettier-check": "npx prettier --check \"./**/*.js\" --config .prettierrc.json" }, "devDependencies": { "prettier": "^1.19.1" From a6fe023519ee16be038e27fb19a754cfa63a59bf Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:23:54 +0530 Subject: [PATCH 41/92] Try using a third-party action for prettier --- .github/workflows/prettier.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 2ea9aab9..5163a5bf 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -10,13 +10,7 @@ jobs: steps: - name: Check out repo uses: actions/checkout@v2 - - uses: actions/cache@v2 - name: Configure npm caching + - uses: actionsx/prettier@v2 with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/workflows/prettier.yml') }} - restore-keys: | - ${{ runner.os }}-npm- - - name: Run prettier - run: |- - npm run prettier-check \ No newline at end of file + # prettier CLI arguments. + args: --check \"./**/*.js\" --config .prettierrc.json \ No newline at end of file From f90a13571c50ac7e3f56107b4a248a9619f284a7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:25:20 +0530 Subject: [PATCH 42/92] typo --- .github/workflows/prettier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 5163a5bf..edc58515 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -13,4 +13,4 @@ jobs: - uses: actionsx/prettier@v2 with: # prettier CLI arguments. - args: --check \"./**/*.js\" --config .prettierrc.json \ No newline at end of file + args: --check "./**/*.js" --config .prettierrc.json \ No newline at end of file From 6cf05df5eef3698e4e338184821eab3691e9aa47 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:31:09 +0530 Subject: [PATCH 43/92] Add the config explicitly --- .github/workflows/prettier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index edc58515..b342a871 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -13,4 +13,4 @@ jobs: - uses: actionsx/prettier@v2 with: # prettier CLI arguments. - args: --check "./**/*.js" --config .prettierrc.json \ No newline at end of file + args: --print-width 120 --tab-width 4 --no-semi --arrow-parens always --trailing-comma es5 --check "./**/*.js" \ No newline at end of file From 31c54c4a4131561f52a3e42c2aab7e5a7a679552 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:34:13 +0530 Subject: [PATCH 44/92] Print the diff --- .github/workflows/prettier.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index b342a871..d2d873f4 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -13,4 +13,5 @@ jobs: - uses: actionsx/prettier@v2 with: # prettier CLI arguments. - args: --print-width 120 --tab-width 4 --no-semi --arrow-parens always --trailing-comma es5 --check "./**/*.js" \ No newline at end of file + args: --print-width 120 --tab-width 4 --no-semi --arrow-parens always --trailing-comma es5 --write "./**/*.js" + - uses: technote-space/get-diff-action@v6 \ No newline at end of file From 262a1464c3b0045a5c2d855bb9ffb60120b84472 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:38:11 +0530 Subject: [PATCH 45/92] Print the diff --- .github/workflows/prettier.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index d2d873f4..09fd8c1b 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -14,4 +14,12 @@ jobs: with: # prettier CLI arguments. args: --print-width 120 --tab-width 4 --no-semi --arrow-parens always --trailing-comma es5 --write "./**/*.js" - - uses: technote-space/get-diff-action@v6 \ No newline at end of file + - uses: GrantBirki/git-diff-action@vX.X.X + id: git-diff-action + with: + json_diff_file_output: diff.json + raw_diff_file_output: diff.txt + - name: print raw diff + env: + DIFF: ${{ steps.git-diff-action.outputs.raw-diff-path }} + run: cat $DIFF \ No newline at end of file From 20d6e17d4dc950b3686fc2babec85f095e92ef3a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:40:24 +0530 Subject: [PATCH 46/92] Print the diff --- .github/workflows/prettier.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 09fd8c1b..4a754965 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -14,12 +14,7 @@ jobs: with: # prettier CLI arguments. args: --print-width 120 --tab-width 4 --no-semi --arrow-parens always --trailing-comma es5 --write "./**/*.js" - - uses: GrantBirki/git-diff-action@vX.X.X - id: git-diff-action + - uses: technote-space/get-diff-action@v6 with: - json_diff_file_output: diff.json - raw_diff_file_output: diff.txt - - name: print raw diff - env: - DIFF: ${{ steps.git-diff-action.outputs.raw-diff-path }} - run: cat $DIFF \ No newline at end of file + PATTERNS: | + +(ui)/**/*.js \ No newline at end of file From e7fd0b3a052f214a276c4a8bd2040ef9c05cbd49 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:42:10 +0530 Subject: [PATCH 47/92] Print the diff --- .github/workflows/prettier.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 4a754965..dcc209f9 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -14,7 +14,5 @@ jobs: with: # prettier CLI arguments. args: --print-width 120 --tab-width 4 --no-semi --arrow-parens always --trailing-comma es5 --write "./**/*.js" - - uses: technote-space/get-diff-action@v6 - with: - PATTERNS: | - +(ui)/**/*.js \ No newline at end of file + - name: Get Diff + run: git diff \ No newline at end of file From 50fdc32ff8c88837cce7b594a8fbecd9bb0eed82 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:44:38 +0530 Subject: [PATCH 48/92] Print the diff --- .github/workflows/prettier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index dcc209f9..06765500 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -13,6 +13,6 @@ jobs: - uses: actionsx/prettier@v2 with: # prettier CLI arguments. - args: --print-width 120 --tab-width 4 --no-semi --arrow-parens always --trailing-comma es5 --write "./**/*.js" + args: --write "./**/*.js" - name: Get Diff run: git diff \ No newline at end of file From 1b40a6baa31e2f26aff53f3461b05fa0e544f957 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:54:14 +0530 Subject: [PATCH 49/92] Print the diff --- .github/workflows/prettier.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 06765500..488c2675 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -10,9 +10,18 @@ jobs: steps: - name: Check out repo uses: actions/checkout@v2 - - uses: actionsx/prettier@v2 + - uses: actions/cache@v2 + name: Configure npm caching with: - # prettier CLI arguments. - args: --write "./**/*.js" - - name: Get Diff - run: git diff \ No newline at end of file + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/workflows/prettier.yml') }} + restore-keys: | + ${{ runner.os }}-npm- + - name: Run prettier + run: |- + npm run prettier-write + - name: Print the diff + run: git diff; git reset --hard + - name: Run prettier + run: |- + npm run prettier-check \ No newline at end of file From 4dc2a96d416f77c1a734528abd062c3e901c678a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 07:55:11 +0530 Subject: [PATCH 50/92] Print the diff --- .github/workflows/prettier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 488c2675..60cfbfd6 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -19,7 +19,7 @@ jobs: ${{ runner.os }}-npm- - name: Run prettier run: |- - npm run prettier-write + npm run prettier-fix - name: Print the diff run: git diff; git reset --hard - name: Run prettier From 51e067b050bd19111a5121b4b59bc75cc9b189e8 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 08:04:15 +0530 Subject: [PATCH 51/92] Try using the pinned version of prettier --- .github/workflows/prettier.yml | 14 ++++++-------- package-lock.json | 32 ++++++++++++++++++++++++++++++++ package.json | 6 +++--- 3 files changed, 41 insertions(+), 11 deletions(-) create mode 100644 package-lock.json diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 60cfbfd6..891220f0 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -10,18 +10,16 @@ jobs: steps: - name: Check out repo uses: actions/checkout@v2 - - uses: actions/cache@v2 - name: Configure npm caching + - uses: actions/setup-node@v3 with: - path: ~/.npm - key: ${{ runner.os }}-npm-${{ hashFiles('**/workflows/prettier.yml') }} - restore-keys: | - ${{ runner.os }}-npm- + node-version: 16 + cache: 'npm' + - run: npm ci - name: Run prettier run: |- npm run prettier-fix - - name: Print the diff + - name: Print the required changes run: git diff; git reset --hard - - name: Run prettier + - name: Check prettier run: |- npm run prettier-check \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..d6923948 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,32 @@ +{ + "name": "stable-diffusion-ui", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "prettier": "1.19.1" + } + }, + "node_modules/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=4" + } + } + }, + "dependencies": { + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 3c071b84..c57b0587 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "scripts": { - "prettier-fix": "npx prettier --write \"./**/*.js\" --config .prettierrc.json", - "prettier-check": "npx prettier --check \"./**/*.js\" --config .prettierrc.json" + "prettier-fix": "npx prettier --write \"./**/*.js\"", + "prettier-check": "npx prettier --check \"./**/*.js\"" }, "devDependencies": { - "prettier": "^1.19.1" + "prettier": "1.19.1" } } From 729f7eb24a77a69d0bc35edcfda8fd9484402116 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 29 Apr 2023 08:18:36 +0530 Subject: [PATCH 52/92] Remove prettier github action --- .github/workflows/prettier.yml | 25 ------------------------- package-lock.json | 32 -------------------------------- package.json | 2 +- yarn.lock | 8 -------- 4 files changed, 1 insertion(+), 66 deletions(-) delete mode 100644 .github/workflows/prettier.yml delete mode 100644 package-lock.json delete mode 100644 yarn.lock diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml deleted file mode 100644 index 891220f0..00000000 --- a/.github/workflows/prettier.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Check JavaScript for conformance with Prettier - -on: - push: - pull_request: - -jobs: - prettier: - runs-on: ubuntu-latest - steps: - - name: Check out repo - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 16 - cache: 'npm' - - run: npm ci - - name: Run prettier - run: |- - npm run prettier-fix - - name: Print the required changes - run: git diff; git reset --hard - - name: Check prettier - run: |- - npm run prettier-check \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index d6923948..00000000 --- a/package-lock.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "stable-diffusion-ui", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "devDependencies": { - "prettier": "1.19.1" - } - }, - "node_modules/prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=4" - } - } - }, - "dependencies": { - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - } - } -} diff --git a/package.json b/package.json index c57b0587..fbf1dadb 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,6 @@ "prettier-check": "npx prettier --check \"./**/*.js\"" }, "devDependencies": { - "prettier": "1.19.1" + "prettier": "^1.19.1" } } diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 6fc84143..00000000 --- a/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -prettier@^1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== From d231c533ae4c9b4f1aa14c76ca9b66f0eee9999f Mon Sep 17 00:00:00 2001 From: JeLuF Date: Tue, 2 May 2023 12:29:02 +0200 Subject: [PATCH 53/92] "Please restart" note for network settings (#1233) * "Please restart" note for network changes https://discord.com/channels/1014774730907209781/1101629831839494344 * typo --- 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 9fc906e4..75abecd7 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -181,7 +181,7 @@ var PARAMETERS = [ { id: "listen_to_network", type: ParameterType.checkbox, - label: "Make Stable Diffusion available on your network", + label: "Make Stable Diffusion available on your network. Please restart the program after changing this.", note: "Other devices on your network can access this web page", icon: "fa-network-wired", default: true, @@ -191,7 +191,7 @@ var PARAMETERS = [ id: "listen_port", type: ParameterType.custom, label: "Network port", - note: "Port that this server listens to. The '9000' part in 'http://localhost:9000'", + note: "Port that this server listens to. The '9000' part in 'http://localhost:9000'. Please restart the program after changing this.", icon: "fa-anchor", render: (parameter) => { return `` From 679b828cf50d11639102cad4640fdcebddb67b74 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Tue, 2 May 2023 03:30:16 -0700 Subject: [PATCH 54/92] Toast notification support (#1228) * Toast notifications for ED Adding support for toast notifications for use in Core and user plugins. * Revert "Toast notifications for ED" This reverts commit dde51c0cefaf36857a54441f68d313ad13ed3e67. * Toast notifications for ED Adding support for toast notifications for use in Core and user plugins. --- ui/media/css/main.css | 51 ++++++++++++++++++++++++++++++++++++++ ui/media/js/utils.js | 57 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 08dea664..ba513237 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1302,3 +1302,54 @@ body.wait-pause { .displayNone { display:none !important; } + +/* TOAST NOTIFICATIONS */ +.toast-notification { + position: fixed; + bottom: 10px; + right: -300px; + width: 300px; + background-color: #333; + color: #fff; + padding: 10px 20px; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + z-index: 9999; + animation: slideInRight 0.5s ease forwards; + transition: bottom 0.5s ease; // Add a transition to smoothly reposition the toasts +} + +.toast-notification-error { + color: red; +} + +@keyframes slideInRight { + from { + right: -300px; + } + to { + right: 10px; + } +} + +.toast-notification.hide { + animation: slideOutRight 0.5s ease forwards; +} + +@keyframes slideOutRight { + from { + right: 10px; + } + to { + right: -300px; + } +} + +@keyframes slideDown { + from { + bottom: 10px; + } + to { + bottom: 0; + } +} diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 6558f7d2..d1578d8e 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -840,3 +840,60 @@ function createTab(request) { } }) } + +/* TOAST NOTIFICATIONS */ +function showToast(message, duration = 5000, error = false) { + const toast = document.createElement("div"); + toast.classList.add("toast-notification"); + if (error === true) { + toast.classList.add("toast-notification-error"); + } + toast.innerHTML = message; + document.body.appendChild(toast); + + // Set the position of the toast on the screen + const toastCount = document.querySelectorAll(".toast-notification").length; + const toastHeight = toast.offsetHeight; + const previousToastsHeight = Array.from(document.querySelectorAll(".toast-notification")) + .slice(0, -1) // exclude current toast + .reduce((totalHeight, toast) => totalHeight + toast.offsetHeight + 10, 0); // add 10 pixels for spacing + toast.style.bottom = `${10 + previousToastsHeight}px`; + toast.style.right = "10px"; + + // Delay the removal of the toast until animation has completed + const removeToast = () => { + toast.classList.add("hide"); + const removeTimeoutId = setTimeout(() => { + toast.remove(); + // Adjust the position of remaining toasts + const remainingToasts = document.querySelectorAll(".toast-notification"); + const removedToastBottom = toast.getBoundingClientRect().bottom; + + remainingToasts.forEach((toast) => { + if (toast.getBoundingClientRect().bottom < removedToastBottom) { + toast.classList.add("slide-down"); + } + }); + + // Wait for the slide-down animation to complete + setTimeout(() => { + // Remove the slide-down class after the animation has completed + const slidingToasts = document.querySelectorAll(".slide-down"); + slidingToasts.forEach((toast) => { + toast.classList.remove("slide-down"); + }); + + // Adjust the position of remaining toasts again, in case there are multiple toasts being removed at once + const remainingToastsDown = document.querySelectorAll(".toast-notification"); + let heightSoFar = 0; + remainingToastsDown.forEach((toast) => { + toast.style.bottom = `${10 + heightSoFar}px`; + heightSoFar += toast.offsetHeight + 10; // add 10 pixels for spacing + }); + }, 0); // The duration of the slide-down animation (in milliseconds) + }, 500); + }; + + // Remove the toast after specified duration + setTimeout(removeToast, duration); +} From eaba64a64ac958cfb07b0d925867f057d5fad91a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 2 May 2023 18:26:29 +0530 Subject: [PATCH 55/92] Log device usage stats during thread startup --- ui/easydiffusion/renderer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/easydiffusion/renderer.py b/ui/easydiffusion/renderer.py index 6c10dca1..6b110468 100644 --- a/ui/easydiffusion/renderer.py +++ b/ui/easydiffusion/renderer.py @@ -17,6 +17,7 @@ from sdkit.utils import ( img_to_base64_str, img_to_buffer, latent_samples_to_images, + get_device_usage, ) context = Context() # thread-local @@ -40,6 +41,9 @@ def init(device): app_config.get("test_diffusers", False) and app_config.get("update_branch", "main") != "main" ) + log.info("Device usage during initialization:") + get_device_usage(device, log_info=True) + device_manager.device_init(context, device) From 75f0780bd106e588a103a7f2b95bd4714d304f8c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 3 May 2023 16:12:11 +0530 Subject: [PATCH 56/92] sdkit 1.0.84 - VRAM optimizations for the diffusers version --- scripts/check_modules.py | 2 +- ui/easydiffusion/renderer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index c8955e0a..b1ec44d6 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.83", + "sdkit": "1.0.84", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", diff --git a/ui/easydiffusion/renderer.py b/ui/easydiffusion/renderer.py index 6b110468..f685d038 100644 --- a/ui/easydiffusion/renderer.py +++ b/ui/easydiffusion/renderer.py @@ -42,7 +42,7 @@ def init(device): ) log.info("Device usage during initialization:") - get_device_usage(device, log_info=True) + get_device_usage(device, log_info=True, process_usage_only=False) device_manager.device_init(context, device) From 5c95bcc65d857760f1fe91670b08ed90b1c0ab0d Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 3 May 2023 16:13:57 +0530 Subject: [PATCH 57/92] changelog --- CHANGES.md | 1 + ui/index.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6b4faf95..d34aaddc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 2.5.35 - 3 May 2023 - (beta-only) VRAM Optimizations for the "Test Diffusers" version. This change significantly reduces the amount of VRAM used by the diffusers version during image generation. * 2.5.34 - 22 Apr 2023 - Don't start the browser in an incognito new profile (on Windows). Thanks @JeLuf. * 2.5.33 - 21 Apr 2023 - Install PyTorch 2.0 on new installations (on Windows and Linux). * 2.5.32 - 19 Apr 2023 - Automatically check for black images, and set full-precision if necessary (for attn). This means custom models based on Stable Diffusion v2.1 will just work, without needing special command-line arguments or editing of yaml config files. diff --git a/ui/index.html b/ui/index.html index 3db86593..be522689 100644 --- a/ui/index.html +++ b/ui/index.html @@ -30,7 +30,7 @@

Easy Diffusion - v2.5.34 + v2.5.35

From c18bf3e41304a33c03961343ed531cf5e30e5998 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 3 May 2023 18:18:18 +0530 Subject: [PATCH 58/92] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d34aaddc..9fe2cff0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,7 +21,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog -* 2.5.35 - 3 May 2023 - (beta-only) VRAM Optimizations for the "Test Diffusers" version. This change significantly reduces the amount of VRAM used by the diffusers version during image generation. +* 2.5.35 - 3 May 2023 - (beta-only) First round of VRAM Optimizations for the "Test Diffusers" version. This change significantly reduces the amount of VRAM used by the diffusers version during image generation. The VRAM usage is still not equal to the "non-diffusers" version, but more optimizations are coming soon. * 2.5.34 - 22 Apr 2023 - Don't start the browser in an incognito new profile (on Windows). Thanks @JeLuf. * 2.5.33 - 21 Apr 2023 - Install PyTorch 2.0 on new installations (on Windows and Linux). * 2.5.32 - 19 Apr 2023 - Automatically check for black images, and set full-precision if necessary (for attn). This means custom models based on Stable Diffusion v2.1 will just work, without needing special command-line arguments or editing of yaml config files. From 01c77129617bc718d31ba0a585ec4d0bacce1c3c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 3 May 2023 18:47:09 +0530 Subject: [PATCH 59/92] Increase task timeout from 15 mins to 30 mins --- ui/easydiffusion/task_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/easydiffusion/task_manager.py b/ui/easydiffusion/task_manager.py index 450636cf..c11acbec 100644 --- a/ui/easydiffusion/task_manager.py +++ b/ui/easydiffusion/task_manager.py @@ -7,7 +7,7 @@ Notes: import json import traceback -TASK_TTL = 15 * 60 # seconds, Discard last session's task timeout +TASK_TTL = 30 * 60 # seconds, Discard last session's task timeout import queue import threading @@ -398,8 +398,8 @@ def get_devices(): return {"name": device_manager.get_processor_name()} mem_free, mem_total = torch.cuda.mem_get_info(device) - mem_free /= float(10 ** 9) - mem_total /= float(10 ** 9) + mem_free /= float(10**9) + mem_total /= float(10**9) return { "name": torch.cuda.get_device_name(device), From 06c990e94dc4457f76986776b7f57ef41e3106b3 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 4 May 2023 00:05:11 +0200 Subject: [PATCH 60/92] Default value for hypernetworkStrength Don't fail when the Hypernetwork Strength text input field is empty --- ui/media/js/main.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 08a73024..a54f6ecb 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -692,6 +692,9 @@ function makeImage() { if (guidanceScaleField.value == "") { guidanceScaleField.value = guidanceScaleSlider.value / 10 } + if (hypernetworkStrengthField.value == "") { + hypernetworkStrengthField.value = hypernetworkStrengthSlider.value / 100 + } const taskTemplate = getCurrentUserRequest() const newTaskRequests = getPrompts().map((prompt) => Object.assign({}, taskTemplate, { From 8e416cef25fc1c9b715276fdfb87243fcfb9f65f Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 4 May 2023 10:12:06 +0530 Subject: [PATCH 61/92] Disable self test link --- ui/plugins/ui/{selftest.plugin.js => selftest.plugin_.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ui/plugins/ui/{selftest.plugin.js => selftest.plugin_.js} (100%) diff --git a/ui/plugins/ui/selftest.plugin.js b/ui/plugins/ui/selftest.plugin_.js similarity index 100% rename from ui/plugins/ui/selftest.plugin.js rename to ui/plugins/ui/selftest.plugin_.js From b27a14b1b444be5d4add6e03d816f35d75138c8c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 4 May 2023 16:04:45 +0530 Subject: [PATCH 62/92] sdkit 1.0.85 - torch.Generator fix for mps/mac --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index b1ec44d6..ede9eeda 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.84", + "sdkit": "1.0.85", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From f9cfe1da459afb7543530eee9a247c5b602ece0c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 4 May 2023 16:09:28 +0530 Subject: [PATCH 63/92] sdkit 1.0.86 - don't use cpu offload for mps/mac, doesn't make sense since the memory is shared between GPU/CPU --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index ede9eeda..209a2049 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.85", + "sdkit": "1.0.86", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 64cfd5506504cc68f24505c73857744a1c626f58 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 4 May 2023 16:31:40 +0530 Subject: [PATCH 64/92] sdkit 1.0.87 - typo --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 209a2049..031f7d66 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.86", + "sdkit": "1.0.87", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 3f9ec378a0094d7f3d25114a7d5c73a36206fdb1 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Thu, 4 May 2023 09:16:19 -0700 Subject: [PATCH 65/92] Fix restoration of inactive image modifiers --- ui/media/js/image-modifiers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/media/js/image-modifiers.js b/ui/media/js/image-modifiers.js index fd4ecaf1..69f31ab1 100644 --- a/ui/media/js/image-modifiers.js +++ b/ui/media/js/image-modifiers.js @@ -246,7 +246,7 @@ function refreshInactiveTags(inactiveTags) { overlays.forEach((i) => { let modifierName = i.parentElement.getElementsByClassName("modifier-card-label")[0].getElementsByTagName("p")[0] .dataset.fullName - if (inactiveTags?.find((element) => element === modifierName) !== undefined) { + if (inactiveTags?.find((element) => trimModifiers(element) === modifierName) !== undefined) { i.parentElement.classList.add("modifier-toggle-inactive") } }) From fec21408962b451c693cae271c57289ca350538f Mon Sep 17 00:00:00 2001 From: Olivia Godone-Maresca Date: Thu, 4 May 2023 19:36:10 -0400 Subject: [PATCH 66/92] Allow grabbing the image to scroll zoomed in images --- ui/media/css/image-modal.css | 8 +++++ ui/media/js/image-modal.js | 63 ++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/ui/media/css/image-modal.css b/ui/media/css/image-modal.css index 1001807c..64096003 100644 --- a/ui/media/css/image-modal.css +++ b/ui/media/css/image-modal.css @@ -70,6 +70,14 @@ max-height: calc(100vh - (var(--popup-padding) * 2) - 4px); } +#viewFullSizeImgModal img:not(.natural-zoom) { + cursor: grab; +} + +#viewFullSizeImgModal .grabbing img:not(.natural-zoom) { + cursor: grabbing; +} + #viewFullSizeImgModal .content > div::-webkit-scrollbar-track, #viewFullSizeImgModal .content > div::-webkit-scrollbar-corner { background: rgba(0, 0, 0, .5) } diff --git a/ui/media/js/image-modal.js b/ui/media/js/image-modal.js index 3a97c4d8..28c1eaf2 100644 --- a/ui/media/js/image-modal.js +++ b/ui/media/js/image-modal.js @@ -63,15 +63,73 @@ const imageModal = (function() { setZoomLevel(imageContainer.querySelector("img")?.classList?.contains("natural-zoom")) ) - const state = { + const initialState = () => ({ previous: undefined, next: undefined, + + start: { + x: 0, + y: 0, + }, + + scroll: { + x: 0, + y: 0, + }, + }) + + const state = initialState() + + // Allow grabbing the image to scroll + const stopGrabbing = (e) => { + if(imageContainer.classList.contains("grabbing")) { + imageContainer.classList.remove("grabbing") + e?.preventDefault() + console.log(`stopGrabbing()`, e) + } + } + + const addImageGrabbing = (image) => { + image?.addEventListener('mousedown', (e) => { + if (!image.classList.contains("natural-zoom")) { + e.stopPropagation() + e.stopImmediatePropagation() + e.preventDefault() + + imageContainer.classList.add("grabbing") + state.start.x = e.pageX - imageContainer.offsetLeft + state.scroll.x = imageContainer.scrollLeft + state.start.y = e.pageY - imageContainer.offsetTop + state.scroll.y = imageContainer.scrollTop + } + }) + + image?.addEventListener('mouseup', stopGrabbing) + image?.addEventListener('mouseleave', stopGrabbing) + image?.addEventListener('mousemove', (e) => { + if(imageContainer.classList.contains("grabbing")) { + e.stopPropagation() + e.stopImmediatePropagation() + e.preventDefault() + + // Might need to increase this multiplier based on the image size to window size ratio + // The default 1:1 is pretty slow + const multiplier = 1.0 + + const deltaX = e.pageX - imageContainer.offsetLeft - state.start.x + imageContainer.scrollLeft = state.scroll.x - (deltaX * multiplier) + const deltaY = e.pageY - imageContainer.offsetTop - state.start.y + imageContainer.scrollTop = state.scroll.y - (deltaY * multiplier) + } + }) } const clear = () => { imageContainer.innerHTML = "" - Object.keys(state).forEach((key) => delete state[key]) + Object.entries(initialState()).forEach(([key, value]) => state[key] = value) + + stopGrabbing() } const close = () => { @@ -95,6 +153,7 @@ const imageModal = (function() { const src = typeof options === "string" ? options : options.src const imgElem = createElement("img", { src }, "natural-zoom") + addImageGrabbing(imgElem) imageContainer.appendChild(imgElem) modalElem.classList.add("active") document.body.style.overflow = "hidden" From 2d1be6186e7037b146fe8062145e8593990db719 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 10 May 2023 20:19:17 +0530 Subject: [PATCH 67/92] sdkit 1.0.88 - Fix LoRA in low VRAM mode --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 031f7d66..3fcfce14 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.87", + "sdkit": "1.0.88", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 08f44472f8bd0052fcb04cb443180b13cc961a6e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 10 May 2023 20:20:59 +0530 Subject: [PATCH 68/92] changelog --- CHANGES.md | 1 + ui/index.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9fe2cff0..570d0c72 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 2.5.36 - 10 May 2023 - (beta-only) Bug fix for "meta" error when using a LoRA in 'low' VRAM usage mode. * 2.5.35 - 3 May 2023 - (beta-only) First round of VRAM Optimizations for the "Test Diffusers" version. This change significantly reduces the amount of VRAM used by the diffusers version during image generation. The VRAM usage is still not equal to the "non-diffusers" version, but more optimizations are coming soon. * 2.5.34 - 22 Apr 2023 - Don't start the browser in an incognito new profile (on Windows). Thanks @JeLuf. * 2.5.33 - 21 Apr 2023 - Install PyTorch 2.0 on new installations (on Windows and Linux). diff --git a/ui/index.html b/ui/index.html index be522689..d196b99d 100644 --- a/ui/index.html +++ b/ui/index.html @@ -30,7 +30,7 @@

Easy Diffusion - v2.5.35 + v2.5.36

From 566a83ce3f14af592903ef0417575229e3b5277a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 11 May 2023 14:49:15 +0530 Subject: [PATCH 69/92] sdkit 1.0.89 - use half precision in test diffusers for low vram usage mode' --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 3fcfce14..b9797515 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.88", + "sdkit": "1.0.89", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 4bca739b3d64f3a5827475bd50c52a9025d6e31d Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 11 May 2023 14:52:30 +0530 Subject: [PATCH 70/92] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 570d0c72..d6d93bd7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,6 +21,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 2.5.36 - 11 May 2023 - (beta-only) Another VRAM optimization for "low" VRAM usage mode. * 2.5.36 - 10 May 2023 - (beta-only) Bug fix for "meta" error when using a LoRA in 'low' VRAM usage mode. * 2.5.35 - 3 May 2023 - (beta-only) First round of VRAM Optimizations for the "Test Diffusers" version. This change significantly reduces the amount of VRAM used by the diffusers version during image generation. The VRAM usage is still not equal to the "non-diffusers" version, but more optimizations are coming soon. * 2.5.34 - 22 Apr 2023 - Don't start the browser in an incognito new profile (on Windows). Thanks @JeLuf. From add05228bdb34278e0393cfb47ccbcd79392b9fc Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 11 May 2023 16:30:06 +0530 Subject: [PATCH 71/92] sdkit 1.0.91 - use slice size 1 for low vram usage mode, to reduce VRAM usage --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index b9797515..8da111e0 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.89", + "sdkit": "1.0.91", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 1cba62af242c9ebfd1109b18bc0d8fafeda463a2 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 11 May 2023 16:30:32 +0530 Subject: [PATCH 72/92] changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d6d93bd7..6168fcc3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,7 +21,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog -* 2.5.36 - 11 May 2023 - (beta-only) Another VRAM optimization for "low" VRAM usage mode. +* 2.5.36 - 11 May 2023 - (beta-only) More VRAM optimizations for "low" VRAM usage mode. * 2.5.36 - 10 May 2023 - (beta-only) Bug fix for "meta" error when using a LoRA in 'low' VRAM usage mode. * 2.5.35 - 3 May 2023 - (beta-only) First round of VRAM Optimizations for the "Test Diffusers" version. This change significantly reduces the amount of VRAM used by the diffusers version during image generation. The VRAM usage is still not equal to the "non-diffusers" version, but more optimizations are coming soon. * 2.5.34 - 22 Apr 2023 - Don't start the browser in an incognito new profile (on Windows). Thanks @JeLuf. From 7240c91db722b0c92803f62131f20a887276c5c7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 12 May 2023 14:48:48 +0530 Subject: [PATCH 73/92] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 6168fcc3..f2432505 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ Our focus continues to remain on an easy installation experience, and an easy us ### Detailed changelog * 2.5.36 - 11 May 2023 - (beta-only) More VRAM optimizations for "low" VRAM usage mode. * 2.5.36 - 10 May 2023 - (beta-only) Bug fix for "meta" error when using a LoRA in 'low' VRAM usage mode. +* 2.5.35 - 8 May 2023 - Allow dragging a zoomed-in image (after opening an image with the "expand" button). Thanks @ogmaresca. * 2.5.35 - 3 May 2023 - (beta-only) First round of VRAM Optimizations for the "Test Diffusers" version. This change significantly reduces the amount of VRAM used by the diffusers version during image generation. The VRAM usage is still not equal to the "non-diffusers" version, but more optimizations are coming soon. * 2.5.34 - 22 Apr 2023 - Don't start the browser in an incognito new profile (on Windows). Thanks @JeLuf. * 2.5.33 - 21 Apr 2023 - Install PyTorch 2.0 on new installations (on Windows and Linux). From 8142fd0701d63a35dd8782ebd0b340d4b2d93aeb Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 12 May 2023 14:50:04 +0530 Subject: [PATCH 74/92] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index f2432505..d6ac5cda 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Major Changes - **Nearly twice as fast** - significantly faster speed of image generation. Code contributions are welcome to make our project even faster: https://github.com/easydiffusion/sdkit/#is-it-fast - **Mac M1/M2 support** - Experimental support for Mac M1/M2. Thanks @michaelgallacher, @JeLuf and vishae. +- **AMD support for Linux** - Experimental support for AMD GPUs on Linux. Thanks @DianaNites and @JeLuf. - **Full support for Stable Diffusion 2.1 (including CPU)** - supports loading v1.4 or v2.0 or v2.1 models seamlessly. No need to enable "Test SD2", and no need to add `sd2_` to your SD 2.0 model file names. Works on CPU as well. - **Memory optimized Stable Diffusion 2.1** - you can now use Stable Diffusion 2.1 models, with the same low VRAM optimizations that we've always had for SD 1.4. Please note, the SD 2.0 and 2.1 models require more GPU and System RAM, as compared to the SD 1.4 and 1.5 models. - **11 new samplers!** - explore the new samplers, some of which can generate great images in less than 10 inference steps! We've added the Karras and UniPC samplers. Thanks @Schorny for the UniPC samplers. From 45db4bb0367e237244ccf675c1f2b9c3522e5255 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 12 May 2023 16:49:13 +0530 Subject: [PATCH 75/92] sdkit 1.0.92 - more vram optimizations for low,balanced,high - reduces VRAM usage by 20% (especially with larger images) --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 8da111e0..21392011 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.91", + "sdkit": "1.0.92", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 366bc7275981f1b4d3eb76a40d0d1faa11764419 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Mon, 15 May 2023 17:01:21 +0200 Subject: [PATCH 76/92] Add GTX1630 to list of FP32 GPUs https://discord.com/channels/1014774730907209781/1014774732018683926/1107677076233912340 --- ui/easydiffusion/device_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/easydiffusion/device_manager.py b/ui/easydiffusion/device_manager.py index 59c07ea3..5dd244b7 100644 --- a/ui/easydiffusion/device_manager.py +++ b/ui/easydiffusion/device_manager.py @@ -165,6 +165,7 @@ def needs_to_force_full_precision(context): and ( " 1660" in device_name or " 1650" in device_name + or " 1630" in device_name or " t400" in device_name or " t550" in device_name or " t600" in device_name From 7562a882f4699084c3e07a6ad69b04b513ca2ab9 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 16 May 2023 16:02:20 +0530 Subject: [PATCH 77/92] sdkit 1.0.93 - lower vram usage for balanced mode, by using attention slice of 1 --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 21392011..18549217 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.92", + "sdkit": "1.0.93", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 1605c5fbccb46f66dae672bd8fb2f41e3e7989c6 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 16 May 2023 16:03:12 +0530 Subject: [PATCH 78/92] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index d6ac5cda..3b910ae6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 2.5.36 - 16 May 2023 - (beta-only) More VRAM optimizations for "balanced" VRAM usage mode. * 2.5.36 - 11 May 2023 - (beta-only) More VRAM optimizations for "low" VRAM usage mode. * 2.5.36 - 10 May 2023 - (beta-only) Bug fix for "meta" error when using a LoRA in 'low' VRAM usage mode. * 2.5.35 - 8 May 2023 - Allow dragging a zoomed-in image (after opening an image with the "expand" button). Thanks @ogmaresca. From 9410879b73cbac56ed23624c71fe74e9e642b985 Mon Sep 17 00:00:00 2001 From: patriceac <48073125+patriceac@users.noreply.github.com> Date: Tue, 16 May 2023 17:43:14 -0700 Subject: [PATCH 79/92] Fix error when removing image Error report: https://discord.com/channels/1014774730907209781/1085803885500825600/1108150298289115187 --- 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 26969365..336e8b50 100644 --- a/ui/plugins/ui/Autoscroll.plugin.js +++ b/ui/plugins/ui/Autoscroll.plugin.js @@ -23,7 +23,7 @@ img.addEventListener( "load", function() { - img.closest(".imageTaskContainer").scrollIntoView() + img?.closest(".imageTaskContainer").scrollIntoView() }, { once: true } ) From 9d408a62bfd4c895807ef1b5e154550cf93ca661 Mon Sep 17 00:00:00 2001 From: Olivia Godone-Maresca Date: Wed, 17 May 2023 21:13:06 -0400 Subject: [PATCH 80/92] Add DDPM and DEIS samplers for diffusers These new samplers will be hidden when diffusers is disabled. Also, samplers that aren't implemented in diffusers yet will be disabled when using diffusers --- README.md | 2 +- ui/index.html | 18 ++++++++++-------- ui/media/js/parameters.js | 21 +++++++++++++++------ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 3cb0bf8e..2d7fcf5a 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages. ### Image generation - **Supports**: "*Text to Image*" and "*Image to Image*". -- **19 Samplers**: `ddim`, `plms`, `heun`, `euler`, `euler_a`, `dpm2`, `dpm2_a`, `lms`, `dpm_solver_stability`, `dpmpp_2s_a`, `dpmpp_2m`, `dpmpp_sde`, `dpm_fast`, `dpm_adaptive`, `unipc_snr`, `unipc_tu`, `unipc_tq`, `unipc_snr_2`, `unipc_tu_2`. +- **21 Samplers**: `ddim`, `plms`, `heun`, `euler`, `euler_a`, `dpm2`, `dpm2_a`, `lms`, `dpm_solver_stability`, `dpmpp_2s_a`, `dpmpp_2m`, `dpmpp_sde`, `dpm_fast`, `dpm_adaptive`, `ddpm`, `deis`, `unipc_snr`, `unipc_tu`, `unipc_tq`, `unipc_snr_2`, `unipc_tu_2`. - **In-Painting**: Specify areas of your image to paint into. - **Simple Drawing Tool**: Draw basic images to guide the AI, without needing an external drawing program. - **Face Correction (GFPGAN)** diff --git a/ui/index.html b/ui/index.html index d196b99d..53632610 100644 --- a/ui/index.html +++ b/ui/index.html @@ -154,16 +154,18 @@ - + - - - - - - + + + + + + + + - + Click to learn more about samplers diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 75abecd7..8c38f2f5 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -388,15 +388,24 @@ async function getAppConfig() { if (config.net && config.net.listen_port !== undefined) { listenPortField.value = config.net.listen_port } - if (config.test_diffusers === undefined || config.update_branch === "main") { - testDiffusers.checked = false + + const testDiffusersEnabled = config.test_diffusers && config.update_branch !== "main" + testDiffusers.checked = testDiffusersEnabled + + if (!testDiffusersEnabled) { document.querySelector("#lora_model_container").style.display = "none" document.querySelector("#lora_alpha_container").style.display = "none" + + document.querySelectorAll("#sampler_name option.diffusers-only").forEach(option => { + option.style.display = "none" + }) } else { - 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" + document.querySelector("#lora_model_container").style.display = "" + document.querySelector("#lora_alpha_container").style.display = loraModelField.value ? "" : "none" + + document.querySelectorAll("#sampler_name option.k_diffusion-only").forEach(option => { + option.disabled = true + }) } console.log("get config status response", config) From d3dd15eb63f0f1de3c5654ca224508d4e3350830 Mon Sep 17 00:00:00 2001 From: Olivia Godone-Maresca Date: Wed, 17 May 2023 21:44:28 -0400 Subject: [PATCH 81/92] Fix unipc_tu --- ui/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 53632610..32d31b34 100644 --- a/ui/index.html +++ b/ui/index.html @@ -162,7 +162,7 @@ - + From 00603ce124d6a137e7e6001cd01fb2007737cd82 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 18 May 2023 13:55:45 +0200 Subject: [PATCH 82/92] Add Clip Skip support --- ui/easydiffusion/model_manager.py | 12 +++++++++++- ui/easydiffusion/types.py | 1 + ui/index.html | 11 +++++++---- ui/media/js/auto-save.js | 1 + ui/media/js/dnd.js | 8 ++++++++ ui/media/js/engine.js | 2 ++ ui/media/js/main.js | 7 +++++++ ui/media/js/parameters.js | 1 + 8 files changed, 38 insertions(+), 5 deletions(-) diff --git a/ui/easydiffusion/model_manager.py b/ui/easydiffusion/model_manager.py index 7bf56575..324dcec9 100644 --- a/ui/easydiffusion/model_manager.py +++ b/ui/easydiffusion/model_manager.py @@ -122,7 +122,7 @@ def reload_models_if_necessary(context: Context, task_data: TaskData): if context.model_paths.get(model_type) != path } - if set_vram_optimizations(context): # reload SD + if set_vram_optimizations(context) or set_clip_skip(context, task_data): # reload SD models_to_reload["stable-diffusion"] = model_paths_in_req["stable-diffusion"] for model_type, model_path_in_req in models_to_reload.items(): @@ -157,6 +157,16 @@ def set_vram_optimizations(context: Context): return False +def set_clip_skip(context: Context, task_data: TaskData): + clip_skip = task_data.clip_skip + + if clip_skip != context.clip_skip: + context.clip_skip = clip_skip + return True + + return False + + def make_model_folders(): for model_type in KNOWN_MODEL_TYPES: model_dir_path = os.path.join(app.MODELS_DIR, model_type) diff --git a/ui/easydiffusion/types.py b/ui/easydiffusion/types.py index 7462355f..7a5201ab 100644 --- a/ui/easydiffusion/types.py +++ b/ui/easydiffusion/types.py @@ -48,6 +48,7 @@ class TaskData(BaseModel): metadata_output_format: str = "txt" # or "json" stream_image_progress: bool = False stream_image_progress_interval: int = 5 + clip_skip: bool = False class MergeRequest(BaseModel): diff --git a/ui/index.html b/ui/index.html index d196b99d..45263e4b 100644 --- a/ui/index.html +++ b/ui/index.html @@ -135,10 +135,13 @@ Click to learn more about custom models - + + + + + Click to learn more about Clip Skip + + Click to learn more about VAEs diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js index 1e536247..ee01ba98 100644 --- a/ui/media/js/auto-save.js +++ b/ui/media/js/auto-save.js @@ -13,6 +13,7 @@ const SETTINGS_IDS_LIST = [ "num_outputs_total", "num_outputs_parallel", "stable_diffusion_model", + "clip_skip", "vae_model", "hypernetwork_model", "lora_model", diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index 548b06ad..02848266 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -240,6 +240,14 @@ const TASK_MAPPING = { readUI: () => stableDiffusionModelField.value, parse: (val) => val, }, + clip_skip: { + name: "Clip Skip", + setUI: (value) => { + clip_skip.checked = value + }, + readUI: () => clip_skip.checked, + parse: (val) => Boolean(val), + }, use_vae_model: { name: "VAE model", setUI: (use_vae_model) => { diff --git a/ui/media/js/engine.js b/ui/media/js/engine.js index f396d951..eccae6ac 100644 --- a/ui/media/js/engine.js +++ b/ui/media/js/engine.js @@ -750,6 +750,7 @@ sampler_name: "string", use_stable_diffusion_model: "string", + clip_skip: "boolean", num_inference_steps: "number", guidance_scale: "number", @@ -763,6 +764,7 @@ const TASK_DEFAULTS = { sampler_name: "plms", use_stable_diffusion_model: "sd-v1-4", + clip_skip: false, num_inference_steps: 50, guidance_scale: 7.5, negative_prompt: "", diff --git a/ui/media/js/main.js b/ui/media/js/main.js index a54f6ecb..c69535df 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -13,6 +13,11 @@ const taskConfigSetup = { num_inference_steps: "Inference Steps", guidance_scale: "Guidance Scale", use_stable_diffusion_model: "Model", + clip_skip: { + label: "Clip Skip", + visible: ({ reqBody }) => reqBody?.clip_skip, + value: ({ reqBody }) => "yes", + }, use_vae_model: { label: "VAE", visible: ({ reqBody }) => reqBody?.use_vae_model !== undefined && reqBody?.use_vae_model.trim() !== "", @@ -82,6 +87,7 @@ let useUpscalingField = document.querySelector("#use_upscale") let upscaleModelField = document.querySelector("#upscale_model") let upscaleAmountField = document.querySelector("#upscale_amount") let stableDiffusionModelField = new ModelDropdown(document.querySelector("#stable_diffusion_model"), "stable-diffusion") +let clipSkipField = document.querySelector("#clip_skip") let vaeModelField = new ModelDropdown(document.querySelector("#vae_model"), "vae", "None") let hypernetworkModelField = new ModelDropdown(document.querySelector("#hypernetwork_model"), "hypernetwork", "None") let hypernetworkStrengthSlider = document.querySelector("#hypernetwork_strength_slider") @@ -1224,6 +1230,7 @@ function getCurrentUserRequest() { sampler_name: samplerField.value, //render_device: undefined, // Set device affinity. Prefer this device, but wont activate. use_stable_diffusion_model: stableDiffusionModelField.value, + clip_skip: clipSkipField.checked, use_vae_model: vaeModelField.value, stream_progress_updates: true, stream_image_progress: numOutputsTotal > 50 ? false : streamImageProgressField.checked, diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 75abecd7..746dc00e 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -397,6 +397,7 @@ async function getAppConfig() { document.querySelector("#lora_model_container").style.display = testDiffusers.checked ? "" : "none" document.querySelector("#lora_alpha_container").style.display = testDiffusers.checked && loraModelField.value !== "" ? "" : "none" + document.querySelector("#clip_skip_config").classList.remove("displayNone") } console.log("get config status response", config) From b77036443ffc8175b21675ad49874c996ca40071 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 18 May 2023 16:04:28 +0200 Subject: [PATCH 83/92] Fail gracefully if proc access isn't possible --- scripts/check_modules.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 18549217..df89f2b2 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -130,10 +130,13 @@ def include_cuda_versions(module_versions: tuple) -> tuple: def is_amd_on_linux(): if os_name == "Linux": - with open("/proc/bus/pci/devices", "r") as f: - device_info = f.read() - if "amdgpu" in device_info and "nvidia" not in device_info: - return True + try: + with open("/proc/bus/pci/devices", "r") as f: + device_info = f.read() + if "amdgpu" in device_info and "nvidia" not in device_info: + return True + except: + return False return False From 063d14d2ac00128ec3096faf09155ae4ce558404 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 19 May 2023 17:25:53 +0530 Subject: [PATCH 84/92] Allow GPUs with less than 2 GB, instead of restricting to 3 GB --- ui/easydiffusion/device_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/easydiffusion/device_manager.py b/ui/easydiffusion/device_manager.py index 59c07ea3..47297900 100644 --- a/ui/easydiffusion/device_manager.py +++ b/ui/easydiffusion/device_manager.py @@ -224,9 +224,9 @@ def is_device_compatible(device): try: _, mem_total = torch.cuda.mem_get_info(device) mem_total /= float(10 ** 9) - if mem_total < 3.0: + if mem_total < 2.0: if is_device_compatible.history.get(device) == None: - log.warn(f"GPU {device} with less than 3 GB of VRAM is not compatible with Stable Diffusion") + log.warn(f"GPU {device} with less than 2 GB of VRAM is not compatible with Stable Diffusion") is_device_compatible.history[device] = 1 return False except RuntimeError as e: From 53b23756a4d97502e03d0ef261c4054ac8726f41 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 19 May 2023 17:26:04 +0530 Subject: [PATCH 85/92] formatting --- ui/easydiffusion/device_manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/easydiffusion/device_manager.py b/ui/easydiffusion/device_manager.py index 47297900..4b92ac6e 100644 --- a/ui/easydiffusion/device_manager.py +++ b/ui/easydiffusion/device_manager.py @@ -98,8 +98,8 @@ def auto_pick_devices(currently_active_devices): continue mem_free, mem_total = torch.cuda.mem_get_info(device) - mem_free /= float(10 ** 9) - mem_total /= float(10 ** 9) + mem_free /= float(10**9) + mem_total /= float(10**9) device_name = torch.cuda.get_device_name(device) log.debug( f"{device} detected: {device_name} - Memory (free/total): {round(mem_free, 2)}Gb / {round(mem_total, 2)}Gb" @@ -181,7 +181,7 @@ def get_max_vram_usage_level(device): else: return "high" - mem_total /= float(10 ** 9) + mem_total /= float(10**9) if mem_total < 4.5: return "low" elif mem_total < 6.5: @@ -223,7 +223,7 @@ def is_device_compatible(device): # Memory check try: _, mem_total = torch.cuda.mem_get_info(device) - mem_total /= float(10 ** 9) + mem_total /= float(10**9) if mem_total < 2.0: if is_device_compatible.history.get(device) == None: log.warn(f"GPU {device} with less than 2 GB of VRAM is not compatible with Stable Diffusion") From 415213878dfb985c45fce946bd111f4951716e72 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 19 May 2023 17:28:54 +0530 Subject: [PATCH 86/92] sdkit 1.0.94 - vram optimizations - perform softmax in half precision --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 18549217..d6e0b251 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.93", + "sdkit": "1.0.94", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From b08e9b79827e6034dba0361e746cad9b36d3e966 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 19 May 2023 17:29:10 +0530 Subject: [PATCH 87/92] changelog --- CHANGES.md | 1 + ui/index.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 3b910ae6..e29f75a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 2.5.37 - 19 May 2023 - (beta-only) More VRAM optimizations for all modes in diffusers. The VRAM usage for diffusers in "low" and "balanced" should now be equal or less than the non-diffusers version. Performs softmax in half precision, like sdkit does. * 2.5.36 - 16 May 2023 - (beta-only) More VRAM optimizations for "balanced" VRAM usage mode. * 2.5.36 - 11 May 2023 - (beta-only) More VRAM optimizations for "low" VRAM usage mode. * 2.5.36 - 10 May 2023 - (beta-only) Bug fix for "meta" error when using a LoRA in 'low' VRAM usage mode. diff --git a/ui/index.html b/ui/index.html index d196b99d..d4b8c7bd 100644 --- a/ui/index.html +++ b/ui/index.html @@ -30,7 +30,7 @@

Easy Diffusion - v2.5.36 + v2.5.37

From 107323d8e7dfe26383c59af7759aacc1f73b81e6 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 19 May 2023 17:42:47 +0530 Subject: [PATCH 88/92] sdkit 1.0.95 - lower vram usage for high mode --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 4b88cd1c..98748967 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.94", + "sdkit": "1.0.95", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 11e1436e2efb92b41aae84289addc4ab3b1b9171 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 19 May 2023 17:56:20 +0530 Subject: [PATCH 89/92] 2 GB cards aren't exactly 2 GB --- ui/easydiffusion/device_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/easydiffusion/device_manager.py b/ui/easydiffusion/device_manager.py index 3a820116..dc705927 100644 --- a/ui/easydiffusion/device_manager.py +++ b/ui/easydiffusion/device_manager.py @@ -225,7 +225,7 @@ def is_device_compatible(device): try: _, mem_total = torch.cuda.mem_get_info(device) mem_total /= float(10**9) - if mem_total < 2.0: + if mem_total < 1.9: if is_device_compatible.history.get(device) == None: log.warn(f"GPU {device} with less than 2 GB of VRAM is not compatible with Stable Diffusion") is_device_compatible.history[device] = 1 From 760909f4958765f2496079b52ac7baef8db2eb92 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 19 May 2023 18:23:10 +0530 Subject: [PATCH 90/92] Update CHANGES.md --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index e29f75a0..9b2b72c1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,8 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 2.5.37 - 19 May 2023 - (beta-only) Two more samplers: DDPM and DEIS. Also disables the samplers that aren't working yet in the Diffusers version. Thanks @ogmaresca. +* 2.5.37 - 19 May 2023 - (beta-only) Support CLIP-Skip. You can set this option under the models dropdown. Thanks @JeLuf. * 2.5.37 - 19 May 2023 - (beta-only) More VRAM optimizations for all modes in diffusers. The VRAM usage for diffusers in "low" and "balanced" should now be equal or less than the non-diffusers version. Performs softmax in half precision, like sdkit does. * 2.5.36 - 16 May 2023 - (beta-only) More VRAM optimizations for "balanced" VRAM usage mode. * 2.5.36 - 11 May 2023 - (beta-only) More VRAM optimizations for "low" VRAM usage mode. From bdf36a8dabff7f2dbd2dfe92c8c82d2dbc96e10f Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 19 May 2023 18:36:37 +0530 Subject: [PATCH 91/92] sdkit 1.0.96 - missing xformers import --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 98748967..1590f569 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.95", + "sdkit": "1.0.96", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From a6dbdf664b2efa965926aa38c81a278817fb48c5 Mon Sep 17 00:00:00 2001 From: Olivia Godone-Maresca Date: Fri, 19 May 2023 19:05:32 -0400 Subject: [PATCH 92/92] Add Clip Skip to metadata files Also, force the properties to be in a consistent order so that, for example, LoRA strength will always be the line below LoRA model. I've rearranged the properties so that they are saved in the same order that the properties are laid out in the UI --- ui/easydiffusion/renderer.py | 2 +- ui/easydiffusion/utils/save_utils.py | 72 ++++++++++++++-------------- ui/media/js/dnd.js | 1 + 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/ui/easydiffusion/renderer.py b/ui/easydiffusion/renderer.py index f685d038..e26b4389 100644 --- a/ui/easydiffusion/renderer.py +++ b/ui/easydiffusion/renderer.py @@ -72,7 +72,7 @@ def make_images( def print_task_info(req: GenerateImageRequest, task_data: TaskData): - req_str = pprint.pformat(get_printable_request(req)).replace("[", "\[") + req_str = pprint.pformat(get_printable_request(req, task_data)).replace("[", "\[") task_str = pprint.pformat(task_data.dict()).replace("[", "\[") log.info(f"request: {req_str}") log.info(f"task data: {task_str}") diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index a7043f27..24b2198c 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -15,23 +15,24 @@ img_number_regex = re.compile("([0-9]{5,})") # keep in sync with `ui/media/js/dnd.js` TASK_TEXT_MAPPING = { "prompt": "Prompt", + "negative_prompt": "Negative Prompt", + "seed": "Seed", + "use_stable_diffusion_model": "Stable Diffusion model", + "clip_skip": "Clip Skip", + "use_vae_model": "VAE model", + "sampler_name": "Sampler", "width": "Width", "height": "Height", - "seed": "Seed", "num_inference_steps": "Steps", "guidance_scale": "Guidance Scale", "prompt_strength": "Prompt Strength", + "use_lora_model": "LoRA model", + "lora_alpha": "LoRA Strength", + "use_hypernetwork_model": "Hypernetwork model", + "hypernetwork_strength": "Hypernetwork Strength", "use_face_correction": "Use Face Correction", "use_upscale": "Use Upscaling", "upscale_amount": "Upscale By", - "sampler_name": "Sampler", - "negative_prompt": "Negative Prompt", - "use_stable_diffusion_model": "Stable Diffusion model", - "use_vae_model": "VAE model", - "use_hypernetwork_model": "Hypernetwork model", - "hypernetwork_strength": "Hypernetwork Strength", - "use_lora_model": "LoRA model", - "lora_alpha": "LoRA Strength", } time_placeholders = { @@ -179,27 +180,7 @@ def save_images_to_disk(images: list, filtered_images: list, req: GenerateImageR def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskData): - metadata = get_printable_request(req) - metadata.update( - { - "use_stable_diffusion_model": task_data.use_stable_diffusion_model, - "use_vae_model": task_data.use_vae_model, - "use_hypernetwork_model": task_data.use_hypernetwork_model, - "use_lora_model": task_data.use_lora_model, - "use_face_correction": task_data.use_face_correction, - "use_upscale": task_data.use_upscale, - } - ) - if metadata["use_upscale"] is not None: - metadata["upscale_amount"] = task_data.upscale_amount - if task_data.use_hypernetwork_model is None: - del metadata["hypernetwork_strength"] - if task_data.use_lora_model is None: - if "lora_alpha" in metadata: - del metadata["lora_alpha"] - app_config = app.getConfig() - if not app_config.get("test_diffusers", False) and "use_lora_model" in metadata: - del metadata["use_lora_model"] + metadata = get_printable_request(req, task_data) # if text, format it in the text format expected by the UI is_txt_format = task_data.metadata_output_format.lower() == "txt" @@ -213,12 +194,33 @@ def get_metadata_entries_for_request(req: GenerateImageRequest, task_data: TaskD return entries -def get_printable_request(req: GenerateImageRequest): - metadata = req.dict() - del metadata["init_image"] - del metadata["init_image_mask"] - if req.init_image is None: +def get_printable_request(req: GenerateImageRequest, task_data: TaskData): + req_metadata = req.dict() + task_data_metadata = task_data.dict() + + # Save the metadata in the order defined in TASK_TEXT_MAPPING + metadata = {} + for key in TASK_TEXT_MAPPING.keys(): + if key in req_metadata: + metadata[key] = req_metadata[key] + elif key in task_data_metadata: + metadata[key] = task_data_metadata[key] + + # Clean up the metadata + if req.init_image is None and "prompt_strength" in metadata: del metadata["prompt_strength"] + if task_data.use_upscale is None and "upscale_amount" in metadata: + del metadata["upscale_amount"] + if task_data.use_hypernetwork_model is None and "hypernetwork_strength" in metadata: + del metadata["hypernetwork_strength"] + if task_data.use_lora_model is None and "lora_alpha" in metadata: + del metadata["lora_alpha"] + + app_config = app.getConfig() + if not app_config.get("test_diffusers", False): + for key in (x for x in ["use_lora_model", "lora_alpha", "clip_skip"] if x in metadata): + del metadata[key] + return metadata diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index 02848266..8b66a3a4 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -37,6 +37,7 @@ function parseBoolean(stringValue) { } } +// keep in sync with `ui/easydiffusion/utils/save_utils.py` const TASK_MAPPING = { prompt: { name: "Prompt",