forked from extern/easydiffusion
Merge pull request #366 from cmdr2/beta
Specify multiple word options in a prompt; UI Plugins system
This commit is contained in:
commit
f94e9449d5
@ -2,21 +2,23 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/png" href="/media/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="/media/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="stylesheet" href="/media/main.css?v=25">
|
||||
<link rel="stylesheet" href="/media/modifier-thumbnails.css?v=1">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="/media/drawingboard.min.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||||
<script src="/media/jquery-3.6.1.min.js"></script>
|
||||
<script src="/media/drawingboard.min.js"></script>
|
||||
<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?v=1">
|
||||
<link rel="stylesheet" href="/media/css/themes.css?v=1">
|
||||
<link rel="stylesheet" href="/media/css/auto-save.css?v=1">
|
||||
<link rel="stylesheet" href="/media/css/main.css?v=1">
|
||||
<link rel="stylesheet" href="/media/css/modifier-thumbnails.css?v=1">
|
||||
<link rel="stylesheet" href="/media/css/fontawesome-all.min.css?v=1">
|
||||
<link rel="stylesheet" href="/media/css/drawingboard.min.css">
|
||||
<script src="/media/js/jquery-3.6.1.min.js"></script>
|
||||
<script src="/media/js/drawingboard.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="top-nav">
|
||||
<div id="logo">
|
||||
<h1>Stable Diffusion UI <small>v2.28 <span id="updateBranchLabel"></span></small></h1>
|
||||
<h1>Stable Diffusion UI <small>v2.3.2 <span id="updateBranchLabel"></span></small></h1>
|
||||
</div>
|
||||
<ul id="top-nav-items">
|
||||
<li class="dropdown">
|
||||
@ -239,7 +241,7 @@
|
||||
<div class="line-separator"> </div>
|
||||
|
||||
<div id="footer" class="panel-box">
|
||||
<p>If you found this project useful and want to help keep it alive, please <a href="https://ko-fi.com/cmdr2_stablediffusion_ui" target="_blank"><img src="media/kofi.png" id="coffeeButton"></a> to help cover the cost of development and maintenance! Thank you for your support!</p>
|
||||
<p>If you found this project useful and want to help keep it alive, please <a href="https://ko-fi.com/cmdr2_stablediffusion_ui" target="_blank"><img src="/media/images/kofi.png" id="coffeeButton"></a> to help cover the cost of development and maintenance! Thank you for your support!</p>
|
||||
<p>Please feel free to join the <a href="https://discord.com/invite/u9yhsFmEkB" target="_blank">discord community</a> or <a href="https://github.com/cmdr2/stable-diffusion-ui/issues" target="_blank">file an issue</a> if you have any problems or suggestions in using this interface.</p>
|
||||
<div id="footer-legal">
|
||||
<p><b>Disclaimer:</b> The authors of this project are not responsible for any content generated using this interface.</p>
|
||||
@ -250,8 +252,13 @@
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="media/auto-save.js?v=2"></script>
|
||||
<script src="media/main.js?v=37"></script>
|
||||
<script src="media/js/plugins.js?v=1"></script>
|
||||
<script src="media/js/utils.js?v=2"></script>
|
||||
<script src="media/js/inpainting-editor.js?v=1"></script>
|
||||
<script src="media/js/image-modifiers.js"></script>
|
||||
<script src="media/js/auto-save.js?v=1"></script>
|
||||
<script src="media/js/main.js?v=2"></script>
|
||||
<script src="media/js/themes.js?v=1"></script>
|
||||
<script>
|
||||
async function init() {
|
||||
await loadModifiers()
|
||||
@ -259,6 +266,7 @@ async function init() {
|
||||
await getAppConfig()
|
||||
await getModels()
|
||||
await initSettings()
|
||||
await loadUIPlugins()
|
||||
|
||||
setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000)
|
||||
healthCheck()
|
||||
|
49
ui/media/css/auto-save.css
Normal file
49
ui/media/css/auto-save.css
Normal file
@ -0,0 +1,49 @@
|
||||
/* Auto-Settings Styling */
|
||||
#auto_save_settings:not(:checked) ~ button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#save-settings-config {
|
||||
position: fixed;
|
||||
background: rgba(32, 33, 36, 50%);
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#save-settings-config > div {
|
||||
background: var(--background-color2);
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
margin-top: 100px;
|
||||
border-radius: 6px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#save-settings-config-table {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#save-settings-config-table td:first-child,
|
||||
#save-settings-config-table th:first-child {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#save-settings-config-table td:last-child,
|
||||
#save-settings-config-table th:last-child {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#save-settings-config-table td small {
|
||||
color: rgb(153, 153, 153);
|
||||
}
|
||||
|
||||
#save-settings-config-close-btn {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
transform: translate(50%, -50%) scaleX(130%);
|
||||
}
|
6
ui/media/css/fontawesome-all.min.css
vendored
Normal file
6
ui/media/css/fontawesome-all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
40
ui/media/css/fonts.css
Normal file
40
ui/media/css/fonts.css
Normal file
@ -0,0 +1,40 @@
|
||||
/* work-sans-regular - latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local(''),
|
||||
url('/media/fonts/work-sans-v18-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||
url('/media/fonts/work-sans-v18-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
}
|
||||
|
||||
/* work-sans-600 - latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: local(''),
|
||||
url('/media/fonts/work-sans-v18-latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||
url('/media/fonts/work-sans-v18-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
}
|
||||
|
||||
/* work-sans-700 - latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local(''),
|
||||
url('/media/fonts/work-sans-v18-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||
url('/media/fonts/work-sans-v18-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
}
|
||||
|
||||
/* work-sans-800 - latin */
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: local(''),
|
||||
url('/media/fonts/work-sans-v18-latin-800.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||
url('/media/fonts/work-sans-v18-latin-800.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
}
|
||||
|
@ -1,151 +1,3 @@
|
||||
:root {
|
||||
--background-color1: rgb(32, 33, 36); /* main parts of the page */
|
||||
--background-color2: rgb(44, 45, 48); /* main panels */
|
||||
--background-color3: rgb(47, 49, 53);
|
||||
--background-color4: rgb(18, 18, 19); /* settings dropdowns */
|
||||
|
||||
--accent-hue: 266;
|
||||
--accent-lightness: 36%;
|
||||
--accent-lightness-hover: 40%;
|
||||
|
||||
--text-color: #eee;
|
||||
|
||||
--input-text-color: black;
|
||||
--input-background-color: #e9e9ed;
|
||||
--input-border-color: #8f8f9d;
|
||||
|
||||
--button-text-color: var(--input-text-color);
|
||||
--button-color: #e9e9ed;
|
||||
--button-border: 1px solid #8f8f9d;
|
||||
|
||||
/* other */
|
||||
--input-border-radius: 4px;
|
||||
--input-border-size: 1px;
|
||||
--accent-color: hsl(var(--accent-hue), 100%, var(--accent-lightness));
|
||||
--accent-color-hover: hsl(var(--accent-hue), 100%, var(--accent-lightness-hover));
|
||||
--make-image-border: 2px solid hsl(var(--accent-hue), 100%, calc(var(--accent-lightness) - 21%));
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
--background-color1: white;
|
||||
--background-color2: #dddddd;
|
||||
--background-color3: #e7e9eb;
|
||||
--background-color4: #cccccc;
|
||||
|
||||
--text-color: black;
|
||||
|
||||
--input-text-color: black;
|
||||
--input-background-color: #f8f9fa;
|
||||
--input-border-color: grey;
|
||||
}
|
||||
|
||||
.theme-discord {
|
||||
--background-color1: #36393f;
|
||||
--background-color2: #2f3136;
|
||||
--background-color3: #292b2f;
|
||||
--background-color4: #202225;
|
||||
|
||||
--accent-hue: 235;
|
||||
--accent-lightness: 65%;
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--input-text-color: #ccc;
|
||||
--input-border-size: 2px;
|
||||
--input-background-color: #202225;
|
||||
--input-border-color: var(--input-background-color);
|
||||
}
|
||||
|
||||
.theme-cool-blue {
|
||||
--main-hue: 222;
|
||||
--main-saturation: 18%;
|
||||
--value-base: 19%;
|
||||
--value-step: 3%;
|
||||
--background-color1: hsl(var(--main-hue), var(--main-saturation), var(--value-base));
|
||||
--background-color2: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (1 * var(--value-step))));
|
||||
--background-color3: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (2 * var(--value-step))));
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step))));
|
||||
|
||||
--accent-hue: 212;
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--input-border-size: 1px;
|
||||
--input-background-color: var(--background-color3);
|
||||
--input-text-color: #ccc;
|
||||
--input-border-color: var(--background-color4);
|
||||
}
|
||||
|
||||
|
||||
.theme-blurple {
|
||||
--main-hue: 235;
|
||||
--main-saturation: 18%;
|
||||
--value-base: 16%;
|
||||
--value-step: 3%;
|
||||
--background-color1: hsl(var(--main-hue), var(--main-saturation), var(--value-base));
|
||||
--background-color2: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (1 * var(--value-step))));
|
||||
--background-color3: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (2 * var(--value-step))));
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step))));
|
||||
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--input-border-size: 1px;
|
||||
--input-background-color: var(--background-color3);
|
||||
--input-text-color: #ccc;
|
||||
--input-border-color: var(--background-color4);
|
||||
}
|
||||
|
||||
.theme-super-dark {
|
||||
--main-hue: 222;
|
||||
--main-saturation: 18%;
|
||||
--value-base: 5%;
|
||||
--value-step: 5%;
|
||||
--background-color1: hsl(var(--main-hue), var(--main-saturation), var(--value-base));
|
||||
--background-color2: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (1 * var(--value-step))));
|
||||
--background-color3: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (2 * var(--value-step))));
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (3 * var(--value-step))));
|
||||
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--input-border-size: 0px;
|
||||
--input-background-color: var(--background-color3);
|
||||
--input-text-color: #ccc;
|
||||
--input-border-color: var(--background-color4);
|
||||
}
|
||||
|
||||
.theme-wild {
|
||||
--main-hue: 128;
|
||||
--main-saturation: 18%;
|
||||
--value-base: 20%;
|
||||
--value-step: 5%;
|
||||
--background-color1: hsl(var(--main-hue), var(--main-saturation), var(--value-base));
|
||||
--background-color2: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (1 * var(--value-step))));
|
||||
--background-color3: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (2 * var(--value-step))));
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step))));
|
||||
|
||||
--accent-hue: 212;
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--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;
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
font-family: Work Sans, Verdana, Geneva, sans-serif;
|
||||
box-sizing: border-box;
|
||||
@ -681,24 +533,6 @@ input::file-selector-button {
|
||||
height: 19px;
|
||||
}
|
||||
|
||||
/* input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
cursor: pointer;
|
||||
line-height: 29px;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb,
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
} */
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
body {
|
||||
margin: 0px;
|
||||
@ -736,56 +570,6 @@ input[type="range"]::-moz-range-thumb {
|
||||
}
|
||||
}
|
||||
|
||||
/* Auto-Settings Styling */
|
||||
#auto_save_settings:not(:checked) ~ button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#save-settings-config {
|
||||
position: fixed;
|
||||
background: rgba(32, 33, 36, 50%);
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#save-settings-config > div {
|
||||
background: var(--background-color2);
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
margin-top: 100px;
|
||||
border-radius: 6px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#save-settings-config-table {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#save-settings-config-table td:first-child,
|
||||
#save-settings-config-table th:first-child {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#save-settings-config-table td:last-child,
|
||||
#save-settings-config-table th:last-child {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#save-settings-config-table td small {
|
||||
color: rgb(153, 153, 153);
|
||||
}
|
||||
|
||||
#save-settings-config-close-btn {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
transform: translate(50%, -50%) scaleX(130%);
|
||||
}
|
||||
|
||||
#promptsFromFileBtn {
|
||||
font-size: 9pt;
|
||||
}
|
146
ui/media/css/themes.css
Normal file
146
ui/media/css/themes.css
Normal file
@ -0,0 +1,146 @@
|
||||
:root {
|
||||
--background-color1: rgb(32, 33, 36); /* main parts of the page */
|
||||
--background-color2: rgb(44, 45, 48); /* main panels */
|
||||
--background-color3: rgb(47, 49, 53);
|
||||
--background-color4: rgb(18, 18, 19); /* settings dropdowns */
|
||||
|
||||
--accent-hue: 266;
|
||||
--accent-lightness: 36%;
|
||||
--accent-lightness-hover: 40%;
|
||||
|
||||
--text-color: #eee;
|
||||
|
||||
--input-text-color: black;
|
||||
--input-background-color: #e9e9ed;
|
||||
--input-border-color: #8f8f9d;
|
||||
|
||||
--button-text-color: var(--input-text-color);
|
||||
--button-color: #e9e9ed;
|
||||
--button-border: 1px solid #8f8f9d;
|
||||
|
||||
/* other */
|
||||
--input-border-radius: 4px;
|
||||
--input-border-size: 1px;
|
||||
--accent-color: hsl(var(--accent-hue), 100%, var(--accent-lightness));
|
||||
--accent-color-hover: hsl(var(--accent-hue), 100%, var(--accent-lightness-hover));
|
||||
--make-image-border: 2px solid hsl(var(--accent-hue), 100%, calc(var(--accent-lightness) - 21%));
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
--background-color1: white;
|
||||
--background-color2: #dddddd;
|
||||
--background-color3: #e7e9eb;
|
||||
--background-color4: #cccccc;
|
||||
|
||||
--text-color: black;
|
||||
|
||||
--input-text-color: black;
|
||||
--input-background-color: #f8f9fa;
|
||||
--input-border-color: grey;
|
||||
}
|
||||
|
||||
.theme-discord {
|
||||
--background-color1: #36393f;
|
||||
--background-color2: #2f3136;
|
||||
--background-color3: #292b2f;
|
||||
--background-color4: #202225;
|
||||
|
||||
--accent-hue: 235;
|
||||
--accent-lightness: 65%;
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--input-text-color: #ccc;
|
||||
--input-border-size: 2px;
|
||||
--input-background-color: #202225;
|
||||
--input-border-color: var(--input-background-color);
|
||||
}
|
||||
|
||||
.theme-cool-blue {
|
||||
--main-hue: 222;
|
||||
--main-saturation: 18%;
|
||||
--value-base: 19%;
|
||||
--value-step: 3%;
|
||||
--background-color1: hsl(var(--main-hue), var(--main-saturation), var(--value-base));
|
||||
--background-color2: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (1 * var(--value-step))));
|
||||
--background-color3: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (2 * var(--value-step))));
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step))));
|
||||
|
||||
--accent-hue: 212;
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--input-border-size: 1px;
|
||||
--input-background-color: var(--background-color3);
|
||||
--input-text-color: #ccc;
|
||||
--input-border-color: var(--background-color4);
|
||||
}
|
||||
|
||||
|
||||
.theme-blurple {
|
||||
--main-hue: 235;
|
||||
--main-saturation: 18%;
|
||||
--value-base: 16%;
|
||||
--value-step: 3%;
|
||||
--background-color1: hsl(var(--main-hue), var(--main-saturation), var(--value-base));
|
||||
--background-color2: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (1 * var(--value-step))));
|
||||
--background-color3: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (2 * var(--value-step))));
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step))));
|
||||
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--input-border-size: 1px;
|
||||
--input-background-color: var(--background-color3);
|
||||
--input-text-color: #ccc;
|
||||
--input-border-color: var(--background-color4);
|
||||
}
|
||||
|
||||
.theme-super-dark {
|
||||
--main-hue: 222;
|
||||
--main-saturation: 18%;
|
||||
--value-base: 5%;
|
||||
--value-step: 5%;
|
||||
--background-color1: hsl(var(--main-hue), var(--main-saturation), var(--value-base));
|
||||
--background-color2: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (1 * var(--value-step))));
|
||||
--background-color3: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (2 * var(--value-step))));
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) + (3 * var(--value-step))));
|
||||
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--input-border-size: 0px;
|
||||
--input-background-color: var(--background-color3);
|
||||
--input-text-color: #ccc;
|
||||
--input-border-color: var(--background-color4);
|
||||
}
|
||||
|
||||
.theme-wild {
|
||||
--main-hue: 128;
|
||||
--main-saturation: 18%;
|
||||
--value-base: 20%;
|
||||
--value-step: 5%;
|
||||
--background-color1: hsl(var(--main-hue), var(--main-saturation), var(--value-base));
|
||||
--background-color2: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (1 * var(--value-step))));
|
||||
--background-color3: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (2 * var(--value-step))));
|
||||
--background-color4: hsl(var(--main-hue), var(--main-saturation), calc(var(--value-base) - (3 * var(--value-step))));
|
||||
|
||||
--accent-hue: 212;
|
||||
--make-image-border: none;
|
||||
|
||||
--button-color: var(--accent-color);
|
||||
--button-border: none;
|
||||
|
||||
--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;
|
||||
}
|
BIN
ui/media/fonts/fa-brands-400.ttf
Normal file
BIN
ui/media/fonts/fa-brands-400.ttf
Normal file
Binary file not shown.
BIN
ui/media/fonts/fa-brands-400.woff2
Normal file
BIN
ui/media/fonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
ui/media/fonts/fa-regular-400.ttf
Normal file
BIN
ui/media/fonts/fa-regular-400.ttf
Normal file
Binary file not shown.
BIN
ui/media/fonts/fa-regular-400.woff2
Normal file
BIN
ui/media/fonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
ui/media/fonts/fa-solid-900.ttf
Normal file
BIN
ui/media/fonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
ui/media/fonts/fa-solid-900.woff2
Normal file
BIN
ui/media/fonts/fa-solid-900.woff2
Normal file
Binary file not shown.
BIN
ui/media/fonts/fa-v4compatibility.ttf
Normal file
BIN
ui/media/fonts/fa-v4compatibility.ttf
Normal file
Binary file not shown.
BIN
ui/media/fonts/fa-v4compatibility.woff2
Normal file
BIN
ui/media/fonts/fa-v4compatibility.woff2
Normal file
Binary file not shown.
BIN
ui/media/fonts/work-sans-v18-latin-600.woff
Normal file
BIN
ui/media/fonts/work-sans-v18-latin-600.woff
Normal file
Binary file not shown.
BIN
ui/media/fonts/work-sans-v18-latin-600.woff2
Normal file
BIN
ui/media/fonts/work-sans-v18-latin-600.woff2
Normal file
Binary file not shown.
BIN
ui/media/fonts/work-sans-v18-latin-700.woff
Normal file
BIN
ui/media/fonts/work-sans-v18-latin-700.woff
Normal file
Binary file not shown.
BIN
ui/media/fonts/work-sans-v18-latin-700.woff2
Normal file
BIN
ui/media/fonts/work-sans-v18-latin-700.woff2
Normal file
Binary file not shown.
BIN
ui/media/fonts/work-sans-v18-latin-800.woff
Normal file
BIN
ui/media/fonts/work-sans-v18-latin-800.woff
Normal file
Binary file not shown.
BIN
ui/media/fonts/work-sans-v18-latin-800.woff2
Normal file
BIN
ui/media/fonts/work-sans-v18-latin-800.woff2
Normal file
Binary file not shown.
BIN
ui/media/fonts/work-sans-v18-latin-regular.woff
Normal file
BIN
ui/media/fonts/work-sans-v18-latin-regular.woff
Normal file
Binary file not shown.
BIN
ui/media/fonts/work-sans-v18-latin-regular.woff2
Normal file
BIN
ui/media/fonts/work-sans-v18-latin-regular.woff2
Normal file
Binary file not shown.
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 466 B |
Before Width: | Height: | Size: 973 B After Width: | Height: | Size: 973 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
230
ui/media/js/image-modifiers.js
Normal file
230
ui/media/js/image-modifiers.js
Normal file
@ -0,0 +1,230 @@
|
||||
let activeTags = []
|
||||
let modifiers = []
|
||||
|
||||
let editorModifierEntries = document.querySelector('#editor-modifiers-entries')
|
||||
let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list')
|
||||
let editorTagsContainer = document.querySelector('#editor-inputs-tags-container')
|
||||
let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider')
|
||||
let previewImageField = document.querySelector('#preview-image')
|
||||
|
||||
const modifierThumbnailPath = 'media/modifier-thumbnails'
|
||||
const activeCardClass = 'modifier-card-active'
|
||||
|
||||
function createModifierCard(name, previews) {
|
||||
const modifierCard = document.createElement('div')
|
||||
modifierCard.className = 'modifier-card'
|
||||
modifierCard.innerHTML = `
|
||||
<div class="modifier-card-overlay"></div>
|
||||
<div class="modifier-card-image-container">
|
||||
<div class="modifier-card-image-overlay">+</div>
|
||||
<p class="modifier-card-error-label"></p>
|
||||
<img onerror="this.remove()" alt="Modifier Image" class="modifier-card-image">
|
||||
</div>
|
||||
<div class="modifier-card-container">
|
||||
<div class="modifier-card-label"><p></p></div>
|
||||
</div>`
|
||||
|
||||
const image = modifierCard.querySelector('.modifier-card-image')
|
||||
const errorText = modifierCard.querySelector('.modifier-card-error-label')
|
||||
const label = modifierCard.querySelector('.modifier-card-label')
|
||||
|
||||
errorText.innerText = 'No Image'
|
||||
|
||||
if (typeof previews == 'object') {
|
||||
image.src = previews[0]; // portrait
|
||||
image.setAttribute('preview-type', 'portrait')
|
||||
} else {
|
||||
image.remove()
|
||||
}
|
||||
|
||||
const maxLabelLength = 30
|
||||
const nameWithoutBy = name.replace('by ', '')
|
||||
|
||||
if(nameWithoutBy.length <= maxLabelLength) {
|
||||
label.querySelector('p').innerText = nameWithoutBy
|
||||
} else {
|
||||
const tooltipText = document.createElement('span')
|
||||
tooltipText.className = 'tooltip-text'
|
||||
tooltipText.innerText = name
|
||||
|
||||
label.classList.add('tooltip')
|
||||
label.appendChild(tooltipText)
|
||||
|
||||
label.querySelector('p').innerText = nameWithoutBy.substring(0, maxLabelLength) + '...'
|
||||
}
|
||||
|
||||
return modifierCard
|
||||
}
|
||||
|
||||
async function loadModifiers() {
|
||||
try {
|
||||
let res = await fetch('/get/modifiers')
|
||||
if (res.status === 200) {
|
||||
res = await res.json()
|
||||
|
||||
modifiers = res; // update global variable
|
||||
|
||||
res.forEach((modifierGroup, idx) => {
|
||||
const title = modifierGroup.category
|
||||
const modifiers = modifierGroup.modifiers
|
||||
|
||||
const titleEl = document.createElement('h5')
|
||||
titleEl.className = 'collapsible'
|
||||
titleEl.innerText = title
|
||||
|
||||
const modifiersEl = document.createElement('div')
|
||||
modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf')
|
||||
|
||||
if (idx == 0) {
|
||||
titleEl.className += ' active'
|
||||
modifiersEl.style.display = 'block'
|
||||
}
|
||||
|
||||
modifiers.forEach(modObj => {
|
||||
const modifierName = modObj.modifier
|
||||
const modifierPreviews = modObj?.previews?.map(preview => `${modifierThumbnailPath}/${preview.path}`)
|
||||
|
||||
const modifierCard = createModifierCard(modifierName, modifierPreviews)
|
||||
|
||||
if(typeof modifierCard == 'object') {
|
||||
modifiersEl.appendChild(modifierCard)
|
||||
|
||||
modifierCard.addEventListener('click', () => {
|
||||
if (activeTags.map(x => x.name).includes(modifierName)) {
|
||||
// remove modifier from active array
|
||||
activeTags = activeTags.filter(x => x.name != modifierName)
|
||||
modifierCard.classList.remove(activeCardClass)
|
||||
|
||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+'
|
||||
} else {
|
||||
// add modifier to active array
|
||||
activeTags.push({
|
||||
'name': modifierName,
|
||||
'element': modifierCard.cloneNode(true),
|
||||
'originElement': modifierCard,
|
||||
'previews': modifierPreviews
|
||||
})
|
||||
|
||||
modifierCard.classList.add(activeCardClass)
|
||||
|
||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-'
|
||||
}
|
||||
|
||||
refreshTagsList()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
let brk = document.createElement('br')
|
||||
brk.style.clear = 'both'
|
||||
modifiersEl.appendChild(brk)
|
||||
|
||||
let e = document.createElement('div')
|
||||
e.appendChild(titleEl)
|
||||
e.appendChild(modifiersEl)
|
||||
|
||||
editorModifierEntries.appendChild(e)
|
||||
})
|
||||
|
||||
createCollapsibles(editorModifierEntries)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error fetching modifiers', e)
|
||||
}
|
||||
}
|
||||
|
||||
function refreshTagsList() {
|
||||
editorModifierTagsList.innerHTML = ''
|
||||
|
||||
if (activeTags.length == 0) {
|
||||
editorTagsContainer.style.display = 'none'
|
||||
return
|
||||
} else {
|
||||
editorTagsContainer.style.display = 'block'
|
||||
}
|
||||
|
||||
activeTags.forEach((tag, index) => {
|
||||
tag.element.querySelector('.modifier-card-image-overlay').innerText = '-'
|
||||
tag.element.classList.add('modifier-card-tiny')
|
||||
|
||||
editorModifierTagsList.appendChild(tag.element)
|
||||
|
||||
tag.element.addEventListener('click', () => {
|
||||
let idx = activeTags.indexOf(tag)
|
||||
|
||||
if (idx !== -1) {
|
||||
activeTags[idx].originElement.classList.remove(activeCardClass)
|
||||
activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+'
|
||||
|
||||
activeTags.splice(idx, 1)
|
||||
refreshTagsList()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let brk = document.createElement('br')
|
||||
brk.style.clear = 'both'
|
||||
editorModifierTagsList.appendChild(brk)
|
||||
}
|
||||
|
||||
function changePreviewImages(val) {
|
||||
const previewImages = document.querySelectorAll('.modifier-card-image-container img')
|
||||
|
||||
let previewArr = []
|
||||
|
||||
modifiers.map(x => x.modifiers).forEach(x => previewArr.push(...x.map(m => m.previews)))
|
||||
|
||||
previewArr = previewArr.map(x => {
|
||||
let obj = {}
|
||||
|
||||
x.forEach(preview => {
|
||||
obj[preview.name] = preview.path
|
||||
})
|
||||
|
||||
return obj
|
||||
})
|
||||
|
||||
previewImages.forEach(previewImage => {
|
||||
const currentPreviewType = previewImage.getAttribute('preview-type')
|
||||
const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + '/').pop()
|
||||
|
||||
const previews = previewArr.find(preview => relativePreviewPath == preview[currentPreviewType])
|
||||
|
||||
if(typeof previews == 'object') {
|
||||
let preview = null
|
||||
|
||||
if (val == 'portrait') {
|
||||
preview = previews.portrait
|
||||
}
|
||||
else if (val == 'landscape') {
|
||||
preview = previews.landscape
|
||||
}
|
||||
|
||||
if(preview != null) {
|
||||
previewImage.src = `${modifierThumbnailPath}/${preview}`
|
||||
previewImage.setAttribute('preview-type', val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function resizeModifierCards(val) {
|
||||
const cardSizePrefix = 'modifier-card-size_'
|
||||
const modifierCardClass = 'modifier-card'
|
||||
|
||||
const modifierCards = document.querySelectorAll(`.${modifierCardClass}`)
|
||||
const cardSize = n => `${cardSizePrefix}${n}`
|
||||
|
||||
modifierCards.forEach(card => {
|
||||
// remove existing size classes
|
||||
const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix))
|
||||
card.className = classes.join(' ').trim()
|
||||
|
||||
if(val != 0) {
|
||||
card.classList.add(cardSize(val))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value)
|
||||
previewImageField.onchange = () => changePreviewImages(previewImageField.value)
|
41
ui/media/js/inpainting-editor.js
Normal file
41
ui/media/js/inpainting-editor.js
Normal file
@ -0,0 +1,41 @@
|
||||
const INPAINTING_EDITOR_SIZE = 450
|
||||
|
||||
let inpaintingEditorContainer = document.querySelector('#inpaintingEditor')
|
||||
let inpaintingEditor = new DrawingBoard.Board('inpaintingEditor', {
|
||||
color: "#ffffff",
|
||||
background: false,
|
||||
size: 30,
|
||||
webStorage: false,
|
||||
controls: [{'DrawingMode': {'filler': false}}, 'Size', 'Navigation']
|
||||
})
|
||||
let inpaintingEditorCanvasBackground = document.querySelector('.drawing-board-canvas-wrapper')
|
||||
|
||||
function resizeInpaintingEditor(widthValue, heightValue) {
|
||||
if (widthValue === heightValue) {
|
||||
widthValue = INPAINTING_EDITOR_SIZE
|
||||
heightValue = INPAINTING_EDITOR_SIZE
|
||||
} else if (widthValue > heightValue) {
|
||||
heightValue = (heightValue / widthValue) * INPAINTING_EDITOR_SIZE
|
||||
widthValue = INPAINTING_EDITOR_SIZE
|
||||
} else {
|
||||
widthValue = (widthValue / heightValue) * INPAINTING_EDITOR_SIZE
|
||||
heightValue = INPAINTING_EDITOR_SIZE
|
||||
}
|
||||
if (inpaintingEditor.opts.aspectRatio === (widthValue / heightValue).toFixed(3)) {
|
||||
// Same ratio, don't reset the canvas.
|
||||
return
|
||||
}
|
||||
inpaintingEditor.opts.aspectRatio = (widthValue / heightValue).toFixed(3)
|
||||
|
||||
inpaintingEditorContainer.style.width = widthValue + 'px'
|
||||
inpaintingEditorContainer.style.height = heightValue + 'px'
|
||||
inpaintingEditor.opts.enlargeYourContainer = true
|
||||
|
||||
inpaintingEditor.opts.size = inpaintingEditor.ctx.lineWidth
|
||||
inpaintingEditor.resize()
|
||||
|
||||
inpaintingEditor.ctx.lineCap = "round"
|
||||
inpaintingEditor.ctx.lineJoin = "round"
|
||||
inpaintingEditor.ctx.lineWidth = inpaintingEditor.opts.size
|
||||
inpaintingEditor.setColor(inpaintingEditor.opts.color)
|
||||
}
|
@ -16,7 +16,6 @@ const OUTPUT_FORMAT_KEY = "outputFormat"
|
||||
const AUTO_SAVE_SETTINGS_KEY = "autoSaveSettings"
|
||||
const HEALTH_PING_INTERVAL = 5 // seconds
|
||||
const MAX_INIT_IMAGE_DIMENSION = 768
|
||||
const INPAINTING_EDITOR_SIZE = 450
|
||||
|
||||
const IMAGE_REGEX = new RegExp('data:image/[A-Za-z]+;base64')
|
||||
|
||||
@ -80,16 +79,7 @@ let clearAllPreviewsBtn = document.querySelector("#clear-all-previews")
|
||||
let maskSetting = document.querySelector('#enable_mask')
|
||||
let negativePromptPanelHandle = document.querySelector('#negative_prompt_handle')
|
||||
|
||||
let editorModifierEntries = document.querySelector('#editor-modifiers-entries')
|
||||
let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list')
|
||||
let editorTagsContainer = document.querySelector('#editor-inputs-tags-container')
|
||||
|
||||
let imagePreview = document.querySelector("#preview")
|
||||
let previewImageField = document.querySelector('#preview-image')
|
||||
previewImageField.onchange = () => changePreviewImages(previewImageField.value)
|
||||
|
||||
let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider')
|
||||
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value)
|
||||
|
||||
// let previewPrompt = document.querySelector('#preview-prompt')
|
||||
|
||||
@ -105,15 +95,6 @@ let serverStatusMsg = document.querySelector('#server-status-msg')
|
||||
|
||||
let advancedPanelHandle = document.querySelector("#editor-settings .collapsible")
|
||||
let modifiersPanelHandle = document.querySelector("#editor-modifiers .collapsible")
|
||||
let inpaintingEditorContainer = document.querySelector('#inpaintingEditor')
|
||||
let inpaintingEditor = new DrawingBoard.Board('inpaintingEditor', {
|
||||
color: "#ffffff",
|
||||
background: false,
|
||||
size: 30,
|
||||
webStorage: false,
|
||||
controls: [{'DrawingMode': {'filler': false}}, 'Size', 'Navigation']
|
||||
})
|
||||
let inpaintingEditorCanvasBackground = document.querySelector('.drawing-board-canvas-wrapper')
|
||||
|
||||
document.querySelector('.drawing-board-control-navigation-back').innerHTML = '<i class="fa-solid fa-rotate-left"></i>'
|
||||
document.querySelector('.drawing-board-control-navigation-forward').innerHTML = '<i class="fa-solid fa-rotate-right"></i>'
|
||||
@ -124,17 +105,12 @@ maskResetButton.style.fontWeight = 'normal'
|
||||
maskResetButton.style.fontSize = '10pt'
|
||||
|
||||
let serverState = {'status': 'Offline', 'time': Date.now()}
|
||||
let activeTags = []
|
||||
let modifiers = []
|
||||
let lastPromptUsed = ''
|
||||
let bellPending = false
|
||||
|
||||
let taskQueue = []
|
||||
let currentTask = null
|
||||
|
||||
const modifierThumbnailPath = 'media/modifier-thumbnails'
|
||||
const activeCardClass = 'modifier-card-active'
|
||||
|
||||
function getLocalStorageItem(key, fallback) {
|
||||
let item = localStorage.getItem(key)
|
||||
if (item === null) {
|
||||
@ -336,40 +312,6 @@ async function healthCheck() {
|
||||
setServerStatus('error', 'offline')
|
||||
}
|
||||
}
|
||||
function resizeInpaintingEditor() {
|
||||
if (!maskSetting.checked) {
|
||||
return
|
||||
}
|
||||
let widthValue = parseInt(widthField.value)
|
||||
let heightValue = parseInt(heightField.value)
|
||||
if (widthValue === heightValue) {
|
||||
widthValue = INPAINTING_EDITOR_SIZE
|
||||
heightValue = INPAINTING_EDITOR_SIZE
|
||||
} else if (widthValue > heightValue) {
|
||||
heightValue = (heightValue / widthValue) * INPAINTING_EDITOR_SIZE
|
||||
widthValue = INPAINTING_EDITOR_SIZE
|
||||
} else {
|
||||
widthValue = (widthValue / heightValue) * INPAINTING_EDITOR_SIZE
|
||||
heightValue = INPAINTING_EDITOR_SIZE
|
||||
}
|
||||
if (inpaintingEditor.opts.aspectRatio === (widthValue / heightValue).toFixed(3)) {
|
||||
// Same ratio, don't reset the canvas.
|
||||
return
|
||||
}
|
||||
inpaintingEditor.opts.aspectRatio = (widthValue / heightValue).toFixed(3)
|
||||
|
||||
inpaintingEditorContainer.style.width = widthValue + 'px'
|
||||
inpaintingEditorContainer.style.height = heightValue + 'px'
|
||||
inpaintingEditor.opts.enlargeYourContainer = true
|
||||
|
||||
inpaintingEditor.opts.size = inpaintingEditor.ctx.lineWidth
|
||||
inpaintingEditor.resize()
|
||||
|
||||
inpaintingEditor.ctx.lineCap = "round"
|
||||
inpaintingEditor.ctx.lineJoin = "round"
|
||||
inpaintingEditor.ctx.lineWidth = inpaintingEditor.opts.size
|
||||
inpaintingEditor.setColor(inpaintingEditor.opts.color)
|
||||
}
|
||||
|
||||
function showImages(reqBody, res, outputContainer, livePreview) {
|
||||
let imageItemElements = outputContainer.querySelectorAll('.imgItem')
|
||||
@ -424,34 +366,42 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
||||
const imageSeedLabel = imageItemElem.querySelector('.imgSeedLabel')
|
||||
imageSeedLabel.innerText = 'Seed: ' + req.seed
|
||||
|
||||
const buttons = {
|
||||
'imgUseBtn': { html: 'Use as Input', click: getUseAsInputHandler(imageItemElem) },
|
||||
'imgSaveBtn': { html: 'Download', click: getSaveImageHandler(imageItemElem, req['output_format']) },
|
||||
// 'imgX2Btn': { html: 'Double Size', click: getStartNewTaskHandler(req, imageItemElem, 'img2img_X2') },
|
||||
// 'imgRedoBtn': { html: 'Redo', click: getStartNewTaskHandler(req, imageItemElem, 'img2img') },
|
||||
}
|
||||
// if (!req.use_upscale) {
|
||||
// buttons.upscaleBtn = { html: 'Upscale', click: getStartNewTaskHandler(req, imageItemElem, 'upscale') }
|
||||
// }
|
||||
let buttons = [
|
||||
{ text: 'Use as Input', on_click: onUseAsInputClick },
|
||||
{ text: 'Download', on_click: onDownloadImageClick },
|
||||
{ text: 'Make Similar Images', on_click: onMakeSimilarClick },
|
||||
{ text: 'Draw another 25 steps', on_click: onContinueDrawingClick },
|
||||
{ text: 'Upscale', on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale },
|
||||
{ text: 'Fix Faces', on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction }
|
||||
]
|
||||
|
||||
// include the plugins
|
||||
buttons = buttons.concat(PLUGINS['IMAGE_INFO_BUTTONS'])
|
||||
|
||||
const imgItemInfo = imageItemElem.querySelector('.imgItemInfo')
|
||||
const createButton = function(name, btnInfo) {
|
||||
const img = imageItemElem.querySelector('img')
|
||||
const createButton = function(btnInfo) {
|
||||
const newButton = document.createElement('button')
|
||||
newButton.classList.add(name)
|
||||
newButton.classList.add('tasksBtns')
|
||||
newButton.innerHTML = btnInfo.html
|
||||
newButton.addEventListener('click', btnInfo.click)
|
||||
newButton.innerText = btnInfo.text
|
||||
newButton.addEventListener('click', function() {
|
||||
btnInfo.on_click(req, img)
|
||||
})
|
||||
imgItemInfo.appendChild(newButton)
|
||||
}
|
||||
Object.keys(buttons).forEach((name) => createButton(name, buttons[name]))
|
||||
buttons.forEach(btn => {
|
||||
if (btn.filter && btn.filter(req, img) === false) {
|
||||
return
|
||||
}
|
||||
|
||||
createButton(btn)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getUseAsInputHandler(imageItemElem) {
|
||||
return function() {
|
||||
const imageElem = imageItemElem.querySelector('img')
|
||||
const imgData = imageElem.src
|
||||
const imageSeed = imageElem.getAttribute('data-seed')
|
||||
function onUseAsInputClick(req, img) {
|
||||
const imgData = img.src
|
||||
|
||||
initImageSelector.value = null
|
||||
initImagePreview.src = imgData
|
||||
@ -461,79 +411,80 @@ function getUseAsInputHandler(imageItemElem) {
|
||||
promptStrengthContainer.style.display = 'table-row'
|
||||
maskSetting.checked = false
|
||||
samplerSelectionContainer.style.display = 'none'
|
||||
|
||||
// maskSetting.style.display = 'block'
|
||||
|
||||
// randomSeedField.checked = false
|
||||
// seedField.value = imageSeed
|
||||
// seedField.disabled = false
|
||||
}
|
||||
}
|
||||
|
||||
function getSaveImageHandler(imageItemElem, outputFormat) {
|
||||
return function() {
|
||||
const imageElem = imageItemElem.querySelector('img')
|
||||
const imgData = imageElem.src
|
||||
const imageSeed = imageElem.getAttribute('data-seed')
|
||||
const imagePrompt = imageElem.getAttribute('data-prompt')
|
||||
const imageInferenceSteps = imageElem.getAttribute('data-steps')
|
||||
const imageGuidanceScale = imageElem.getAttribute('data-guidance')
|
||||
function onDownloadImageClick(req, img) {
|
||||
const imgData = img.src
|
||||
const imageSeed = img.getAttribute('data-seed')
|
||||
const imagePrompt = img.getAttribute('data-prompt')
|
||||
const imageInferenceSteps = img.getAttribute('data-steps')
|
||||
const imageGuidanceScale = img.getAttribute('data-guidance')
|
||||
|
||||
const imgDownload = document.createElement('a')
|
||||
imgDownload.download = createFileName(imagePrompt, imageSeed, imageInferenceSteps, imageGuidanceScale, outputFormat)
|
||||
imgDownload.download = createFileName(imagePrompt, imageSeed, imageInferenceSteps, imageGuidanceScale, req['output_format'])
|
||||
imgDownload.href = imgData
|
||||
imgDownload.click()
|
||||
}
|
||||
}
|
||||
function getStartNewTaskHandler(reqBody, imageItemElem, mode) {
|
||||
return function() {
|
||||
if (!isServerAvailable()) {
|
||||
alert('The server is not available.')
|
||||
return
|
||||
}
|
||||
const imageElem = imageItemElem.querySelector('img')
|
||||
|
||||
function modifyCurrentRequest(req, reqDiff) {
|
||||
const newTaskRequest = getCurrentUserRequest()
|
||||
switch (mode) {
|
||||
case 'img2img':
|
||||
case 'img2img_X2':
|
||||
newTaskRequest.reqBody = Object.assign({}, reqBody, {
|
||||
num_outputs: 1,
|
||||
use_cpu: useCPUField.checked,
|
||||
|
||||
newTaskRequest.reqBody = Object.assign({}, req, reqDiff, {
|
||||
use_cpu: useCPUField.checked
|
||||
})
|
||||
if (!newTaskRequest.reqBody.init_image || mode === 'img2img_X2') {
|
||||
newTaskRequest.reqBody.sampler = 'ddim'
|
||||
newTaskRequest.reqBody.prompt_strength = '0.5'
|
||||
newTaskRequest.reqBody.init_image = imageElem.src
|
||||
delete newTaskRequest.reqBody.mask
|
||||
} else {
|
||||
newTaskRequest.reqBody.seed = 1 + newTaskRequest.reqBody.seed
|
||||
}
|
||||
if (mode === 'img2img_X2') {
|
||||
newTaskRequest.reqBody.width = reqBody.width * 2
|
||||
newTaskRequest.reqBody.height = reqBody.height * 2
|
||||
newTaskRequest.reqBody.num_inference_steps = Math.min(100, reqBody.num_inference_steps * 2)
|
||||
if (useUpscalingField.checked) {
|
||||
newTaskRequest.reqBody.use_upscale = upscaleModelField.value
|
||||
} else {
|
||||
delete newTaskRequest.reqBody.use_upscale
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'upscale':
|
||||
newTaskRequest.reqBody = Object.assign({}, reqBody, {
|
||||
num_outputs: 1,
|
||||
//use_face_correction: 'GFPGANv1.3',
|
||||
use_upscale: upscaleModelField.value,
|
||||
})
|
||||
break
|
||||
default:
|
||||
throw new Error("Unknown upscale mode: " + mode)
|
||||
}
|
||||
newTaskRequest.seed = newTaskRequest.reqBody.seed
|
||||
newTaskRequest.numOutputsTotal = 1
|
||||
newTaskRequest.batchCount = 1
|
||||
|
||||
return newTaskRequest
|
||||
}
|
||||
|
||||
function onMakeSimilarClick(req, img) {
|
||||
const newTaskRequest = modifyCurrentRequest(req, {
|
||||
num_outputs: 1,
|
||||
num_inference_steps: 50,
|
||||
guidance_scale: 7.5,
|
||||
prompt_strength: 0.7,
|
||||
init_image: img.src,
|
||||
seed: Math.floor(Math.random() * 10000000)
|
||||
})
|
||||
|
||||
newTaskRequest.numOutputsTotal = 5
|
||||
newTaskRequest.batchCount = 5
|
||||
|
||||
delete newTaskRequest.reqBody.mask
|
||||
|
||||
createTask(newTaskRequest)
|
||||
}
|
||||
|
||||
function enqueueImageVariationTask(req, img, reqDiff) {
|
||||
const imageSeed = img.getAttribute('data-seed')
|
||||
|
||||
const newTaskRequest = modifyCurrentRequest(req, reqDiff, {
|
||||
num_outputs: 1, // this can be user-configurable in the future
|
||||
seed: imageSeed
|
||||
})
|
||||
|
||||
newTaskRequest.numOutputsTotal = 1 // this can be user-configurable in the future
|
||||
newTaskRequest.batchCount = 1
|
||||
|
||||
createTask(newTaskRequest)
|
||||
}
|
||||
|
||||
function onUpscaleClick(req, img) {
|
||||
enqueueImageVariationTask(req, img, {
|
||||
use_upscale: upscaleModelField.value
|
||||
})
|
||||
}
|
||||
|
||||
function onFixFacesClick(req, img) {
|
||||
enqueueImageVariationTask(req, img, {
|
||||
use_face_correction: 'GFPGANv1.3'
|
||||
})
|
||||
}
|
||||
|
||||
function onContinueDrawingClick(req, img) {
|
||||
enqueueImageVariationTask(req, img, {
|
||||
num_inference_steps: parseInt(req.num_inference_steps) + 25
|
||||
})
|
||||
}
|
||||
|
||||
// makes a single image. don't call this directly, use makeImage() instead
|
||||
@ -801,7 +752,7 @@ async function checkTasks() {
|
||||
})
|
||||
}
|
||||
if (genSeeds) {
|
||||
newTask.reqBody.seed = startSeed + (i * newTask.reqBody.num_outputs)
|
||||
newTask.reqBody.seed = parseInt(startSeed) + (i * newTask.reqBody.num_outputs)
|
||||
newTask.seed = newTask.reqBody.seed
|
||||
} else if (newTask.seed !== newTask.reqBody.seed) {
|
||||
newTask.seed = newTask.reqBody.seed
|
||||
@ -998,14 +949,34 @@ function getPrompts() {
|
||||
}
|
||||
|
||||
prompts = prompts.split('\n')
|
||||
prompts = prompts.map(prompt => prompt.trim())
|
||||
prompts = prompts.filter(prompt => prompt !== '')
|
||||
|
||||
let promptsToMake = []
|
||||
prompts.forEach(prompt => {
|
||||
prompt = prompt.trim()
|
||||
if (prompt === '') {
|
||||
return
|
||||
let promptsToMake = applySetOperator(prompts)
|
||||
promptsToMake = applyPermuteOperator(promptsToMake)
|
||||
|
||||
if (activeTags.length <= 0) {
|
||||
return promptsToMake
|
||||
}
|
||||
|
||||
const promptTags = activeTags.map(x => x.name).join(", ")
|
||||
return promptsToMake.map((prompt) => `${prompt}, ${promptTags}`)
|
||||
}
|
||||
|
||||
function applySetOperator(prompts) {
|
||||
let promptsToMake = []
|
||||
let braceExpander = new BraceExpander()
|
||||
prompts.forEach(prompt => {
|
||||
let expandedPrompts = braceExpander.expand(prompt)
|
||||
promptsToMake = promptsToMake.concat(expandedPrompts)
|
||||
})
|
||||
|
||||
return promptsToMake
|
||||
}
|
||||
|
||||
function applyPermuteOperator(prompts) {
|
||||
let promptsToMake = []
|
||||
prompts.forEach(prompt => {
|
||||
let promptMatrix = prompt.split('|')
|
||||
prompt = promptMatrix.shift().trim()
|
||||
promptsToMake.push(prompt)
|
||||
@ -1018,12 +989,9 @@ function getPrompts() {
|
||||
promptsToMake = promptsToMake.concat(promptPermutations)
|
||||
}
|
||||
})
|
||||
if (activeTags.length <= 0) {
|
||||
|
||||
return promptsToMake
|
||||
}
|
||||
const promptTags = activeTags.map(x => x.name).join(", ")
|
||||
return promptsToMake.map((prompt) => `${prompt}, ${promptTags}`)
|
||||
}
|
||||
|
||||
function permutePrompts(promptBase, promptMatrix) {
|
||||
let prompts = []
|
||||
@ -1046,28 +1014,6 @@ function permutePrompts(promptBase, promptMatrix) {
|
||||
return prompts
|
||||
}
|
||||
|
||||
function permute(arr) {
|
||||
let permutations = []
|
||||
let n = arr.length
|
||||
let n_permutations = Math.pow(2, n)
|
||||
for (let i = 0; i < n_permutations; i++) {
|
||||
let perm = []
|
||||
let mask = Number(i).toString(2).padStart(n, '0')
|
||||
|
||||
for (let idx = 0; idx < mask.length; idx++) {
|
||||
if (mask[idx] === '1' && arr[idx].trim() !== '') {
|
||||
perm.push(arr[idx])
|
||||
}
|
||||
}
|
||||
|
||||
if (perm.length > 0) {
|
||||
permutations.push(perm)
|
||||
}
|
||||
}
|
||||
|
||||
return permutations
|
||||
}
|
||||
|
||||
// create a file name with embedded prompt and metadata
|
||||
// for easier cateloging and comparison
|
||||
function createFileName(prompt, seed, steps, guidance, outputFormat) {
|
||||
@ -1170,8 +1116,18 @@ outputFormatField.addEventListener('change', handleStringSettingChange(OUTPUT_FO
|
||||
outputFormatField.value = getOutputFormat()
|
||||
|
||||
diskPathField.addEventListener('change', handleStringSettingChange(DISK_PATH_KEY))
|
||||
widthField.addEventListener('change', resizeInpaintingEditor)
|
||||
heightField.addEventListener('change', resizeInpaintingEditor)
|
||||
widthField.addEventListener('change', onDimensionChange)
|
||||
heightField.addEventListener('change', onDimensionChange)
|
||||
|
||||
function onDimensionChange() {
|
||||
if (!maskSetting.checked) {
|
||||
return
|
||||
}
|
||||
let widthValue = parseInt(widthField.value)
|
||||
let heightValue = parseInt(heightField.value)
|
||||
|
||||
resizeInpaintingEditor(widthValue, heightValue)
|
||||
}
|
||||
|
||||
saveToDiskField.addEventListener('click', function(e) {
|
||||
diskPathField.disabled = !this.checked
|
||||
@ -1216,6 +1172,7 @@ function updateGuidanceScaleSlider() {
|
||||
}
|
||||
|
||||
guidanceScaleSlider.value = guidanceScaleField.value * 10
|
||||
guidanceScaleSlider.dispatchEvent(new Event("change"))
|
||||
}
|
||||
|
||||
guidanceScaleSlider.addEventListener('input', updateGuidanceScale)
|
||||
@ -1234,6 +1191,7 @@ function updatePromptStrengthSlider() {
|
||||
}
|
||||
|
||||
promptStrengthSlider.value = promptStrengthField.value * 100
|
||||
promptStrengthSlider.dispatchEvent(new Event("change"))
|
||||
}
|
||||
|
||||
promptStrengthSlider.addEventListener('input', updatePromptStrength)
|
||||
@ -1380,7 +1338,7 @@ initImageClearBtn.addEventListener('click', function() {
|
||||
|
||||
maskSetting.addEventListener('click', function() {
|
||||
inpaintingEditorContainer.style.display = (this.checked ? 'block' : 'none')
|
||||
resizeInpaintingEditor()
|
||||
onDimensionChange()
|
||||
})
|
||||
|
||||
promptsFromFileBtn.addEventListener('click', function() {
|
||||
@ -1404,150 +1362,6 @@ promptsFromFileSelector.addEventListener('change', function() {
|
||||
}
|
||||
})
|
||||
|
||||
// function showMaskImagePreview() {
|
||||
// if (maskImageSelector.files.length === 0) {
|
||||
// // maskImagePreviewContainer.style.display = 'none'
|
||||
// return
|
||||
// }
|
||||
|
||||
// let reader = new FileReader()
|
||||
// let file = maskImageSelector.files[0]
|
||||
|
||||
// reader.addEventListener('load', function() {
|
||||
// // maskImagePreview.src = reader.result
|
||||
// // maskImagePreviewContainer.style.display = 'block'
|
||||
// })
|
||||
|
||||
// if (file) {
|
||||
// reader.readAsDataURL(file)
|
||||
// }
|
||||
// }
|
||||
// maskImageSelector.addEventListener('change', showMaskImagePreview)
|
||||
// showMaskImagePreview()
|
||||
|
||||
// maskImageClearBtn.addEventListener('click', function() {
|
||||
// maskImageSelector.value = null
|
||||
// maskImagePreview.src = ''
|
||||
// // maskImagePreviewContainer.style.display = 'none'
|
||||
// })
|
||||
|
||||
// https://stackoverflow.com/a/8212878
|
||||
function millisecondsToStr(milliseconds) {
|
||||
function numberEnding (number) {
|
||||
return (number > 1) ? 's' : ''
|
||||
}
|
||||
|
||||
var temp = Math.floor(milliseconds / 1000)
|
||||
var hours = Math.floor((temp %= 86400) / 3600)
|
||||
var s = ''
|
||||
if (hours) {
|
||||
s += hours + ' hour' + numberEnding(hours) + ' '
|
||||
}
|
||||
var minutes = Math.floor((temp %= 3600) / 60)
|
||||
if (minutes) {
|
||||
s += minutes + ' minute' + numberEnding(minutes) + ' '
|
||||
}
|
||||
var seconds = temp % 60
|
||||
if (!hours && minutes < 4 && seconds) {
|
||||
s += seconds + ' second' + numberEnding(seconds)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/
|
||||
function getNextSibling(elem, selector) {
|
||||
// Get the next sibling element
|
||||
var sibling = elem.nextElementSibling
|
||||
|
||||
// If there's no selector, return the first sibling
|
||||
if (!selector) return sibling
|
||||
|
||||
// If the sibling matches our selector, use it
|
||||
// If not, jump to the next sibling and continue the loop
|
||||
while (sibling) {
|
||||
if (sibling.matches(selector)) return sibling
|
||||
sibling = sibling.nextElementSibling
|
||||
}
|
||||
}
|
||||
|
||||
function createCollapsibles(node) {
|
||||
if (!node) {
|
||||
node = document
|
||||
}
|
||||
|
||||
let collapsibles = node.querySelectorAll(".collapsible")
|
||||
collapsibles.forEach(function(c) {
|
||||
let handle = document.createElement('span')
|
||||
handle.className = 'collapsible-handle'
|
||||
|
||||
if (c.className.indexOf('active') !== -1) {
|
||||
handle.innerHTML = '➖' // minus
|
||||
} else {
|
||||
handle.innerHTML = '➕' // plus
|
||||
}
|
||||
c.insertBefore(handle, c.firstChild)
|
||||
|
||||
c.addEventListener('click', function() {
|
||||
this.classList.toggle("active")
|
||||
let content = getNextSibling(this, '.collapsible-content')
|
||||
if (content.style.display === "block") {
|
||||
content.style.display = "none"
|
||||
handle.innerHTML = '➕' // plus
|
||||
} else {
|
||||
content.style.display = "block"
|
||||
handle.innerHTML = '➖' // minus
|
||||
}
|
||||
|
||||
if (this == advancedPanelHandle) {
|
||||
let state = (content.style.display === 'block' ? 'true' : 'false')
|
||||
localStorage.setItem(ADVANCED_PANEL_OPEN_KEY, state)
|
||||
} else if (this == modifiersPanelHandle) {
|
||||
let state = (content.style.display === 'block' ? 'true' : 'false')
|
||||
localStorage.setItem(MODIFIERS_PANEL_OPEN_KEY, state)
|
||||
} else if (this == negativePromptPanelHandle) {
|
||||
let state = (content.style.display === 'block' ? 'true' : 'false')
|
||||
localStorage.setItem(NEGATIVE_PROMPT_PANEL_OPEN_KEY, state)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
createCollapsibles()
|
||||
|
||||
function refreshTagsList() {
|
||||
editorModifierTagsList.innerHTML = ''
|
||||
|
||||
if (activeTags.length == 0) {
|
||||
editorTagsContainer.style.display = 'none'
|
||||
return
|
||||
} else {
|
||||
editorTagsContainer.style.display = 'block'
|
||||
}
|
||||
|
||||
activeTags.forEach((tag, index) => {
|
||||
tag.element.querySelector('.modifier-card-image-overlay').innerText = '-'
|
||||
tag.element.classList.add('modifier-card-tiny')
|
||||
|
||||
editorModifierTagsList.appendChild(tag.element)
|
||||
|
||||
tag.element.addEventListener('click', () => {
|
||||
let idx = activeTags.indexOf(tag)
|
||||
|
||||
if (idx !== -1) {
|
||||
activeTags[idx].originElement.classList.remove(activeCardClass)
|
||||
activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+'
|
||||
|
||||
activeTags.splice(idx, 1)
|
||||
refreshTagsList()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let brk = document.createElement('br')
|
||||
brk.style.clear = 'both'
|
||||
editorModifierTagsList.appendChild(brk)
|
||||
}
|
||||
|
||||
async function getDiskPath() {
|
||||
try {
|
||||
let diskPath = getSavedDiskPath()
|
||||
@ -1569,246 +1383,4 @@ async function getDiskPath() {
|
||||
}
|
||||
}
|
||||
|
||||
function createModifierCard(name, previews) {
|
||||
const modifierCard = document.createElement('div')
|
||||
modifierCard.className = 'modifier-card'
|
||||
modifierCard.innerHTML = `
|
||||
<div class="modifier-card-overlay"></div>
|
||||
<div class="modifier-card-image-container">
|
||||
<div class="modifier-card-image-overlay">+</div>
|
||||
<p class="modifier-card-error-label"></p>
|
||||
<img onerror="this.remove()" alt="Modifier Image" class="modifier-card-image">
|
||||
</div>
|
||||
<div class="modifier-card-container">
|
||||
<div class="modifier-card-label"><p></p></div>
|
||||
</div>`
|
||||
|
||||
const image = modifierCard.querySelector('.modifier-card-image')
|
||||
const errorText = modifierCard.querySelector('.modifier-card-error-label')
|
||||
const label = modifierCard.querySelector('.modifier-card-label')
|
||||
|
||||
errorText.innerText = 'No Image'
|
||||
|
||||
if (typeof previews == 'object') {
|
||||
image.src = previews[0]; // portrait
|
||||
image.setAttribute('preview-type', 'portrait')
|
||||
} else {
|
||||
image.remove()
|
||||
}
|
||||
|
||||
const maxLabelLength = 30
|
||||
const nameWithoutBy = name.replace('by ', '')
|
||||
|
||||
if(nameWithoutBy.length <= maxLabelLength) {
|
||||
label.querySelector('p').innerText = nameWithoutBy
|
||||
} else {
|
||||
const tooltipText = document.createElement('span')
|
||||
tooltipText.className = 'tooltip-text'
|
||||
tooltipText.innerText = name
|
||||
|
||||
label.classList.add('tooltip')
|
||||
label.appendChild(tooltipText)
|
||||
|
||||
label.querySelector('p').innerText = nameWithoutBy.substring(0, maxLabelLength) + '...'
|
||||
}
|
||||
|
||||
return modifierCard
|
||||
}
|
||||
|
||||
function changePreviewImages(val) {
|
||||
const previewImages = document.querySelectorAll('.modifier-card-image-container img')
|
||||
|
||||
let previewArr = []
|
||||
|
||||
modifiers.map(x => x.modifiers).forEach(x => previewArr.push(...x.map(m => m.previews)))
|
||||
|
||||
previewArr = previewArr.map(x => {
|
||||
let obj = {}
|
||||
|
||||
x.forEach(preview => {
|
||||
obj[preview.name] = preview.path
|
||||
})
|
||||
|
||||
return obj
|
||||
})
|
||||
|
||||
previewImages.forEach(previewImage => {
|
||||
const currentPreviewType = previewImage.getAttribute('preview-type')
|
||||
const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + '/').pop()
|
||||
|
||||
const previews = previewArr.find(preview => relativePreviewPath == preview[currentPreviewType])
|
||||
|
||||
if(typeof previews == 'object') {
|
||||
let preview = null
|
||||
|
||||
if (val == 'portrait') {
|
||||
preview = previews.portrait
|
||||
}
|
||||
else if (val == 'landscape') {
|
||||
preview = previews.landscape
|
||||
}
|
||||
|
||||
if(preview != null) {
|
||||
previewImage.src = `${modifierThumbnailPath}/${preview}`
|
||||
previewImage.setAttribute('preview-type', val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function resizeModifierCards(val) {
|
||||
const cardSizePrefix = 'modifier-card-size_'
|
||||
const modifierCardClass = 'modifier-card'
|
||||
|
||||
const modifierCards = document.querySelectorAll(`.${modifierCardClass}`)
|
||||
const cardSize = n => `${cardSizePrefix}${n}`
|
||||
|
||||
modifierCards.forEach(card => {
|
||||
// remove existing size classes
|
||||
const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix))
|
||||
card.className = classes.join(' ').trim()
|
||||
|
||||
if(val != 0) {
|
||||
card.classList.add(cardSize(val))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function loadModifiers() {
|
||||
try {
|
||||
let res = await fetch('/get/modifiers')
|
||||
if (res.status === 200) {
|
||||
res = await res.json()
|
||||
|
||||
modifiers = res; // update global variable
|
||||
|
||||
res.forEach((modifierGroup, idx) => {
|
||||
const title = modifierGroup.category
|
||||
const modifiers = modifierGroup.modifiers
|
||||
|
||||
const titleEl = document.createElement('h5')
|
||||
titleEl.className = 'collapsible'
|
||||
titleEl.innerText = title
|
||||
|
||||
const modifiersEl = document.createElement('div')
|
||||
modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf')
|
||||
|
||||
if (idx == 0) {
|
||||
titleEl.className += ' active'
|
||||
modifiersEl.style.display = 'block'
|
||||
}
|
||||
|
||||
modifiers.forEach(modObj => {
|
||||
const modifierName = modObj.modifier
|
||||
const modifierPreviews = modObj?.previews?.map(preview => `${modifierThumbnailPath}/${preview.path}`)
|
||||
|
||||
const modifierCard = createModifierCard(modifierName, modifierPreviews)
|
||||
|
||||
if(typeof modifierCard == 'object') {
|
||||
modifiersEl.appendChild(modifierCard)
|
||||
|
||||
modifierCard.addEventListener('click', () => {
|
||||
if (activeTags.map(x => x.name).includes(modifierName)) {
|
||||
// remove modifier from active array
|
||||
activeTags = activeTags.filter(x => x.name != modifierName)
|
||||
modifierCard.classList.remove(activeCardClass)
|
||||
|
||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+'
|
||||
} else {
|
||||
// add modifier to active array
|
||||
activeTags.push({
|
||||
'name': modifierName,
|
||||
'element': modifierCard.cloneNode(true),
|
||||
'originElement': modifierCard,
|
||||
'previews': modifierPreviews
|
||||
})
|
||||
|
||||
modifierCard.classList.add(activeCardClass)
|
||||
|
||||
modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-'
|
||||
}
|
||||
|
||||
refreshTagsList()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
let brk = document.createElement('br')
|
||||
brk.style.clear = 'both'
|
||||
modifiersEl.appendChild(brk)
|
||||
|
||||
let e = document.createElement('div')
|
||||
e.appendChild(titleEl)
|
||||
e.appendChild(modifiersEl)
|
||||
|
||||
editorModifierEntries.appendChild(e)
|
||||
})
|
||||
|
||||
createCollapsibles(editorModifierEntries)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error fetching modifiers', e)
|
||||
}
|
||||
}
|
||||
|
||||
var DEFAULT_THEME = {};
|
||||
var THEMES = []; // initialized in initTheme from data in css
|
||||
|
||||
function getThemeName(theme) {
|
||||
theme = theme.replace("theme-", "");
|
||||
theme = theme.split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
||||
return theme;
|
||||
}
|
||||
// init themefield
|
||||
function initTheme() {
|
||||
Array.from(document.styleSheets)
|
||||
.filter(sheet => sheet.href?.startsWith(window.location.origin))
|
||||
.flatMap(sheet => Array.from(sheet.cssRules))
|
||||
.forEach(rule => {
|
||||
var selector = rule.selectorText; // TODO: also do selector == ":root", re-run un-set props
|
||||
if (selector && selector.startsWith(".theme-")) {
|
||||
var theme_key = selector.substring(1);
|
||||
THEMES.push({
|
||||
key: theme_key,
|
||||
name: getThemeName(theme_key),
|
||||
rule: rule
|
||||
})
|
||||
}
|
||||
if (selector && selector == ":root") {
|
||||
DEFAULT_THEME = {
|
||||
key: "theme-default",
|
||||
name: "Default",
|
||||
rule: rule
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
THEMES.forEach(theme => {
|
||||
var new_option = document.createElement("option");
|
||||
new_option.setAttribute("value", theme.key);
|
||||
new_option.innerText = theme.name;
|
||||
themeField.appendChild(new_option);
|
||||
});
|
||||
}
|
||||
initTheme();
|
||||
|
||||
function themeFieldChanged() {
|
||||
var theme_key = themeField.value;
|
||||
|
||||
var body = document.querySelector("body");
|
||||
body.classList.remove(...THEMES.map(theme => theme.key));
|
||||
body.classList.add(theme_key);
|
||||
|
||||
body.style = "";
|
||||
var theme = THEMES.find(t => t.key == theme_key);
|
||||
if (theme) {
|
||||
// refresh variables incase they are back referencing
|
||||
Array.from(DEFAULT_THEME.rule.style)
|
||||
.filter(cssVariable => !Array.from(theme.rule.style).includes(cssVariable))
|
||||
.forEach(cssVariable => {
|
||||
body.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
themeField.addEventListener('change', themeFieldChanged);
|
||||
createCollapsibles()
|
47
ui/media/js/plugins.js
Normal file
47
ui/media/js/plugins.js
Normal file
@ -0,0 +1,47 @@
|
||||
const PLUGIN_API_VERSION = "1.0"
|
||||
|
||||
const PLUGINS = {
|
||||
/**
|
||||
* Register new buttons to show on each output image.
|
||||
*
|
||||
* Example:
|
||||
* PLUGINS['IMAGE_INFO_BUTTONS'].push({
|
||||
* text: 'Make a Similar Image',
|
||||
* on_click: function(origRequest, image) {
|
||||
* let newTaskRequest = getCurrentUserRequest()
|
||||
* newTaskRequest.reqBody = Object.assign({}, origRequest, {
|
||||
* init_image: image.src,
|
||||
* prompt_strength: 0.7,
|
||||
* seed: Math.floor(Math.random() * 10000000)
|
||||
* })
|
||||
* newTaskRequest.seed = newTaskRequest.reqBody.seed
|
||||
* createTask(newTaskRequest)
|
||||
* },
|
||||
* filter: function(origRequest, image) {
|
||||
* // this is an optional function. return true/false to show/hide the button
|
||||
* // if this function isn't set, the button will always be visible
|
||||
* return true
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
IMAGE_INFO_BUTTONS: []
|
||||
}
|
||||
|
||||
async function loadUIPlugins() {
|
||||
try {
|
||||
let res = await fetch('/get/ui_plugins')
|
||||
if (res.status === 200) {
|
||||
res = await res.json()
|
||||
res.forEach(pluginPath => {
|
||||
let script = document.createElement('script')
|
||||
script.src = pluginPath + '?t=' + Date.now()
|
||||
|
||||
console.log('loading plugin', pluginPath)
|
||||
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error fetching plugin paths', e)
|
||||
}
|
||||
}
|
61
ui/media/js/themes.js
Normal file
61
ui/media/js/themes.js
Normal file
@ -0,0 +1,61 @@
|
||||
var DEFAULT_THEME = {};
|
||||
var THEMES = []; // initialized in initTheme from data in css
|
||||
|
||||
function getThemeName(theme) {
|
||||
theme = theme.replace("theme-", "");
|
||||
theme = theme.split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
||||
return theme;
|
||||
}
|
||||
// init themefield
|
||||
function initTheme() {
|
||||
Array.from(document.styleSheets)
|
||||
.filter(sheet => sheet.href?.startsWith(window.location.origin))
|
||||
.flatMap(sheet => Array.from(sheet.cssRules))
|
||||
.forEach(rule => {
|
||||
var selector = rule.selectorText; // TODO: also do selector == ":root", re-run un-set props
|
||||
if (selector && selector.startsWith(".theme-")) {
|
||||
var theme_key = selector.substring(1);
|
||||
THEMES.push({
|
||||
key: theme_key,
|
||||
name: getThemeName(theme_key),
|
||||
rule: rule
|
||||
})
|
||||
}
|
||||
if (selector && selector == ":root") {
|
||||
DEFAULT_THEME = {
|
||||
key: "theme-default",
|
||||
name: "Default",
|
||||
rule: rule
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
THEMES.forEach(theme => {
|
||||
var new_option = document.createElement("option");
|
||||
new_option.setAttribute("value", theme.key);
|
||||
new_option.innerText = theme.name;
|
||||
themeField.appendChild(new_option);
|
||||
});
|
||||
}
|
||||
initTheme();
|
||||
|
||||
function themeFieldChanged() {
|
||||
var theme_key = themeField.value;
|
||||
|
||||
var body = document.querySelector("body");
|
||||
body.classList.remove(...THEMES.map(theme => theme.key));
|
||||
body.classList.add(theme_key);
|
||||
|
||||
body.style = "";
|
||||
var theme = THEMES.find(t => t.key == theme_key);
|
||||
if (theme) {
|
||||
// refresh variables incase they are back referencing
|
||||
Array.from(DEFAULT_THEME.rule.style)
|
||||
.filter(cssVariable => !Array.from(theme.rule.style).includes(cssVariable))
|
||||
.forEach(cssVariable => {
|
||||
body.style.setProperty(cssVariable, DEFAULT_THEME.rule.style.getPropertyValue(cssVariable));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
themeField.addEventListener('change', themeFieldChanged);
|
276
ui/media/js/utils.js
Normal file
276
ui/media/js/utils.js
Normal file
@ -0,0 +1,276 @@
|
||||
// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/
|
||||
function getNextSibling(elem, selector) {
|
||||
// Get the next sibling element
|
||||
var sibling = elem.nextElementSibling
|
||||
|
||||
// If there's no selector, return the first sibling
|
||||
if (!selector) return sibling
|
||||
|
||||
// If the sibling matches our selector, use it
|
||||
// If not, jump to the next sibling and continue the loop
|
||||
while (sibling) {
|
||||
if (sibling.matches(selector)) return sibling
|
||||
sibling = sibling.nextElementSibling
|
||||
}
|
||||
}
|
||||
|
||||
function createCollapsibles(node) {
|
||||
if (!node) {
|
||||
node = document
|
||||
}
|
||||
|
||||
let collapsibles = node.querySelectorAll(".collapsible")
|
||||
collapsibles.forEach(function(c) {
|
||||
let handle = document.createElement('span')
|
||||
handle.className = 'collapsible-handle'
|
||||
|
||||
if (c.className.indexOf('active') !== -1) {
|
||||
handle.innerHTML = '➖' // minus
|
||||
} else {
|
||||
handle.innerHTML = '➕' // plus
|
||||
}
|
||||
c.insertBefore(handle, c.firstChild)
|
||||
|
||||
c.addEventListener('click', function() {
|
||||
this.classList.toggle("active")
|
||||
let content = getNextSibling(this, '.collapsible-content')
|
||||
if (content.style.display === "block") {
|
||||
content.style.display = "none"
|
||||
handle.innerHTML = '➕' // plus
|
||||
} else {
|
||||
content.style.display = "block"
|
||||
handle.innerHTML = '➖' // minus
|
||||
}
|
||||
|
||||
if (this == advancedPanelHandle) {
|
||||
let state = (content.style.display === 'block' ? 'true' : 'false')
|
||||
localStorage.setItem(ADVANCED_PANEL_OPEN_KEY, state)
|
||||
} else if (this == modifiersPanelHandle) {
|
||||
let state = (content.style.display === 'block' ? 'true' : 'false')
|
||||
localStorage.setItem(MODIFIERS_PANEL_OPEN_KEY, state)
|
||||
} else if (this == negativePromptPanelHandle) {
|
||||
let state = (content.style.display === 'block' ? 'true' : 'false')
|
||||
localStorage.setItem(NEGATIVE_PROMPT_PANEL_OPEN_KEY, state)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function permute(arr) {
|
||||
let permutations = []
|
||||
let n = arr.length
|
||||
let n_permutations = Math.pow(2, n)
|
||||
for (let i = 0; i < n_permutations; i++) {
|
||||
let perm = []
|
||||
let mask = Number(i).toString(2).padStart(n, '0')
|
||||
|
||||
for (let idx = 0; idx < mask.length; idx++) {
|
||||
if (mask[idx] === '1' && arr[idx].trim() !== '') {
|
||||
perm.push(arr[idx])
|
||||
}
|
||||
}
|
||||
|
||||
if (perm.length > 0) {
|
||||
permutations.push(perm)
|
||||
}
|
||||
}
|
||||
|
||||
return permutations
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/8212878
|
||||
function millisecondsToStr(milliseconds) {
|
||||
function numberEnding (number) {
|
||||
return (number > 1) ? 's' : ''
|
||||
}
|
||||
|
||||
var temp = Math.floor(milliseconds / 1000)
|
||||
var hours = Math.floor((temp %= 86400) / 3600)
|
||||
var s = ''
|
||||
if (hours) {
|
||||
s += hours + ' hour' + numberEnding(hours) + ' '
|
||||
}
|
||||
var minutes = Math.floor((temp %= 3600) / 60)
|
||||
if (minutes) {
|
||||
s += minutes + ' minute' + numberEnding(minutes) + ' '
|
||||
}
|
||||
var seconds = temp % 60
|
||||
if (!hours && minutes < 4 && seconds) {
|
||||
s += seconds + ' second' + numberEnding(seconds)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// https://rosettacode.org/wiki/Brace_expansion#JavaScript
|
||||
function BraceExpander() {
|
||||
'use strict'
|
||||
|
||||
// Index of any closing brace matching the opening
|
||||
// brace at iPosn,
|
||||
// with the indices of any immediately-enclosed commas.
|
||||
function bracePair(tkns, iPosn, iNest, lstCommas) {
|
||||
if (iPosn >= tkns.length || iPosn < 0) return null;
|
||||
|
||||
var t = tkns[iPosn],
|
||||
n = (t === '{') ? (
|
||||
iNest + 1
|
||||
) : (t === '}' ? (
|
||||
iNest - 1
|
||||
) : iNest),
|
||||
lst = (t === ',' && iNest === 1) ? (
|
||||
lstCommas.concat(iPosn)
|
||||
) : lstCommas;
|
||||
|
||||
return n ? bracePair(tkns, iPosn + 1, n, lst) : {
|
||||
close: iPosn,
|
||||
commas: lst
|
||||
};
|
||||
}
|
||||
|
||||
// Parse of a SYNTAGM subtree
|
||||
function andTree(dctSofar, tkns) {
|
||||
if (!tkns.length) return [dctSofar, []];
|
||||
|
||||
var dctParse = dctSofar ? dctSofar : {
|
||||
fn: and,
|
||||
args: []
|
||||
},
|
||||
|
||||
head = tkns[0],
|
||||
tail = head ? tkns.slice(1) : [],
|
||||
|
||||
dctBrace = head === '{' ? bracePair(
|
||||
tkns, 0, 0, []
|
||||
) : null,
|
||||
|
||||
lstOR = dctBrace && (
|
||||
dctBrace.close
|
||||
) && dctBrace.commas.length ? (
|
||||
splitAt(dctBrace.close + 1, tkns)
|
||||
) : null;
|
||||
|
||||
return andTree({
|
||||
fn: and,
|
||||
args: dctParse.args.concat(
|
||||
lstOR ? (
|
||||
orTree(dctParse, lstOR[0], dctBrace.commas)
|
||||
) : head
|
||||
)
|
||||
}, lstOR ? (
|
||||
lstOR[1]
|
||||
) : tail);
|
||||
}
|
||||
|
||||
// Parse of a PARADIGM subtree
|
||||
function orTree(dctSofar, tkns, lstCommas) {
|
||||
if (!tkns.length) return [dctSofar, []];
|
||||
var iLast = lstCommas.length;
|
||||
|
||||
return {
|
||||
fn: or,
|
||||
args: splitsAt(
|
||||
lstCommas, tkns
|
||||
).map(function (x, i) {
|
||||
var ts = x.slice(
|
||||
1, i === iLast ? (
|
||||
-1
|
||||
) : void 0
|
||||
);
|
||||
|
||||
return ts.length ? ts : [''];
|
||||
}).map(function (ts) {
|
||||
return ts.length > 1 ? (
|
||||
andTree(null, ts)[0]
|
||||
) : ts[0];
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// List of unescaped braces and commas, and remaining strings
|
||||
function tokens(str) {
|
||||
// Filter function excludes empty splitting artefacts
|
||||
var toS = function (x) {
|
||||
return x.toString();
|
||||
};
|
||||
|
||||
return str.split(/(\\\\)/).filter(toS).reduce(function (a, s) {
|
||||
return a.concat(s.charAt(0) === '\\' ? s : s.split(
|
||||
/(\\*[{,}])/
|
||||
).filter(toS));
|
||||
}, []);
|
||||
}
|
||||
|
||||
// PARSE TREE OPERATOR (1 of 2)
|
||||
// Each possible head * each possible tail
|
||||
function and(args) {
|
||||
var lng = args.length,
|
||||
head = lng ? args[0] : null,
|
||||
lstHead = "string" === typeof head ? (
|
||||
[head]
|
||||
) : head;
|
||||
|
||||
return lng ? (
|
||||
1 < lng ? lstHead.reduce(function (a, h) {
|
||||
return a.concat(
|
||||
and(args.slice(1)).map(function (t) {
|
||||
return h + t;
|
||||
})
|
||||
);
|
||||
}, []) : lstHead
|
||||
) : [];
|
||||
}
|
||||
|
||||
// PARSE TREE OPERATOR (2 of 2)
|
||||
// Each option flattened
|
||||
function or(args) {
|
||||
return args.reduce(function (a, b) {
|
||||
return a.concat(b);
|
||||
}, []);
|
||||
}
|
||||
|
||||
// One list split into two (first sublist length n)
|
||||
function splitAt(n, lst) {
|
||||
return n < lst.length + 1 ? [
|
||||
lst.slice(0, n), lst.slice(n)
|
||||
] : [lst, []];
|
||||
}
|
||||
|
||||
// One list split into several (sublist lengths [n])
|
||||
function splitsAt(lstN, lst) {
|
||||
return lstN.reduceRight(function (a, x) {
|
||||
return splitAt(x, a[0]).concat(a.slice(1));
|
||||
}, [lst]);
|
||||
}
|
||||
|
||||
// Value of the parse tree
|
||||
function evaluated(e) {
|
||||
return typeof e === 'string' ? e :
|
||||
e.fn(e.args.map(evaluated));
|
||||
}
|
||||
|
||||
// JSON prettyprint (for parse tree, token list etc)
|
||||
function pp(e) {
|
||||
return JSON.stringify(e, function (k, v) {
|
||||
return typeof v === 'function' ? (
|
||||
'[function ' + v.name + ']'
|
||||
) : v;
|
||||
}, 2)
|
||||
}
|
||||
|
||||
|
||||
// ----------------------- MAIN ------------------------
|
||||
|
||||
// s -> [s]
|
||||
this.expand = function(s) {
|
||||
// BRACE EXPRESSION PARSED
|
||||
var dctParse = andTree(null, tokens(s))[0];
|
||||
|
||||
// ABSTRACT SYNTAX TREE LOGGED
|
||||
// console.log(pp(dctParse));
|
||||
|
||||
// AST EVALUATED TO LIST OF STRINGS
|
||||
return evaluated(dctParse);
|
||||
}
|
||||
|
||||
}
|
@ -207,6 +207,7 @@ def get_base_path(disk_path, session_id, prompt, img_id, ext, suffix=None):
|
||||
|
||||
prompt_flattened = filename_regex.sub('_', prompt)[:50]
|
||||
|
||||
|
||||
if suffix is not None:
|
||||
return os.path.join(session_out_path, f"{prompt_flattened}_{img_id}_{suffix}.{ext}")
|
||||
return os.path.join(session_out_path, f"{prompt_flattened}_{img_id}.{ext}")
|
||||
@ -505,7 +506,7 @@ def do_mk_img(req: Request):
|
||||
if (len(filters_applied) > 0):
|
||||
filtered_image = Image.fromarray(x_sample)
|
||||
filtered_img_data = img_to_base64_str(filtered_image, req.output_format)
|
||||
response_image = ResponseImage(data=filtered_img_data, seed=req.seed)
|
||||
response_image = ResponseImage(data=filtered_img_data, seed=opt_seed)
|
||||
res.images.append(response_image)
|
||||
if req.save_to_disk_path is not None:
|
||||
filtered_img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format, "_".join(filters_applied))
|
||||
|
17
ui/server.py
17
ui/server.py
@ -12,6 +12,7 @@ sys.path.append(os.path.dirname(SD_UI_DIR))
|
||||
|
||||
CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, '..', 'scripts'))
|
||||
MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'models'))
|
||||
UI_PLUGINS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'plugins', 'ui'))
|
||||
|
||||
OUTPUT_DIRNAME = "Stable Diffusion UI" # in the user's home folder
|
||||
TASK_TTL = 15 * 60 # Discard last session's task timeout
|
||||
@ -31,11 +32,15 @@ app = FastAPI()
|
||||
modifiers_cache = None
|
||||
outpath = os.path.join(os.path.expanduser("~"), OUTPUT_DIRNAME)
|
||||
|
||||
os.makedirs(UI_PLUGINS_DIR, exist_ok=True)
|
||||
|
||||
# don't show access log entries for URLs that start with the given prefix
|
||||
ACCESS_LOG_SUPPRESS_PATH_PREFIXES = ['/ping', '/image', '/modifier-thumbnails']
|
||||
|
||||
NOCACHE_HEADERS={"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"}
|
||||
app.mount('/media', StaticFiles(directory=os.path.join(SD_UI_DIR, 'media/')), name="media")
|
||||
|
||||
app.mount('/media', StaticFiles(directory=os.path.join(SD_UI_DIR, 'media')), name="media")
|
||||
app.mount('/plugins', StaticFiles(directory=UI_PLUGINS_DIR), name="plugins")
|
||||
|
||||
class SetAppConfigRequest(BaseModel):
|
||||
update_branch: str = "main"
|
||||
@ -251,6 +256,15 @@ def getModels():
|
||||
|
||||
return models
|
||||
|
||||
def getUIPlugins():
|
||||
plugins = []
|
||||
|
||||
for file in os.listdir(UI_PLUGINS_DIR):
|
||||
if file.endswith('.plugin.js'):
|
||||
plugins.append(f'/plugins/{file}')
|
||||
|
||||
return plugins
|
||||
|
||||
@app.get('/get/{key:path}')
|
||||
def read_web_data(key:str=None):
|
||||
if not key: # /get without parameters, stable-diffusion easter egg.
|
||||
@ -264,6 +278,7 @@ def read_web_data(key:str=None):
|
||||
return JSONResponse(getModels(), headers=NOCACHE_HEADERS)
|
||||
elif key == 'modifiers': return FileResponse(os.path.join(SD_UI_DIR, 'modifiers.json'), headers=NOCACHE_HEADERS)
|
||||
elif key == 'output_dir': return JSONResponse({ 'output_dir': outpath }, headers=NOCACHE_HEADERS)
|
||||
elif key == 'ui_plugins': return JSONResponse(getUIPlugins(), headers=NOCACHE_HEADERS)
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f'Request for unknown {key}') # HTTP404 Not Found
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user