Merge branch 'beta' into scanner

This commit is contained in:
cmdr2 2022-11-18 16:01:50 +05:30 committed by GitHub
commit 1f815d7562
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 199 additions and 49 deletions

View File

@ -1,10 +1,30 @@
# What's new?
### 2.4.6
* 16 Nov 2022 - Fix a regression in VRAM usage during startup, which caused 'Out of Memory' errors when starting on GPUs with 4gb (or less) VRAM
* 16 Nov 2022 - Add Picklescan - a scanner for malicious model files. If it finds a malicious file, it will halt the web application and alert the user
## v2.4
### Major Changes
- **Support for custom VAE models**. You can place your VAE files in the `models/vae` folder, and refresh the browser page to use them. More info: https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder
- **Experimental support for multiple GPUs!** It should work automatically. Just open one browser tab per GPU, and spread your tasks across your GPUs. For e.g. open our UI in two browser tabs if you have two GPUs. You can customize which GPUs it should use in the "Settings" tab, otherwise let it automatically pick the best GPUs. Thanks @madrang . More info: https://github.com/cmdr2/stable-diffusion-ui/wiki/Run-on-Multiple-GPUs
- **Cleaner UI design** - Show settings and help in new tabs, instead of dropdown popups (which were buggy). Thanks @mdiller
- **Progress bar.** Thanks @mdiller
- **Custom Image Modifiers** - You can now save your custom image modifiers! Your saved modifiers can include special characters like `{}, (), [], |`
- Drag and Drop **text files generated from previously saved images**, and copy settings to clipboard. Thanks @madrang
- Paste settings from clipboard. Thanks @JeLuf
- Bug fixes to reduce the chances of tasks crashing during long multi-hour runs (chrome can put long-running background tabs to sleep). Thanks @JeLuf and @madrang
- **Improved documentation.** Thanks @JeLuf and @jsuelwald
- Improved the codebase for dealing with system settings and UI settings. Thanks @mdiller
- Help instructions next to some setttings, and in the tab
- Show system info in the settings tab
- Keyboard shortcut: Ctrl+Enter to start a task
- Configuration to prevent the browser from opening on startup
- Lots of minor bug fixes
- A `What's New?` tab in the UI
### 2.4.5
* 16 Nov 2022 - Add checkbox for "Open browser on startup".
* 16 Nov 2022 - Add a directory for core plugins that ship with Stable Diffusion UI by default.
* 16 Nov 2022 - Add a "What's New?" tab as a core plugin, which fetches the contents of CHANGES.md from the app's release branch.
### Detailed changelog
* 2.4.9 - 18 Nov 2022 - Add Picklescan - a scanner for malicious model files. If it finds a malicious file, it will halt the web application and alert the user. Thanks @JeLuf
* 2.4.8 - 18 Nov 2022 - A `Use as Input` button to use the settings from a previously generated image task. Thanks @patriceac
* 2.4.7 - 18 Nov 2022 - Don't crash if a VAE file fails to load
* 2.4.7 - 17 Nov 2022 - Fix a bug where Face Correction (GFPGAN) would fail on cuda:N (i.e. GPUs other than cuda:0), as well as fail on CPU if the system had an incompatible GPU.
* 2.4.6 - 16 Nov 2022 - Fix a regression in VRAM usage during startup, which caused 'Out of Memory' errors when starting on GPUs with 4gb (or less) VRAM
* 2.4.5 - 16 Nov 2022 - Add checkbox for "Open browser on startup".
* 2.4.5 - 16 Nov 2022 - Add a directory for core plugins that ship with Stable Diffusion UI by default.
* 2.4.5 - 16 Nov 2022 - Add a "What's New?" tab as a core plugin, which fetches the contents of CHANGES.md from the app's release branch.

View File

