mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2025-01-14 02:08:32 +01:00
commit
5b47da67f6
27
3rd-PARTY-LICENSES
Normal file
27
3rd-PARTY-LICENSES
Normal file
@ -0,0 +1,27 @@
|
||||
jquery-confirm
|
||||
==============
|
||||
https://craftpip.github.io/jquery-confirm/
|
||||
|
||||
jquery-confirm is licensed under the MIT license:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Boniface Pereira
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -19,8 +19,14 @@
|
||||
- Configuration to prevent the browser from opening on startup
|
||||
- Lots of minor bug fixes
|
||||
- A `What's New?` tab in the UI
|
||||
- Ask for a confimation before clearing the results pane or stopping a render task. The dialog can be skipped by holding down the shift key while clicking on the button.
|
||||
- Show the network addresses of the server in the systems setting dialog
|
||||
|
||||
### Detailed changelog
|
||||
* 2.4.17 - 30 Nov 2022 - Scroll to generated image. Thanks @patriceac
|
||||
* 2.4.17 - 30 Nov 2022 - Show the network addresses of the server in the systems setting dialog. Thanks @JeLuf
|
||||
* 2.4.17 - 30 Nov 2022 - Fix a bug where GFPGAN wouldn't work properly when multiple GPUs tried to run it at the same time. Thanks @madrang
|
||||
* 2.4.17 - 30 Nov 2022 - Confirm before stopping or clearing all the tasks. Thanks @JeLuf
|
||||
* 2.4.16 - 29 Nov 2022 - Bug fixes for SD 2.0 - remove the need for patching, default to SD 1.4 model if trying to load an SD2 model in SD1.4.
|
||||
* 2.4.15 - 25 Nov 2022 - Experimental support for SD 2.0. Uses lots of memory, not optimized, probably GPU-only.
|
||||
* 2.4.14 - 22 Nov 2022 - Change the backend to a custom fork of Stable Diffusion
|
||||
|
@ -44,7 +44,7 @@ if NOT DEFINED test_sd2 set test_sd2=N
|
||||
@call git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a
|
||||
)
|
||||
if "%test_sd2%" == "Y" (
|
||||
@call git -c advice.detachedHead=false checkout 8878d67decd3deb3c98472c1e39d2a51dc5950f9
|
||||
@call git -c advice.detachedHead=false checkout 5d647c5459f4cd790672512222bc41903c01bb71
|
||||
)
|
||||
|
||||
@cd ..
|
||||
|
@ -38,7 +38,7 @@ if [ -e "scripts/install_status.txt" ] && [ `grep -c sd_git_cloned scripts/insta
|
||||
if [ "$test_sd2" == "N" ]; then
|
||||
git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a
|
||||
elif [ "$test_sd2" == "Y" ]; then
|
||||
git -c advice.detachedHead=false checkout 8878d67decd3deb3c98472c1e39d2a51dc5950f9
|
||||
git -c advice.detachedHead=false checkout 5d647c5459f4cd790672512222bc41903c01bb71
|
||||
fi
|
||||
|
||||
cd ..
|
||||
|
@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<title>Stable Diffusion UI</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#673AB6">
|
||||
<link rel="icon" type="image/png" href="/media/images/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="/media/images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="stylesheet" href="/media/css/fonts.css">
|
||||
@ -12,7 +13,10 @@
|
||||
<link rel="stylesheet" href="/media/css/modifier-thumbnails.css">
|
||||
<link rel="stylesheet" href="/media/css/fontawesome-all.min.css">
|
||||
<link rel="stylesheet" href="/media/css/drawingboard.min.css">
|
||||
<link rel="stylesheet" href="/media/css/jquery-confirm.min.css">
|
||||
<link rel="manifest" href="/media/manifest.webmanifest">
|
||||
<script src="/media/js/jquery-3.6.1.min.js"></script>
|
||||
<script src="/media/js/jquery-confirm.min.js"></script>
|
||||
<script src="/media/js/drawingboard.min.js"></script>
|
||||
<script src="/media/js/marked.min.js"></script>
|
||||
</head>
|
||||
@ -22,7 +26,7 @@
|
||||
<div id="logo">
|
||||
<h1>
|
||||
Stable Diffusion UI
|
||||
<small>v2.4.16 <span id="updateBranchLabel"></span></small>
|
||||
<small>v2.4.17 <span id="updateBranchLabel"></span></small>
|
||||
</h1>
|
||||
</div>
|
||||
<div id="server-status">
|
||||
@ -67,7 +71,7 @@
|
||||
<div id="init_image_wrapper">
|
||||
<img id="init_image_preview" src="" />
|
||||
<span id="init_image_size_box"></span>
|
||||
<button class="init_image_clear image_clear_btn">X</button>
|
||||
<button class="init_image_clear image_clear_btn"><i class="fa-solid fa-xmark"></i></button>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
@ -82,7 +86,7 @@
|
||||
</div>
|
||||
|
||||
<div id="editor-inputs-tags-container" class="row">
|
||||
<label>Image Modifiers: <small>(click an Image Modifier to remove it)</small></label>
|
||||
<label>Image Modifiers <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">click an Image Modifier to remove it, use Ctrl+Mouse Wheel to adjust its weight</span></i>:</label>
|
||||
<div id="editor-inputs-tags-list"></div>
|
||||
</div>
|
||||
|
||||
@ -250,8 +254,17 @@
|
||||
<br/><br/>
|
||||
<div>
|
||||
<h3><i class="fa fa-microchip icon"></i> System Info</h3>
|
||||
<div id="system-info"></div>
|
||||
<div id="system-info">
|
||||
<table>
|
||||
<tr><td><label>Processor:</label></td><td id="system-info-cpu" class="value"></td></tr>
|
||||
<tr><td><label>Compatible Graphics Cards (all):</label></td><td id="system-info-gpus-all" class="value"></td></tr>
|
||||
<tr><td></td><td> </td></tr>
|
||||
<tr><td><label>Used for rendering 🔥:</label></td><td id="system-info-rendering-devices" class="value"></td></tr>
|
||||
<tr><td><label>Server Addresses <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">You can access Stable Diffusion UI from other devices using these addresses</span></i> :</label></td><td id="system-info-server-hosts" class="value"></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-content-about" class="tab-content">
|
||||
@ -348,7 +361,7 @@ async function init() {
|
||||
await getAppConfig()
|
||||
await loadModifiers()
|
||||
await loadUIPlugins()
|
||||
await getDevices()
|
||||
await getSystemInfo()
|
||||
|
||||
setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000)
|
||||
healthCheck()
|
||||
|
9
ui/media/css/jquery-confirm.min.css
vendored
Normal file
9
ui/media/css/jquery-confirm.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -64,6 +64,11 @@ code {
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
.image_clear_btn:active {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: auto;
|
||||
}
|
||||
.settings-box ul {
|
||||
font-size: 9pt;
|
||||
margin-bottom: 5px;
|
||||
@ -734,6 +739,15 @@ input::file-selector-button {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
#server-status #server-status-msg {
|
||||
display: none;
|
||||
}
|
||||
#server-status:hover #server-status-msg {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
/* #editor {
|
||||
max-width: 480px;
|
||||
@ -1001,8 +1015,17 @@ button:hover {
|
||||
button:active {
|
||||
transition-duration: 0.1s;
|
||||
background-color: hsl(var(--accent-hue), 100%, calc(var(--accent-lightness) + 24%));
|
||||
position: relative;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
button#save-system-settings-btn {
|
||||
padding: 4pt 8pt;
|
||||
}
|
||||
#ip-info a {
|
||||
color:var(--text-color)
|
||||
}
|
||||
#ip-info div {
|
||||
line-height: 200%;
|
||||
}
|
||||
|
@ -30,6 +30,9 @@
|
||||
--primary-button-border: none;
|
||||
--input-switch-padding: 1px;
|
||||
--input-height: 18px;
|
||||
|
||||
/* Main theme color, hex color fallback. */
|
||||
--theme-color-fallback: #673AB6;
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
@ -44,6 +47,8 @@
|
||||
--input-text-color: black;
|
||||
--input-background-color: #f8f9fa;
|
||||
--input-border-color: grey;
|
||||
|
||||
--theme-color-fallback: #aaaaaa;
|
||||
}
|
||||
|
||||
.theme-discord {
|
||||
@ -58,6 +63,8 @@
|
||||
--input-border-size: 2px;
|
||||
--input-background-color: #202225;
|
||||
--input-border-color: var(--input-background-color);
|
||||
|
||||
--theme-color-fallback: #202225;
|
||||
}
|
||||
|
||||
.theme-cool-blue {
|
||||
@ -71,8 +78,10 @@
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step))));
|
||||
|
||||
--input-background-color: var(--background-color3);
|
||||
|
||||
|
||||
--accent-hue: 212;
|
||||
|
||||
--theme-color-fallback: #0056b8;
|
||||
}
|
||||
|
||||
|
||||
@ -87,6 +96,8 @@
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step))));
|
||||
|
||||
--input-background-color: var(--background-color3);
|
||||
|
||||
--theme-color-fallback: #5300b8;
|
||||
}
|
||||
|
||||
.theme-super-dark {
|
||||
@ -101,6 +112,8 @@
|
||||
|
||||
--input-background-color: var(--background-color3);
|
||||
--input-border-size: 0px;
|
||||
|
||||
--theme-color-fallback: #000000;
|
||||
}
|
||||
|
||||
.theme-wild {
|
||||
@ -117,8 +130,8 @@
|
||||
|
||||
--input-border-size: 1px;
|
||||
--input-background-color: hsl(222, var(--main-saturation), calc(var(--value-base) - (2 * var(--value-step))));
|
||||
--input-text-color: red;
|
||||
--input-border-color: green;
|
||||
--input-text-color: #FF0000;
|
||||
--input-border-color: #005E05;
|
||||
}
|
||||
|
||||
.theme-gnomie {
|
||||
@ -136,6 +149,8 @@
|
||||
--input-background-color: #2a2a2a;
|
||||
--input-border-size: 0px;
|
||||
--input-border-color: var(--input-background-color);
|
||||
|
||||
--theme-color-fallback: #2168bf;
|
||||
}
|
||||
|
||||
.theme-gnomie .panel-box {
|
||||
|
@ -35,6 +35,7 @@ const SETTINGS_IDS_LIST = [
|
||||
"sound_toggle",
|
||||
"turbo",
|
||||
"use_full_precision",
|
||||
"confirm_dangerous_actions",
|
||||
"auto_save_settings"
|
||||
]
|
||||
|
||||
@ -55,6 +56,9 @@ async function initSettings() {
|
||||
if (!element) {
|
||||
console.error(`Missing settings element ${id}`)
|
||||
}
|
||||
if (id in SETTINGS) { // don't create it again
|
||||
return
|
||||
}
|
||||
SETTINGS[id] = {
|
||||
key: id,
|
||||
element: element,
|
||||
|
@ -85,14 +85,13 @@ function createModifierGroup(modifierGroup, initiallyExpanded) {
|
||||
|
||||
if(typeof modifierCard == 'object') {
|
||||
modifiersEl.appendChild(modifierCard)
|
||||
const trimmedName = trimModifiers(modifierName)
|
||||
|
||||
modifierCard.addEventListener('click', () => {
|
||||
if (activeTags.map(x => x.name).includes(modifierName)) {
|
||||
if (activeTags.map(x => trimModifiers(x.name)).includes(trimmedName)) {
|
||||
// remove modifier from active array
|
||||
activeTags = activeTags.filter(x => x.name != modifierName)
|
||||
modifierCard.classList.remove(activeCardClass)
|
||||
|
||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+'
|
||||
activeTags = activeTags.filter(x => trimModifiers(x.name) != trimmedName)
|
||||
toggleCardState(modifierCard, false)
|
||||
} else {
|
||||
// add modifier to active array
|
||||
activeTags.push({
|
||||
@ -101,10 +100,7 @@ function createModifierGroup(modifierGroup, initiallyExpanded) {
|
||||
'originElement': modifierCard,
|
||||
'previews': modifierPreviews
|
||||
})
|
||||
|
||||
modifierCard.classList.add(activeCardClass)
|
||||
|
||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-'
|
||||
toggleCardState(modifierCard, true)
|
||||
}
|
||||
|
||||
refreshTagsList()
|
||||
@ -125,6 +121,10 @@ function createModifierGroup(modifierGroup, initiallyExpanded) {
|
||||
return e
|
||||
}
|
||||
|
||||
function trimModifiers(tag) {
|
||||
return tag.replace(/^\(+|\)+$/g, '').replace(/^\[+|\]+$/g, '')
|
||||
}
|
||||
|
||||
async function loadModifiers() {
|
||||
try {
|
||||
let res = await fetch('/get/modifiers')
|
||||
@ -222,8 +222,7 @@ function refreshTagsList() {
|
||||
let idx = activeTags.indexOf(tag)
|
||||
|
||||
if (idx !== -1 && activeTags[idx].originElement !== undefined) {
|
||||
activeTags[idx].originElement.classList.remove(activeCardClass)
|
||||
activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+'
|
||||
toggleCardState(activeTags[idx].originElement, false)
|
||||
|
||||
activeTags.splice(idx, 1)
|
||||
refreshTagsList()
|
||||
@ -236,6 +235,16 @@ function refreshTagsList() {
|
||||
editorModifierTagsList.appendChild(brk)
|
||||
}
|
||||
|
||||
function toggleCardState(card, makeActive) {
|
||||
if (makeActive) {
|
||||
card.classList.add(activeCardClass)
|
||||
card.querySelector('.modifier-card-image-overlay').innerText = '-'
|
||||
} else {
|
||||
card.classList.remove(activeCardClass)
|
||||
card.querySelector('.modifier-card-image-overlay').innerText = '+'
|
||||
}
|
||||
}
|
||||
|
||||
function changePreviewImages(val) {
|
||||
const previewImages = document.querySelectorAll('.modifier-card-image-container img')
|
||||
|
||||
|
10
ui/media/js/jquery-confirm.min.js
vendored
Normal file
10
ui/media/js/jquery-confirm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -138,6 +138,35 @@ function isServerAvailable() {
|
||||
}
|
||||
}
|
||||
|
||||
// shiftOrConfirm(e, prompt, fn)
|
||||
// e : MouseEvent
|
||||
// prompt : Text to be shown as prompt. Should be a question to which "yes" is a good answer.
|
||||
// fn : function to be called if the user confirms the dialog or has the shift key pressed
|
||||
//
|
||||
// If the user had the shift key pressed while clicking, the function fn will be executed.
|
||||
// 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)
|
||||
} else {
|
||||
$.confirm({
|
||||
theme: 'modern',
|
||||
title: prompt,
|
||||
useBootstrap: false,
|
||||
animateFromElement: false,
|
||||
content: '<small>Tip: To skip this dialog, use shift-click or disable the "Confirm dangerous actions" setting in the Settings tab.</small>',
|
||||
buttons: {
|
||||
yes: () => { fn(e) },
|
||||
cancel: () => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function logMsg(msg, level, outputMsg) {
|
||||
if (outputMsg.hasChildNodes()) {
|
||||
outputMsg.appendChild(document.createElement('br'))
|
||||
@ -169,34 +198,6 @@ function playSound() {
|
||||
})
|
||||
}
|
||||
}
|
||||
function setSystemInfo(devices) {
|
||||
let cpu = devices.all.cpu.name
|
||||
let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu')
|
||||
let activeGPUs = Object.keys(devices.active)
|
||||
|
||||
function ID_TO_TEXT(d) {
|
||||
let info = devices.all[d]
|
||||
if ("mem_free" in info && "mem_total" in info) {
|
||||
return `${info.name} <small>(${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(1)} Gb total)</small>`
|
||||
} else {
|
||||
return `${info.name} <small>(${d}) (no memory info)</small>`
|
||||
}
|
||||
}
|
||||
|
||||
allGPUs = allGPUs.map(ID_TO_TEXT)
|
||||
activeGPUs = activeGPUs.map(ID_TO_TEXT)
|
||||
|
||||
let systemInfo = `
|
||||
<table>
|
||||
<tr><td><label>Processor:</label></td><td class="value">${cpu}</td></tr>
|
||||
<tr><td><label>Compatible Graphics Cards (all):</label></td><td class="value">${allGPUs.join('</br>')}</td></tr>
|
||||
<tr><td></td><td> </td></tr>
|
||||
<tr><td><label>Used for rendering 🔥:</label></td><td class="value">${activeGPUs.join('</br>')}</td></tr>
|
||||
</table>`
|
||||
|
||||
let systemInfoEl = document.querySelector('#system-info')
|
||||
systemInfoEl.innerHTML = systemInfo
|
||||
}
|
||||
|
||||
async function healthCheck() {
|
||||
try {
|
||||
@ -231,7 +232,7 @@ async function healthCheck() {
|
||||
break
|
||||
}
|
||||
if (serverState.devices) {
|
||||
setSystemInfo(serverState.devices)
|
||||
setDeviceInfo(serverState.devices)
|
||||
}
|
||||
serverState.time = Date.now()
|
||||
} catch (e) {
|
||||
@ -887,24 +888,26 @@ function createTask(task) {
|
||||
task['progressBar'] = taskEntry.querySelector('.progress-bar')
|
||||
task['stopTask'] = taskEntry.querySelector('.stopTask')
|
||||
|
||||
task['stopTask'].addEventListener('click', async function(e) {
|
||||
e.stopPropagation()
|
||||
if (task['isProcessing']) {
|
||||
task.isProcessing = false
|
||||
task.progressBar.classList.remove("active")
|
||||
try {
|
||||
let res = await fetch('/image/stop?session_id=' + sessionId)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
} else {
|
||||
let idx = taskQueue.indexOf(task)
|
||||
if (idx >= 0) {
|
||||
taskQueue.splice(idx, 1)
|
||||
}
|
||||
task['stopTask'].addEventListener('click', (e) => {
|
||||
let question = (task['isProcessing'] ? "Stop this task?" : "Remove this task?")
|
||||
shiftOrConfirm(e, question, async function(e) {
|
||||
if (task['isProcessing']) {
|
||||
task.isProcessing = false
|
||||
task.progressBar.classList.remove("active")
|
||||
try {
|
||||
let res = await fetch('/image/stop?session_id=' + sessionId)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
} else {
|
||||
let idx = taskQueue.indexOf(task)
|
||||
if (idx >= 0) {
|
||||
taskQueue.splice(idx, 1)
|
||||
}
|
||||
|
||||
taskEntry.remove()
|
||||
}
|
||||
removeTask(taskEntry)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
task['useSettings'] = taskEntry.querySelector('.useSettings')
|
||||
@ -1047,21 +1050,25 @@ async function stopAllTasks() {
|
||||
}
|
||||
}
|
||||
|
||||
clearAllPreviewsBtn.addEventListener('click', async function() {
|
||||
function removeTask(taskToRemove) {
|
||||
taskToRemove.remove()
|
||||
|
||||
if (document.querySelector('.imageTaskContainer') === null) {
|
||||
previewTools.style.display = 'none'
|
||||
initialText.style.display = 'block'
|
||||
}
|
||||
}
|
||||
|
||||
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(task => {
|
||||
task.remove()
|
||||
})
|
||||
taskEntries.forEach(removeTask)
|
||||
})})
|
||||
|
||||
previewTools.style.display = 'none'
|
||||
initialText.style.display = 'block'
|
||||
})
|
||||
|
||||
stopImageBtn.addEventListener('click', async function() {
|
||||
stopImageBtn.addEventListener('click', (e) => { shiftOrConfirm(e, "Stop all the tasks?", async function(e) {
|
||||
await stopAllTasks()
|
||||
})
|
||||
})})
|
||||
|
||||
widthField.addEventListener('change', onDimensionChange)
|
||||
heightField.addEventListener('change', onDimensionChange)
|
||||
|
@ -114,6 +114,14 @@ var PARAMETERS = [
|
||||
icon: "fa-gear",
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
id: "confirm_dangerous_actions",
|
||||
type: ParameterType.checkbox,
|
||||
label: "Confirm dangerous actions",
|
||||
note: "Actions that might lead to data loss must either be clicked with the shift key pressed, or confirmed in an 'Are you sure?' dialog",
|
||||
icon: "fa-check-double",
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
id: "listen_to_network",
|
||||
type: ParameterType.checkbox,
|
||||
@ -207,9 +215,11 @@ let listenPortField = document.querySelector("#listen_port")
|
||||
let testSD2Field = document.querySelector("#test_sd2")
|
||||
let useBetaChannelField = document.querySelector("#use_beta_channel")
|
||||
let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_start")
|
||||
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
|
||||
|
||||
let saveSettingsBtn = document.querySelector('#save-system-settings-btn')
|
||||
|
||||
|
||||
async function changeAppConfig(configDelta) {
|
||||
try {
|
||||
let res = await fetch('/app_config', {
|
||||
@ -327,14 +337,45 @@ async function getDiskPath() {
|
||||
}
|
||||
}
|
||||
|
||||
async function getDevices() {
|
||||
function setDeviceInfo(devices) {
|
||||
let cpu = devices.all.cpu.name
|
||||
let allGPUs = Object.keys(devices.all).filter(d => d != 'cpu')
|
||||
let activeGPUs = Object.keys(devices.active)
|
||||
|
||||
function ID_TO_TEXT(d) {
|
||||
let info = devices.all[d]
|
||||
if ("mem_free" in info && "mem_total" in info) {
|
||||
return `${info.name} <small>(${d}) (${info.mem_free.toFixed(1)}Gb free / ${info.mem_total.toFixed(1)} Gb total)</small>`
|
||||
} else {
|
||||
return `${info.name} <small>(${d}) (no memory info)</small>`
|
||||
}
|
||||
}
|
||||
|
||||
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('</br>')
|
||||
systemInfoEl.querySelector('#system-info-rendering-devices').innerHTML = activeGPUs.join('</br>')
|
||||
}
|
||||
|
||||
function setHostInfo(hosts) {
|
||||
let port = listenPortField.value
|
||||
hosts = hosts.map(addr => `http://${addr}:${port}/`).map(url => `<div><a href="${url}">${url}</a></div>`)
|
||||
document.querySelector('#system-info-server-hosts').innerHTML = hosts.join('')
|
||||
}
|
||||
|
||||
async function getSystemInfo() {
|
||||
try {
|
||||
let res = await fetch('/get/devices')
|
||||
let res = await fetch('/get/system_info')
|
||||
if (res.status === 200) {
|
||||
res = await res.json()
|
||||
let devices = res['devices']
|
||||
let hosts = res['hosts']
|
||||
|
||||
let allDeviceIds = Object.keys(res['all']).filter(d => d !== 'cpu')
|
||||
let activeDeviceIds = Object.keys(res['active']).filter(d => d !== 'cpu')
|
||||
let allDeviceIds = Object.keys(devices['all']).filter(d => d !== 'cpu')
|
||||
let activeDeviceIds = Object.keys(devices['active']).filter(d => d !== 'cpu')
|
||||
|
||||
if (activeDeviceIds.length === 0) {
|
||||
useCPUField.checked = true
|
||||
@ -352,11 +393,11 @@ async function getDevices() {
|
||||
useCPUField.disabled = true // no compatible GPUs, so make the CPU mandatory
|
||||
}
|
||||
|
||||
autoPickGPUsField.checked = (res['config'] === 'auto')
|
||||
autoPickGPUsField.checked = (devices['config'] === 'auto')
|
||||
|
||||
useGPUsField.innerHTML = ''
|
||||
allDeviceIds.forEach(device => {
|
||||
let deviceName = res['all'][device]['name']
|
||||
let deviceName = devices['all'][device]['name']
|
||||
let deviceOption = `<option value="${device}">${deviceName} (${device})</option>`
|
||||
useGPUsField.insertAdjacentHTML('beforeend', deviceOption)
|
||||
})
|
||||
@ -367,6 +408,9 @@ async function getDevices() {
|
||||
} else {
|
||||
$('#use_gpus').val(activeDeviceIds)
|
||||
}
|
||||
|
||||
setDeviceInfo(devices)
|
||||
setHostInfo(hosts)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error fetching devices', e)
|
||||
|
@ -60,6 +60,7 @@ function themeFieldChanged() {
|
||||
|
||||
body.style = "";
|
||||
var theme = THEMES.find(t => t.key == theme_key);
|
||||
let borderColor = undefined
|
||||
if (theme) {
|
||||
// refresh variables incase they are back referencing
|
||||
Array.from(DEFAULT_THEME.rule.style)
|
||||
@ -67,7 +68,14 @@ function themeFieldChanged() {
|
||||
.forEach(cssVariable => {
|
||||
body.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable));
|
||||
});
|
||||
borderColor = theme.rule.style.getPropertyValue('--input-border-color').trim()
|
||||
if (!borderColor.startsWith('#')) {
|
||||
borderColor = theme.rule.style.getPropertyValue('--theme-color-fallback')
|
||||
}
|
||||
} else {
|
||||
borderColor = DEFAULT_THEME.rule.style.getPropertyValue('--theme-color-fallback')
|
||||
}
|
||||
document.querySelector('meta[name="theme-color"]').setAttribute("content", borderColor)
|
||||
}
|
||||
|
||||
themeField.addEventListener('change', themeFieldChanged);
|
||||
|
8
ui/media/manifest.webmanifest
Normal file
8
ui/media/manifest.webmanifest
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Stable Diffusion UI",
|
||||
"display": "standalone",
|
||||
"display_override": [
|
||||
"window-controls-overlay"
|
||||
],
|
||||
"theme_color": "#000000"
|
||||
}
|
42
ui/plugins/ui/Autoscroll.plugin.js
Normal file
42
ui/plugins/ui/Autoscroll.plugin.js
Normal file
@ -0,0 +1,42 @@
|
||||
(function () {
|
||||
"use strict"
|
||||
|
||||
var styleSheet = document.createElement("style");
|
||||
styleSheet.textContent = `
|
||||
.auto-scroll {
|
||||
float: right;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styleSheet);
|
||||
|
||||
const autoScrollControl = document.createElement('div');
|
||||
autoScrollControl.innerHTML = `<input id="auto_scroll" name="auto_scroll" type="checkbox">
|
||||
<label for="auto_scroll">Scroll to generated image</label>`
|
||||
autoScrollControl.className = "auto-scroll"
|
||||
clearAllPreviewsBtn.parentNode.insertBefore(autoScrollControl, clearAllPreviewsBtn.nextSibling)
|
||||
prettifyInputs(document);
|
||||
let autoScroll = document.querySelector("#auto_scroll")
|
||||
|
||||
SETTINGS_IDS_LIST.push("auto_scroll")
|
||||
initSettings()
|
||||
|
||||
// observe for changes in the preview pane
|
||||
var observer = new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (mutation) {
|
||||
if (mutation.target.className == 'img-batch') {
|
||||
Autoscroll(mutation.target)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
observer.observe(document.getElementById('preview'), {
|
||||
childList: true,
|
||||
subtree: true
|
||||
})
|
||||
|
||||
function Autoscroll(target) {
|
||||
if (autoScroll.checked && target !== null) {
|
||||
target.parentElement.parentElement.parentElement.scrollIntoView();
|
||||
}
|
||||
}
|
||||
})()
|
@ -18,40 +18,42 @@
|
||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
||||
overlays.forEach (i => {
|
||||
i.onwheel = (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
const delta = Math.sign(event.deltaY)
|
||||
let s = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText
|
||||
if (delta < 0) {
|
||||
// wheel scrolling up
|
||||
if (s.substring(0, 1) == '[' && s.substring(s.length-1) == ']') {
|
||||
s = s.substring(1, s.length - 1)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s.substring(0, 10) !== '('.repeat(10) && s.substring(s.length-10) !== ')'.repeat(10)) {
|
||||
s = '(' + s + ')'
|
||||
if (e.ctrlKey == true) {
|
||||
e.preventDefault()
|
||||
|
||||
const delta = Math.sign(event.deltaY)
|
||||
let s = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText
|
||||
if (delta < 0) {
|
||||
// wheel scrolling up
|
||||
if (s.substring(0, 1) == '[' && s.substring(s.length-1) == ']') {
|
||||
s = s.substring(1, s.length - 1)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s.substring(0, 10) !== '('.repeat(10) && s.substring(s.length-10) !== ')'.repeat(10)) {
|
||||
s = '(' + s + ')'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
// wheel scrolling down
|
||||
if (s.substring(0, 1) == '(' && s.substring(s.length-1) == ')') {
|
||||
s = s.substring(1, s.length - 1)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s.substring(0, 10) !== '['.repeat(10) && s.substring(s.length-10) !== ']'.repeat(10)) {
|
||||
s = '[' + s + ']'
|
||||
else{
|
||||
// wheel scrolling down
|
||||
if (s.substring(0, 1) == '(' && s.substring(s.length-1) == ')') {
|
||||
s = s.substring(1, s.length - 1)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s.substring(0, 10) !== '['.repeat(10) && s.substring(s.length-10) !== ']'.repeat(10)) {
|
||||
s = '[' + s + ']'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText = s
|
||||
// update activeTags
|
||||
for (let it = 0; it < overlays.length; it++) {
|
||||
if (i == overlays[it]) {
|
||||
activeTags[it].name = s
|
||||
break
|
||||
i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText = s
|
||||
// update activeTags
|
||||
for (let it = 0; it < overlays.length; it++) {
|
||||
if (i == overlays[it]) {
|
||||
activeTags[it].name = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ def device_init(thread_data, device):
|
||||
|
||||
# Force full precision on 1660 and 1650 NVIDIA cards to avoid creating green images
|
||||
device_name = thread_data.device_name.lower()
|
||||
thread_data.force_full_precision = ('nvidia' in device_name or 'geforce' in device_name) and (' 1660' in device_name or ' 1650' in device_name)
|
||||
thread_data.force_full_precision = (('nvidia' in device_name or 'geforce' in device_name) and (' 1660' in device_name or ' 1650' in device_name)) or ('Quadro T2000' in device_name)
|
||||
if thread_data.force_full_precision:
|
||||
print('forcing full precision on NVIDIA 16xx cards, to avoid green images. GPU detected: ', thread_data.device_name)
|
||||
# Apply force_full_precision now before models are loaded.
|
||||
|
@ -28,6 +28,8 @@ from gfpgan import GFPGANer
|
||||
from basicsr.archs.rrdbnet_arch import RRDBNet
|
||||
from realesrgan import RealESRGANer
|
||||
|
||||
from threading import Lock
|
||||
|
||||
import uuid
|
||||
|
||||
logging.set_verbosity_error()
|
||||
@ -35,7 +37,7 @@ logging.set_verbosity_error()
|
||||
# consts
|
||||
config_yaml = "optimizedSD/v1-inference.yaml"
|
||||
filename_regex = re.compile('[^a-zA-Z0-9]')
|
||||
force_gfpgan_to_cuda0 = True # workaround: gfpgan currently works only on cuda:0
|
||||
gfpgan_temp_device_lock = Lock() # workaround: gfpgan currently can only start on one device at a time.
|
||||
|
||||
# api stuff
|
||||
from sd_internal import device_manager
|
||||
@ -309,12 +311,6 @@ def move_to_cpu(model):
|
||||
|
||||
def load_model_gfpgan():
|
||||
if thread_data.gfpgan_file is None: raise ValueError(f'Thread gfpgan_file is undefined.')
|
||||
|
||||
# hack for a bug in facexlib: https://github.com/xinntao/facexlib/pull/19/files
|
||||
from facexlib.detection import retinaface
|
||||
retinaface.device = torch.device(thread_data.device)
|
||||
print('forced retinaface.device to', thread_data.device)
|
||||
|
||||
model_path = thread_data.gfpgan_file + ".pth"
|
||||
thread_data.model_gfpgan = GFPGANer(device=torch.device(thread_data.device), model_path=model_path, upscale=1, arch='clean', channel_multiplier=2, bg_upsampler=None)
|
||||
print('loaded', thread_data.gfpgan_file, 'to', thread_data.model_gfpgan.device, 'precision', thread_data.precision)
|
||||
@ -370,15 +366,23 @@ def apply_filters(filter_name, image_data, model_path=None):
|
||||
image_data.to(thread_data.device)
|
||||
|
||||
if filter_name == 'gfpgan':
|
||||
if model_path is not None and model_path != thread_data.gfpgan_file:
|
||||
thread_data.gfpgan_file = model_path
|
||||
load_model_gfpgan()
|
||||
elif not thread_data.model_gfpgan:
|
||||
load_model_gfpgan()
|
||||
if thread_data.model_gfpgan is None: raise Exception('Model "gfpgan" not loaded.')
|
||||
print('enhance with', thread_data.gfpgan_file, 'on', thread_data.model_gfpgan.device, 'precision', thread_data.precision)
|
||||
_, _, output = thread_data.model_gfpgan.enhance(image_data[:,:,::-1], has_aligned=False, only_center_face=False, paste_back=True)
|
||||
image_data = output[:,:,::-1]
|
||||
# This lock is only ever used here. No need to use timeout for the request. Should never deadlock.
|
||||
with gfpgan_temp_device_lock: # Wait for any other devices to complete before starting.
|
||||
# hack for a bug in facexlib: https://github.com/xinntao/facexlib/pull/19/files
|
||||
from facexlib.detection import retinaface
|
||||
retinaface.device = torch.device(thread_data.device)
|
||||
print('forced retinaface.device to', thread_data.device)
|
||||
|
||||
if model_path is not None and model_path != thread_data.gfpgan_file:
|
||||
thread_data.gfpgan_file = model_path
|
||||
load_model_gfpgan()
|
||||
elif not thread_data.model_gfpgan:
|
||||
load_model_gfpgan()
|
||||
if thread_data.model_gfpgan is None: raise Exception('Model "gfpgan" not loaded.')
|
||||
|
||||
print('enhance with', thread_data.gfpgan_file, 'on', thread_data.model_gfpgan.device, 'precision', thread_data.precision)
|
||||
_, _, output = thread_data.model_gfpgan.enhance(image_data[:,:,::-1], has_aligned=False, only_center_face=False, paste_back=True)
|
||||
image_data = output[:,:,::-1]
|
||||
|
||||
if filter_name == 'real_esrgan':
|
||||
if model_path is not None and model_path != thread_data.real_esrgan_file:
|
||||
|
33
ui/server.py
33
ui/server.py
@ -7,6 +7,7 @@ import traceback
|
||||
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import picklescan.scanner
|
||||
import rich
|
||||
|
||||
@ -244,9 +245,9 @@ def is_malicious_model(file_path):
|
||||
return False
|
||||
except Exception as e:
|
||||
print('error while scanning', file_path, 'error:', e)
|
||||
|
||||
return False
|
||||
|
||||
known_models = {}
|
||||
def getModels():
|
||||
models = {
|
||||
'active': {
|
||||
@ -269,9 +270,14 @@ def getModels():
|
||||
if not file.endswith(model_extension):
|
||||
continue
|
||||
|
||||
if is_malicious_model(os.path.join(models_dir, file)):
|
||||
models['scan-error'] = file
|
||||
return
|
||||
model_path = os.path.join(models_dir, file)
|
||||
mtime = os.path.getmtime(model_path)
|
||||
mod_time = known_models[model_path] if model_path in known_models else -1
|
||||
if mod_time != mtime:
|
||||
if is_malicious_model(model_path):
|
||||
models['scan-error'] = file
|
||||
return
|
||||
known_models[model_path] = mtime
|
||||
|
||||
model_name = file[:-len(model_extension)]
|
||||
models['options'][model_type].append(model_name)
|
||||
@ -300,6 +306,11 @@ def getUIPlugins():
|
||||
|
||||
return plugins
|
||||
|
||||
def getIPConfig():
|
||||
ips = socket.gethostbyname_ex(socket.gethostname())
|
||||
ips[2].append(ips[0])
|
||||
return ips[2]
|
||||
|
||||
@app.get('/get/{key:path}')
|
||||
def read_web_data(key:str=None):
|
||||
if not key: # /get without parameters, stable-diffusion easter egg.
|
||||
@ -309,11 +320,14 @@ def read_web_data(key:str=None):
|
||||
if config is None:
|
||||
config = APP_CONFIG_DEFAULTS
|
||||
return JSONResponse(config, headers=NOCACHE_HEADERS)
|
||||
elif key == 'devices':
|
||||
elif key == 'system_info':
|
||||
config = getConfig()
|
||||
devices = task_manager.get_devices()
|
||||
devices['config'] = config.get('render_devices', "auto")
|
||||
return JSONResponse(devices, headers=NOCACHE_HEADERS)
|
||||
system_info = {
|
||||
'devices': task_manager.get_devices(),
|
||||
'hosts': getIPConfig(),
|
||||
}
|
||||
system_info['devices']['config'] = config.get('render_devices', "auto")
|
||||
return JSONResponse(system_info, headers=NOCACHE_HEADERS)
|
||||
elif key == 'models':
|
||||
return JSONResponse(getModels(), headers=NOCACHE_HEADERS)
|
||||
elif key == 'modifiers': return FileResponse(os.path.join(SD_UI_DIR, 'modifiers.json'), headers=NOCACHE_HEADERS)
|
||||
@ -449,6 +463,9 @@ class LogSuppressFilter(logging.Filter):
|
||||
return True
|
||||
logging.getLogger('uvicorn.access').addFilter(LogSuppressFilter())
|
||||
|
||||
# Check models and prepare cache for UI open
|
||||
getModels()
|
||||
|
||||
# Start the task_manager
|
||||
task_manager.default_model_to_load = resolve_ckpt_to_use()
|
||||
task_manager.default_vae_to_load = resolve_vae_to_use()
|
||||
|
Loading…
Reference in New Issue
Block a user