Merge pull request #1215 from cmdr2/beta

Beta
This commit is contained in:
cmdr2 2023-04-27 15:50:06 +05:30 committed by GitHub
commit 9a01e917c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 479 additions and 339 deletions

View File

@ -2,7 +2,7 @@
## v2.5 ## v2.5
### Major Changes ### 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. - **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. - **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. - **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.
@ -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. 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 ### Detailed changelog
* 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 - 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 - 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 - Allow seeds upto `4,294,967,295`. Thanks @ogmaresca.
* 2.5.31 - 6 Apr 2023 - Buttons to show the previous/next image in the image popup. Thanks @ogmaresca. * 2.5.31 - 6 Apr 2023 - Buttons to show the previous/next image in the image popup. Thanks @ogmaresca.

View File

@ -18,7 +18,7 @@ os_name = platform.system()
modules_to_check = { modules_to_check = {
"torch": ("1.11.0", "1.13.1", "2.0.0"), "torch": ("1.11.0", "1.13.1", "2.0.0"),
"torchvision": ("0.12.0", "0.14.1", "0.15.1"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"),
"sdkit": "1.0.80", "sdkit": "1.0.81",
"stable-diffusion-sdkit": "2.1.4", "stable-diffusion-sdkit": "2.1.4",
"rich": "12.6.0", "rich": "12.6.0",
"uvicorn": "0.19.0", "uvicorn": "0.19.0",

45
scripts/get_config.py Normal file
View File

@ -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)

View File

@ -12,6 +12,16 @@ if exist "scripts\user_config.bat" (
@call 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%"=="" ( if "%update_branch%"=="" (
set update_branch=main 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\on_sd_start.bat scripts\ /Y
@copy sd-ui-files\scripts\check_modules.py 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\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\Start Stable Diffusion UI.cmd" . /Y
@copy "sd-ui-files\scripts\Developer Console.cmd" . /Y @copy "sd-ui-files\scripts\Developer Console.cmd" . /Y

View File

@ -4,6 +4,8 @@ source ./scripts/functions.sh
printf "\n\nEasy Diffusion\n\n" printf "\n\nEasy Diffusion\n\n"
export PYTHONNOUSERSITE=y
if [ -f "scripts/config.sh" ]; then if [ -f "scripts/config.sh" ]; then
source scripts/config.sh source scripts/config.sh
fi fi
@ -12,6 +14,11 @@ if [ -f "scripts/user_config.sh" ]; then
source scripts/user_config.sh source scripts/user_config.sh
fi 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 if [ "$update_branch" == "" ]; then
export update_branch="main" export update_branch="main"
@ -44,6 +51,7 @@ cp sd-ui-files/scripts/on_sd_start.sh scripts/
cp sd-ui-files/scripts/bootstrap.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_modules.py scripts/
cp sd-ui-files/scripts/check_models.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/start.sh .
cp sd-ui-files/scripts/developer_console.sh . cp sd-ui-files/scripts/developer_console.sh .
cp sd-ui-files/scripts/functions.sh scripts/ cp sd-ui-files/scripts/functions.sh scripts/

View File

@ -6,9 +6,10 @@
@copy sd-ui-files\scripts\on_env_start.bat scripts\ /Y @copy sd-ui-files\scripts\on_env_start.bat scripts\ /Y
@copy sd-ui-files\scripts\check_modules.py 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\check_models.py scripts\ /Y
@copy sd-ui-files\scripts\get_config.py scripts\ /Y
if exist "%cd%\profile" ( if exist "%cd%\profile" (
set USERPROFILE=%cd%\profile set HF_HOME=%cd%\profile\.cache\huggingface
) )
@rem set the correct installer path (current vs legacy) @rem set the correct installer path (current vs legacy)
@ -103,14 +104,25 @@ call python --version
@cd .. @cd ..
@set SD_UI_PATH=%cd%\ui @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 @cd stable-diffusion
@rem set any overrides @rem set any overrides
set HF_HUB_DISABLE_SYMLINKS_WARNING=true set HF_HUB_DISABLE_SYMLINKS_WARNING=true
@if NOT DEFINED SD_UI_BIND_PORT set SD_UI_BIND_PORT=9000 @uvicorn main:server_api --app-dir "%SD_UI_PATH%" --port %ED_BIND_PORT% --host %ED_BIND_IP% --log-level error
@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
@pause @pause

View File

@ -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/bootstrap.sh scripts/
cp sd-ui-files/scripts/check_modules.py scripts/ cp sd-ui-files/scripts/check_modules.py scripts/
cp sd-ui-files/scripts/check_models.py scripts/ cp sd-ui-files/scripts/check_models.py scripts/
cp sd-ui-files/scripts/get_config.py scripts/
source ./scripts/functions.sh source ./scripts/functions.sh
@ -74,8 +75,17 @@ python --version
cd .. cd ..
export SD_UI_PATH=`pwd`/ui 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 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" read -p "Press any key to continue"

View File

@ -101,51 +101,6 @@ def setConfig(config):
except: except:
log.error(traceback.format_exc()) 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): def save_to_config(ckpt_model_name, vae_model_name, hypernetwork_model_name, vram_usage_level):
config = getConfig() config = getConfig()
if "model" not in config: if "model" not in config:

View File

@ -30,7 +30,7 @@
<h1> <h1>
<img id="logo_img" src="/media/images/icon-512x512.png" > <img id="logo_img" src="/media/images/icon-512x512.png" >
Easy Diffusion Easy Diffusion
<small>v2.5.33 <span id="updateBranchLabel"></span></small> <small>v2.5.34 <span id="updateBranchLabel"></span></small>
</h1> </h1>
</div> </div>
<div id="server-status"> <div id="server-status">

View File

@ -720,9 +720,140 @@ Array.prototype.addEventListener = function(method, callback) {
const originalFunction = this[method] const originalFunction = this[method]
if (originalFunction) { if (originalFunction) {
this[method] = function() { this[method] = function() {
console.log(`Array.${method}()`, arguments)
originalFunction.apply(this, arguments) originalFunction.apply(this, arguments)
callback.apply(this, arguments) callback.apply(this, arguments)
} }
} }
} }
/**
* @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> | (() => (string | Node | Promise<string | Node>)) | undefined} content
* HTML string or HTML element
* @property {((TabOpenDetails, Event) => (undefined | string | Node | Promise<string | Node>)) | 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)
}
})
}

View File

@ -130,34 +130,11 @@
} }
drawDiagram(fn) drawDiagram(fn)
} }
createTab({
/////////////////////// Tab implementation id: 'merge',
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` icon: 'fa-code-merge',
<span id="tab-merge" class="tab"> label: 'Merge models',
<span><i class="fa fa-code-merge icon"></i> Merge models</span> css: `
</span>
`)
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
<div id="tab-content-merge" class="tab-content">
<div id="merge" class="tab-content-inner">
Loading..
</div>
</div>
`)
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', `
<style>
#tab-content-merge .tab-content-inner { #tab-content-merge .tab-content-inner {
max-width: 100%; max-width: 100%;
padding: 10pt; padding: 10pt;
@ -233,226 +210,229 @@
} }
.merge-container #merge-warning { .merge-container #merge-warning {
color: rgb(153, 153, 153); color: rgb(153, 153, 153);
} }`,
</style> content: `
`) <div class="merge-container panel-box">
<div class="merge-input">
merge.innerHTML = ` <p><label for="#mergeModelA">Select Model A:</label></p>
<div class="merge-container panel-box"> <input id="mergeModelA" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
<div class="merge-input"> <p><label for="#mergeModelB">Select Model B:</label></p>
<p><label for="#mergeModelA">Select Model A:</label></p> <input id="mergeModelB" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
<input id="mergeModelA" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" /> <br/><br/>
<p><label for="#mergeModelB">Select Model B:</label></p> <p id="merge-warning"><small><b>Important:</b> Please merge models of similar type.<br/>For e.g. <code>SD 1.4</code> models with only <code>SD 1.4/1.5</code> models,<br/><code>SD 2.0</code> with <code>SD 2.0</code>-type, and <code>SD 2.1</code> with <code>SD 2.1</code>-type models.</small></p>
<input id="mergeModelB" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" /> <br/>
<br/><br/> <table>
<p id="merge-warning"><small><b>Important:</b> Please merge models of similar type.<br/>For e.g. <code>SD 1.4</code> models with only <code>SD 1.4/1.5</code> models,<br/><code>SD 2.0</code> with <code>SD 2.0</code>-type, and <code>SD 2.1</code> with <code>SD 2.1</code>-type models.</small></p> <tr>
<br/> <td><label for="#merge-filename">Output file name:</label></td>
<table> <td><input id="merge-filename" size=24> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Base name of the output file.<br>Mix ratio and file suffix will be appended to this.</span></i></td>
<tr> </tr>
<td><label for="#merge-filename">Output file name:</label></td> <tr>
<td><input id="merge-filename" size=24> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Base name of the output file.<br>Mix ratio and file suffix will be appended to this.</span></i></td> <td><label for="#merge-fp">Output precision:</label></td>
</tr> <td><select id="merge-fp">
<tr> <option value="fp16">fp16 (smaller file size)</option>
<td><label for="#merge-fp">Output precision:</label></td> <option value="fp32">fp32 (larger file size)</option>
<td><select id="merge-fp"> </select>
<option value="fp16">fp16 (smaller file size)</option> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Image generation uses fp16, so it's a good choice.<br>Use fp32 if you want to use the result models for more mixes</span></i>
<option value="fp32">fp32 (larger file size)</option> </td>
</select> </tr>
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Image generation uses fp16, so it's a good choice.<br>Use fp32 if you want to use the result models for more mixes</span></i> <tr>
</td> <td><label for="#merge-format">Output file format:</label></td>
</tr> <td><select id="merge-format">
<tr> <option value="safetensors">Safetensors (recommended)</option>
<td><label for="#merge-format">Output file format:</label></td> <option value="ckpt">CKPT/Pickle (legacy format)</option>
<td><select id="merge-format"> </select>
<option value="safetensors">Safetensors (recommended)</option> </td>
<option value="ckpt">CKPT/Pickle (legacy format)</option> </tr>
</select> </table>
</td> <br/>
</tr> <div id="merge-log-container">
</table> <p><label for="#merge-log">Log messages:</label></p>
<br/> <div id="merge-log"></div>
<div id="merge-log-container"> </div>
<p><label for="#merge-log">Log messages:</label></p> </div>
<div id="merge-log"></div> <div class="merge-config">
</div> <div class="tab-container">
</div> <span id="tab-merge-opts-single" class="tab active">
<div class="merge-config"> <span>Make a single file</small></span>
<div class="tab-container"> </span>
<span id="tab-merge-opts-single" class="tab active"> <span id="tab-merge-opts-batch" class="tab">
<span>Make a single file</small></span> <span>Make multiple variations</small></span>
</span> </span>
<span id="tab-merge-opts-batch" class="tab"> </div>
<span>Make multiple variations</small></span> <div>
</span> <div id="tab-content-merge-opts-single" class="tab-content active">
</div> <div class="tab-content-inner">
<div> <small>Saves a single merged model file, at the specified merge ratio.</small><br/><br/>
<div id="tab-content-merge-opts-single" class="tab-content active"> <label for="#single-merge-ratio-slider">Merge ratio:</label>
<div class="tab-content-inner"> <input id="single-merge-ratio-slider" name="single-merge-ratio-slider" class="editor-slider" value="50" type="range" min="1" max="1000">
<small>Saves a single merged model file, at the specified merge ratio.</small><br/><br/> <input id="single-merge-ratio" size=2 value="5">%
<label for="#single-merge-ratio-slider">Merge ratio:</label> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Model A's contribution to the mix. The rest will be from Model B.</span></i>
<input id="single-merge-ratio-slider" name="single-merge-ratio-slider" class="editor-slider" value="50" type="range" min="1" max="1000"> </div>
<input id="single-merge-ratio" size=2 value="5">%
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Model A's contribution to the mix. The rest will be from Model B.</span></i>
</div> </div>
</div> <div id="tab-content-merge-opts-batch" class="tab-content">
<div id="tab-content-merge-opts-batch" class="tab-content"> <div class="tab-content-inner">
<div class="tab-content-inner"> <small>Saves multiple variations of the model, at different merge ratios.<br/>Each variation will be saved as a separate file.</small><br/><br/>
<small>Saves multiple variations of the model, at different merge ratios.<br/>Each variation will be saved as a separate file.</small><br/><br/> <table>
<table> <tr><td><label for="#merge-count">Number of variations:</label></td>
<tr><td><label for="#merge-count">Number of variations:</label></td> <td> <input id="merge-count" size=2 value="5"></td>
<td> <input id="merge-count" size=2 value="5"></td> <td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Number of models to create</span></i></td></tr>
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Number of models to create</span></i></td></tr> <tr><td><label for="#merge-start">Starting merge ratio:</label></td>
<tr><td><label for="#merge-start">Starting merge ratio:</label></td> <td> <input id="merge-start" size=2 value="5">%</td>
<td> <input id="merge-start" size=2 value="5">%</td> <td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Smallest share of model A in the mix</span></i></td></tr>
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Smallest share of model A in the mix</span></i></td></tr> <tr><td><label for="#merge-step">Increment each step:</label></td>
<tr><td><label for="#merge-step">Increment each step:</label></td> <td> <input id="merge-step" size=2 value="10">%</td>
<td> <input id="merge-step" size=2 value="10">%</td> <td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Share of model A added into the mix per step</span></i></td></tr>
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Share of model A added into the mix per step</span></i></td></tr> <tr><td><label for="#merge-interpolation">Interpolation model:</label></td>
<tr><td><label for="#merge-interpolation">Interpolation model:</label></td> <td> <select id="merge-interpolation">
<td> <select id="merge-interpolation"> <option>Exact</option>
<option>Exact</option> <option>SmoothStep</option>
<option>SmoothStep</option> <option>SmootherStep</option>
<option>SmootherStep</option> <option>SmoothestStep</option>
<option>SmoothestStep</option> </select></td>
</select></td> <td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Sigmoid function to be applied to the model share before mixing</span></i></td></tr>
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Sigmoid function to be applied to the model share before mixing</span></i></td></tr> </table>
</table> <br/>
<br/> <small>Preview of variation ratios:</small><br/>
<small>Preview of variation ratios:</small><br/> <canvas id="merge-canvas" width="400" height="400"></canvas>
<canvas id="merge-canvas" width="400" height="400"></canvas> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="merge-buttons">
<div class="merge-buttons"> <button id="merge-button" class="primaryButton">Merge models</button>
<button id="merge-button" class="primaryButton">Merge models</button> </div>
</div> </div>`,
</div>` onOpen: ({ firstOpen }) => {
if (!firstOpen) {
const tabSettingsSingle = document.querySelector('#tab-merge-opts-single') return
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('<i>Aborting: maximum ratio is &#8805; 100%</i>')
addLogMessage('Reduce the number of variations or the step size')
addLogSeparator()
document.querySelector('#merge-count').focus()
return
}
if (document.querySelector('#merge-filename').value == "") {
addLogMessage('<i>Aborting: No output file name specified</i>')
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<iterations; i++) {
let alpha = ( start + i * step ) / 100
switch (document.querySelector('#merge-interpolation').value) {
case 'SmoothStep':
alpha = smoothstep(alpha)
break
case 'SmootherStep':
alpha = smootherstep(alpha)
break
case 'SmoothestStep':
alpha = smootheststep(alpha)
break
} }
addLogMessage(`merging batch job ${i+1}/${iterations}, alpha = ${alpha.toFixed(5)}...`)
request['out_path'] = document.querySelector('#merge-filename').value const tabSettingsSingle = document.querySelector('#tab-merge-opts-single')
request['out_path'] += '-' + alpha.toFixed(5) + '.' + document.querySelector('#merge-format').value const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch')
addLogMessage(`&nbsp;&nbsp;filename: ${request['out_path']}`) linkTabContents(tabSettingsSingle)
linkTabContents(tabSettingsBatch)
request['ratio'] = alpha console.log('Activate')
let res = await fetch('/model/merge', { let mergeModelAField = new ModelDropdown(document.querySelector('#mergeModelA'), 'stable-diffusion')
method: 'POST', let mergeModelBField = new ModelDropdown(document.querySelector('#mergeModelB'), 'stable-diffusion')
headers: { 'Content-Type': 'application/json' }, updateChart()
body: JSON.stringify(request) })
const data = await res.json();
addLogMessage(JSON.stringify(data))
}
addLogMessage("<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> 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 // slider
stableDiffusionModelField.innerHTML = '' const singleMergeRatioField = document.querySelector('#single-merge-ratio')
vaeModelField.innerHTML = '' const singleMergeRatioSlider = document.querySelector('#single-merge-ratio-slider')
hypernetworkModelField.innerHTML = ''
await getModels() 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('<i>Aborting: maximum ratio is &#8805; 100%</i>')
addLogMessage('Reduce the number of variations or the step size')
addLogSeparator()
document.querySelector('#merge-count').focus()
return
}
if (document.querySelector('#merge-filename').value == "") {
addLogMessage('<i>Aborting: No output file name specified</i>')
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<iterations; i++) {
let alpha = ( start + i * step ) / 100
switch (document.querySelector('#merge-interpolation').value) {
case 'SmoothStep':
alpha = smoothstep(alpha)
break
case 'SmootherStep':
alpha = smootherstep(alpha)
break
case 'SmoothestStep':
alpha = smootheststep(alpha)
break
}
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(`&nbsp;&nbsp;filename: ${request['out_path']}`)
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("<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> 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()
})
},
}) })
})() })()

View File

@ -9,56 +9,41 @@
} }
} }
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` createTab({
<span id="tab-news" class="tab"> id: 'news',
<span><i class="fa fa-bolt icon"></i> What's new?</span> icon: 'fa-bolt',
</span> label: "What's new",
`) css: `
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
<div id="tab-content-news" class="tab-content">
<div id="news" class="tab-content-inner">
Loading..
</div>
</div>
`)
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', `
<style>
#tab-content-news .tab-content-inner { #tab-content-news .tab-content-inner {
max-width: 100%; max-width: 100%;
text-align: left; text-align: left;
padding: 10pt; padding: 10pt;
} }
</style> `,
`) 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')
let appConfig = await fetch('/get/app_config') if (!appConfig.ok) {
if (!appConfig.ok) { console.error('[release-notes] Failed to get app_config.')
console.error('[release-notes] Failed to get app_config.') return
return }
} appConfig = await appConfig.json()
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`) return marked.parse(releaseNotes)
if (!releaseNotes.ok) { }
console.error('[release-notes] Failed to get CHANGES.md.') },
return
}
releaseNotes = await releaseNotes.text()
news.innerHTML = marked.parse(releaseNotes)
}) })
})() })()