@ -7,7 +7,7 @@
<link rel="icon" type="image/png" href="/media/images/favicon-32x32.png" sizes="32x32">
<link rel="stylesheet" href="/media/css/fonts.css?v=1">
<link rel="stylesheet" href="/media/css/themes.css?v=3">
<link rel="stylesheet" href="/media/css/main.css?v=17">
<link rel="stylesheet" href="/media/css/main.css?v=22">
<link rel="stylesheet" href="/media/css/auto-save.css?v=5">
<link rel="stylesheet" href="/media/css/modifier-thumbnails.css?v=4">
<link rel="stylesheet" href="/media/css/fontawesome-all.min.css?v=1">
@ -20,7 +20,7 @@
<div id="container">
<div id="top-nav">
<div id="logo">
<h1>Stable Diffusion UI <small>v2.4.6 <span id="updateBranchLabel"></span></small></h1>
<h1>Stable Diffusion UI <small>v2.4.8 <span id="updateBranchLabel"></span></small></h1>
</div>
<div id="server-status">
<div id="server-status-color"></div>
@ -264,6 +264,7 @@
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/UI-Overview" target="_blank"><i class="fa-solid fa-list fa-fw"></i> UI Overview</a>
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Writing-Prompts" target="_blank"><i class="fa-solid fa-pen-to-square fa-fw"></i> Writing prompts</a>
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Inpainting" target="_blank"><i class="fa-solid fa-paintbrush fa-fw"></i> Inpainting</a>
<li> <a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Run-on-Multiple-GPUs" target="_blank"><i class="fa-solid fa-paintbrush fa-fw"></i> Run on Multiple GPUs</a>
</ul>
<li><span class="help-section">Installation</span>
@ -328,15 +329,15 @@
</div>
</body>
<script src="media/js/parameters.js?v=9"></script>
<script src="media/js/plugins.js?v=1"></script>
<script src="media/js/utils.js?v=6"></script>
<script src="media/js/parameters.js?v=10"></script>
<script src="media/js/plugins.js?v=1"></script>
<script src="media/js/inpainting-editor.js?v=1"></script>
<script src="media/js/image-modifiers.js?v=6"></script>
<script src="media/js/image-modifiers.js?v=7"></script>
<script src="media/js/auto-save.js?v=8"></script>
<script src="media/js/main.js?v=23"></script>
<script src="media/js/main.js?v=26"></script>
<script src="media/js/themes.js?v=4"></script>
<script src="media/js/dnd.js?v=9"></script>
<script src="media/js/dnd.js?v=11"></script>
<script>
async function init() {
await initSettings()

View File

@ -22,6 +22,11 @@ a:visited {
label {
font-size: 10pt;
}
code {
background: var(--background-color4);
padding: 2px 4px;
border-radius: 4px;
}
#prompt {
width: 100%;
height: 65pt;
@ -437,6 +442,17 @@ img {
.secondaryButton:hover {
background: rgb(177, 27, 0);
}
.useSettings {
background: var(--accent-color);
border: 1px solid var(--accent-color);
color: rgb(255, 221, 255);
padding: 3pt 6pt;
margin-right: 6pt;
float: right;
}
.useSettings:hover {
background: hsl(var(--accent-hue), 100%, calc(var(--accent-lightness) + 6%));
}
.stopTask {
float: right;
}
@ -657,7 +673,7 @@ input::file-selector-button {
@media (min-width: 700px) {
/* #editor {
max-width: 480px;
} */
}*/
.float-container {
padding: 20px;
}
@ -898,6 +914,9 @@ input::file-selector-button {
i.active {
background: var(--accent-color);
}
.primaryButton.active {
background: hsl(var(--accent-hue), 100%, 50%);
}
#system-info {
max-width: 800px;
font-size: 10pt;

View File

@ -243,7 +243,9 @@ const TASK_MAPPING = {
parse: (val) => val
}
}
function restoreTaskToUI(task) {
function restoreTaskToUI(task, fieldsToSkip) {
fieldsToSkip = fieldsToSkip || []
if ('numOutputsTotal' in task) {
numOutputsTotalField.value = task.numOutputsTotal
}
@ -255,10 +257,47 @@ function restoreTaskToUI(task) {
return
}
for (const key in TASK_MAPPING) {
if (key in task.reqBody) {
if (key in task.reqBody && !fieldsToSkip.includes(key)) {
TASK_MAPPING[key].setUI(task.reqBody[key])
}
}
// restore the original tag
promptField.value = task.reqBody.original_prompt || task.reqBody.prompt
// Restore modifiers
if (task.reqBody.active_tags) {
refreshModifiersState(task.reqBody.active_tags)
}
// properly reset checkboxes
if (!('use_face_correction' in task.reqBody)) {
useFaceCorrectionField.checked = false
}
if (!('use_upscale' in task.reqBody)) {
useUpscalingField.checked = false
}
if (!('mask' in task.reqBody)) {
maskSetting.checked = false
}
upscaleModelField.disabled = !useUpscalingField.checked
// Show the source picture if present
initImagePreview.src = (task.reqBody.init_image == undefined ? '' : task.reqBody.init_image)
if (IMAGE_REGEX.test(initImagePreview.src)) {
Boolean(task.reqBody.mask) ? inpaintingEditor.setImg(task.reqBody.mask) : inpaintingEditor.resetBackground()
initImagePreviewContainer.style.display = 'block'
inpaintingEditorContainer.style.display = 'none'
promptStrengthContainer.style.display = 'table-row'
//samplerSelectionContainer.style.display = 'none'
// maskSetting.checked = false
inpaintingEditorContainer.style.display = maskSetting.checked ? 'block' : 'none'
} else {
initImagePreviewContainer.style.display = 'none'
// inpaintingEditorContainer.style.display = 'none'
promptStrengthContainer.style.display = 'none'
// maskSetting.style.display = 'none'
}
}
function readUI() {
const reqBody = {}
@ -430,10 +469,10 @@ function checkWriteToClipboardPermission (result) {
copyIcon.innerHTML = `<span class="simple-tooltip right">Copy Image Settings</span>`
copyIcon.addEventListener('click', (event) => {
event.stopPropagation()
// Add css class 'active'
copyIcon.classList.add('active')
// In 1000 ms remove the 'active' class
asyncDelay(1000).then(() => copyIcon.classList.remove('active'))
// Add css class 'active'
copyIcon.classList.add('active')
// In 350 ms remove the 'active' class
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)) {
@ -450,10 +489,10 @@ function checkWriteToClipboardPermission (result) {
pasteIcon.innerHTML = `<span class="simple-tooltip right">Paste Image Settings</span>`
pasteIcon.addEventListener('click', (event) => {
event.stopPropagation()
// Add css class 'active'
pasteIcon.classList.add('active')
// In 1000 ms remove the 'active' class
asyncDelay(1000).then(() => pasteIcon.classList.remove('active'))
// Add css class 'active'
pasteIcon.classList.add('active')
// In 350 ms remove the 'active' class
asyncDelay(350).then(() => pasteIcon.classList.remove('active'))
pasteFromClipboard()
})
resetSettings.parentNode.insertBefore(pasteIcon, resetSettings)

View File

@ -148,6 +148,58 @@ async function loadModifiers() {
loadCustomModifiers()
}
function refreshModifiersState(newTags) {
// clear existing modifiers
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => {
const modifierName = modifierCard.querySelector('.modifier-card-label').innerText
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 => {
let found = false
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(modifierCard => {
const modifierName = modifierCard.querySelector('.modifier-card-label').innerText
if (tag == modifierName) {
// add modifier to active array
activeTags.push({
'name': modifierName,
'element': modifierCard.cloneNode(true),
'originElement': modifierCard
})
modifierCard.classList.add(activeCardClass)
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-'
found = true
}
})
if (found == false) { // custom tag went missing, create one here
let modifierCard = createModifierCard(tag, undefined) // create a modifier card for the missing tag, no image
modifierCard.addEventListener('click', () => {
if (activeTags.map(x => x.name).includes(tag)) {
// remove modifier from active array
activeTags = activeTags.filter(x => x.name != tag)
modifierCard.classList.remove(activeCardClass)
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+'
}
refreshTagsList()
})
activeTags.push({
'name': tag,
'element': modifierCard,
'originElement': undefined // no origin element for missing tags
})
}
})
refreshTagsList()
}
function refreshTagsList() {
editorModifierTagsList.innerHTML = ''
@ -167,7 +219,7 @@ function refreshTagsList() {
tag.element.addEventListener('click', () => {
let idx = activeTags.indexOf(tag)
if (idx !== -1) {
if (idx !== -1 && activeTags[idx].originElement !== undefined) {
activeTags[idx].originElement.classList.remove(activeCardClass)
activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+'

View File

@ -789,7 +789,9 @@ function getCurrentUserRequest() {
stream_progress_updates: true,
stream_image_progress: (numOutputsTotal > 50 ? false : streamImageProgressField.checked),
show_only_filtered_image: showOnlyFilteredImageField.checked,
output_format: outputFormatField.value
output_format: outputFormatField.value,
original_prompt: promptField.value,
active_tags: (activeTags.map(x => x.name))
}
}
if (IMAGE_REGEX.test(initImagePreview.src)) {
@ -856,6 +858,7 @@ function createTask(task) {
taskEntry.innerHTML = ` <div class="header-content panel collapsible active">
<div class="taskStatusLabel">Enqueued</div>
<button class="secondaryButton stopTask"><i class="fa-solid fa-trash-can"></i> Remove</button>
<button class="secondaryButton useSettings"><i class="fa-solid fa-redo"></i> Use as Input</button>
<div class="preview-prompt collapsible active"></div>
<div class="taskConfig">${taskConfig}</div>
<div class="outputMsg"></div>
@ -894,6 +897,12 @@ function createTask(task) {
}
})
task['useSettings'] = taskEntry.querySelector('.useSettings')
task['useSettings'].addEventListener('click', function(e) {
e.stopPropagation()
restoreTaskToUI(task, TASK_REQ_NO_EXPORT)
})
imagePreview.insertBefore(taskEntry, previewTools.nextSibling)
task.previewPrompt.innerText = task.reqBody.prompt

View File

@ -327,4 +327,7 @@ saveSettingsBtn.addEventListener('click', function() {
'update_branch': updateBranch,
'ui_open_browser_on_start': uiOpenBrowserOnStartField.checked
})
saveSettingsBtn.classList.add('active')
asyncDelay(300).then(() => saveSettingsBtn.classList.remove('active'))
})

View File

@ -140,15 +140,24 @@ def load_model_ckpt():
_, _ = modelFS.load_state_dict(sd, strict=False)
if thread_data.vae_file is not None:
for model_extension in ['.ckpt', '.vae.pt']:
if os.path.exists(thread_data.vae_file + model_extension):
print(f"Loading VAE weights from: {thread_data.vae_file}{model_extension}")
vae_ckpt = torch.load(thread_data.vae_file + model_extension, map_location="cpu")
vae_dict = {k: v for k, v in vae_ckpt["state_dict"].items() if k[0:4] != "loss"}
modelFS.first_stage_model.load_state_dict(vae_dict, strict=False)
break
else:
print(f'Cannot find VAE file: {thread_data.vae_file}{model_extension}')
try:
loaded = False
for model_extension in ['.ckpt', '.vae.pt']:
if os.path.exists(thread_data.vae_file + model_extension):
print(f"Loading VAE weights from: {thread_data.vae_file}{model_extension}")
vae_ckpt = torch.load(thread_data.vae_file + model_extension, map_location="cpu")
vae_dict = {k: v for k, v in vae_ckpt["state_dict"].items() if k[0:4] != "loss"}
modelFS.first_stage_model.load_state_dict(vae_dict, strict=False)
loaded = True
break
if not loaded:
print(f'Cannot find VAE: {thread_data.vae_file}')
thread_data.vae_file = None
except:
print(traceback.format_exc())
print(f'Could not load VAE: {thread_data.vae_file}')
thread_data.vae_file = None
modelFS.eval()
# if thread_data.device != 'cpu':
@ -236,9 +245,14 @@ def wait_model_move_to(model, target_device): # Send to target_device and wait u
def load_model_gfpgan():
if thread_data.gfpgan_file is None: raise ValueError(f'Thread gfpgan_file is undefined.')
# hack for a bug in facexlib: https://github.com/xinntao/facexlib/pull/19/files
from facexlib.detection import retinaface
retinaface.device = torch.device(thread_data.device)
print('forced retinaface.device to', thread_data.device)
model_path = thread_data.gfpgan_file + ".pth"
device = 'cuda:0' if force_gfpgan_to_cuda0 else thread_data.device
thread_data.model_gfpgan = GFPGANer(device=torch.device(device), model_path=model_path, upscale=1, arch='clean', channel_multiplier=2, bg_upsampler=None)
thread_data.model_gfpgan = GFPGANer(device=torch.device(thread_data.device), model_path=model_path, upscale=1, arch='clean', channel_multiplier=2, bg_upsampler=None)
print('loaded', thread_data.gfpgan_file, 'to', thread_data.model_gfpgan.device, 'precision', thread_data.precision)
def load_model_real_esrgan():
@ -288,10 +302,10 @@ def apply_filters(filter_name, image_data, model_path=None):
print(f'Applying filter {filter_name}...')
gc() # Free space before loading new data.
if filter_name == 'gfpgan':
if isinstance(image_data, torch.Tensor):
image_data.to('cuda:0' if force_gfpgan_to_cuda0 else thread_data.device)
if isinstance(image_data, torch.Tensor):
image_data.to(thread_data.device)
if filter_name == 'gfpgan':
if model_path is not None and model_path != thread_data.gfpgan_file:
thread_data.gfpgan_file = model_path
load_model_gfpgan()
@ -303,9 +317,6 @@ def apply_filters(filter_name, image_data, model_path=None):
image_data = output[:,:,::-1]
if filter_name == 'real_esrgan':
if isinstance(image_data, torch.Tensor):
image_data.to(thread_data.device)
if model_path is not None and model_path != thread_data.real_esrgan_file:
thread_data.real_esrgan_file = model_path
load_model_real_esrgan()

View File

@ -217,10 +217,6 @@ def thread_get_next_task():
task = None
try: # Select a render task.
for queued_task in tasks_queue:
if queued_task.request.use_face_correction and runtime.thread_data.device == 'cpu' and is_alive() == 1:
queued_task.error = Exception('The CPU cannot be used to run this task currently. Please remove "Fix incorrect faces" from Image Settings and try again.')
task = queued_task
break
if queued_task.render_device and runtime.thread_data.device != queued_task.render_device:
# Is asking for a specific render device.
if is_alive(queued_task.render_device) > 0:
@ -440,7 +436,7 @@ def stop_render_thread(device):
try:
device_manager.validate_device_id(device, log_prefix='stop_render_thread')
except:
print(traceback.format_exec())
print(traceback.format_exc())
return False
if not manager_lock.acquire(blocking=True, timeout=LOCK_TIMEOUT): raise Exception('stop_render_thread' + ERR_LOCK_FAILED)