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>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<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/images/favicon-16x16.png" sizes="16x16">
|
||||||
<link rel="icon" type="image/png" href="/media/favicon-32x32.png" sizes="32x32">
|
<link rel="icon" type="image/png" href="/media/images/favicon-32x32.png" sizes="32x32">
|
||||||
<link rel="stylesheet" href="/media/main.css?v=25">
|
<link rel="stylesheet" href="/media/css/fonts.css?v=1">
|
||||||
<link rel="stylesheet" href="/media/modifier-thumbnails.css?v=1">
|
<link rel="stylesheet" href="/media/css/themes.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/css/auto-save.css?v=1">
|
||||||
<link rel="stylesheet" href="/media/drawingboard.min.css">
|
<link rel="stylesheet" href="/media/css/main.css?v=1">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
|
<link rel="stylesheet" href="/media/css/modifier-thumbnails.css?v=1">
|
||||||
<script src="/media/jquery-3.6.1.min.js"></script>
|
<link rel="stylesheet" href="/media/css/fontawesome-all.min.css?v=1">
|
||||||
<script src="/media/drawingboard.min.js"></script>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="top-nav">
|
<div id="top-nav">
|
||||||
<div id="logo">
|
<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>
|
</div>
|
||||||
<ul id="top-nav-items">
|
<ul id="top-nav-items">
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
@ -106,7 +108,7 @@
|
|||||||
<ul id="editor-settings-entries" class="collapsible-content">
|
<ul id="editor-settings-entries" class="collapsible-content">
|
||||||
<li><table>
|
<li><table>
|
||||||
<tr><b class="settings-subheader">Image Settings</b></tr>
|
<tr><b class="settings-subheader">Image Settings</b></tr>
|
||||||
<tr class="pl-5"><td><label for="seed">Seed:</label></td><td><input id="seed" name="seed" size="10" value="30000"> <input id="random_seed" name="random_seed" type="checkbox" checked> <label for="random_seed">Random</label></td></tr>
|
<tr class="pl-5"><td><label for="seed">Seed:</label></td><td><input id="seed" name="seed" size="10" value="30000"> <input id="random_seed" name="random_seed" type="checkbox" checked><label for="random_seed">Random</label></td></tr>
|
||||||
<tr class="pl-5"><td><label for="num_outputs_total">Number of Images:</label></td><td><input id="num_outputs_total" name="num_outputs_total" value="1" size="1"> <label><small>(total)</small></label> <input id="num_outputs_parallel" name="num_outputs_parallel" value="1" size="1"> <label for="num_outputs_parallel"><small>(in parallel)</small></label></td></tr>
|
<tr class="pl-5"><td><label for="num_outputs_total">Number of Images:</label></td><td><input id="num_outputs_total" name="num_outputs_total" value="1" size="1"> <label><small>(total)</small></label> <input id="num_outputs_parallel" name="num_outputs_parallel" value="1" size="1"> <label for="num_outputs_parallel"><small>(in parallel)</small></label></td></tr>
|
||||||
<tr class="pl-5"><td><label for="stable_diffusion_model">Model:</label></td><td>
|
<tr class="pl-5"><td><label for="stable_diffusion_model">Model:</label></td><td>
|
||||||
<select id="stable_diffusion_model" name="stable_diffusion_model">
|
<select id="stable_diffusion_model" name="stable_diffusion_model">
|
||||||
@ -239,7 +241,7 @@
|
|||||||
<div class="line-separator"> </div>
|
<div class="line-separator"> </div>
|
||||||
|
|
||||||
<div id="footer" class="panel-box">
|
<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>
|
<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">
|
<div id="footer-legal">
|
||||||
<p><b>Disclaimer:</b> The authors of this project are not responsible for any content generated using this interface.</p>
|
<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>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script src="media/auto-save.js?v=2"></script>
|
<script src="media/js/plugins.js?v=1"></script>
|
||||||
<script src="media/main.js?v=37"></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>
|
<script>
|
||||||
async function init() {
|
async function init() {
|
||||||
await loadModifiers()
|
await loadModifiers()
|
||||||
@ -259,6 +266,7 @@ async function init() {
|
|||||||
await getAppConfig()
|
await getAppConfig()
|
||||||
await getModels()
|
await getModels()
|
||||||
await initSettings()
|
await initSettings()
|
||||||
|
await loadUIPlugins()
|
||||||
|
|
||||||
setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000)
|
setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000)
|
||||||
healthCheck()
|
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;
|
font-family: Work Sans, Verdana, Geneva, sans-serif;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -681,24 +533,6 @@ input::file-selector-button {
|
|||||||
height: 19px;
|
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) {
|
@media screen and (max-width: 700px) {
|
||||||
body {
|
body {
|
||||||
margin: 0px;
|
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 {
|
#promptsFromFileBtn {
|
||||||
font-size: 9pt;
|
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 AUTO_SAVE_SETTINGS_KEY = "autoSaveSettings"
|
||||||
const HEALTH_PING_INTERVAL = 5 // seconds
|
const HEALTH_PING_INTERVAL = 5 // seconds
|
||||||
const MAX_INIT_IMAGE_DIMENSION = 768
|
const MAX_INIT_IMAGE_DIMENSION = 768
|
||||||
const INPAINTING_EDITOR_SIZE = 450
|
|
||||||
|
|
||||||
const IMAGE_REGEX = new RegExp('data:image/[A-Za-z]+;base64')
|
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 maskSetting = document.querySelector('#enable_mask')
|
||||||
let negativePromptPanelHandle = document.querySelector('#negative_prompt_handle')
|
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 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')
|
// 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 advancedPanelHandle = document.querySelector("#editor-settings .collapsible")
|
||||||
let modifiersPanelHandle = document.querySelector("#editor-modifiers .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-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>'
|
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'
|
maskResetButton.style.fontSize = '10pt'
|
||||||
|
|
||||||
let serverState = {'status': 'Offline', 'time': Date.now()}
|
let serverState = {'status': 'Offline', 'time': Date.now()}
|
||||||
let activeTags = []
|
|
||||||
let modifiers = []
|
|
||||||
let lastPromptUsed = ''
|
let lastPromptUsed = ''
|
||||||
let bellPending = false
|
let bellPending = false
|
||||||
|
|
||||||
let taskQueue = []
|
let taskQueue = []
|
||||||
let currentTask = null
|
let currentTask = null
|
||||||
|
|
||||||
const modifierThumbnailPath = 'media/modifier-thumbnails'
|
|
||||||
const activeCardClass = 'modifier-card-active'
|
|
||||||
|
|
||||||
function getLocalStorageItem(key, fallback) {
|
function getLocalStorageItem(key, fallback) {
|
||||||
let item = localStorage.getItem(key)
|
let item = localStorage.getItem(key)
|
||||||
if (item === null) {
|
if (item === null) {
|
||||||
@ -336,40 +312,6 @@ async function healthCheck() {
|
|||||||
setServerStatus('error', 'offline')
|
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) {
|
function showImages(reqBody, res, outputContainer, livePreview) {
|
||||||
let imageItemElements = outputContainer.querySelectorAll('.imgItem')
|
let imageItemElements = outputContainer.querySelectorAll('.imgItem')
|
||||||
@ -424,116 +366,125 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
|||||||
const imageSeedLabel = imageItemElem.querySelector('.imgSeedLabel')
|
const imageSeedLabel = imageItemElem.querySelector('.imgSeedLabel')
|
||||||
imageSeedLabel.innerText = 'Seed: ' + req.seed
|
imageSeedLabel.innerText = 'Seed: ' + req.seed
|
||||||
|
|
||||||
const buttons = {
|
let buttons = [
|
||||||
'imgUseBtn': { html: 'Use as Input', click: getUseAsInputHandler(imageItemElem) },
|
{ text: 'Use as Input', on_click: onUseAsInputClick },
|
||||||
'imgSaveBtn': { html: 'Download', click: getSaveImageHandler(imageItemElem, req['output_format']) },
|
{ text: 'Download', on_click: onDownloadImageClick },
|
||||||
// 'imgX2Btn': { html: 'Double Size', click: getStartNewTaskHandler(req, imageItemElem, 'img2img_X2') },
|
{ text: 'Make Similar Images', on_click: onMakeSimilarClick },
|
||||||
// 'imgRedoBtn': { html: 'Redo', click: getStartNewTaskHandler(req, imageItemElem, 'img2img') },
|
{ text: 'Draw another 25 steps', on_click: onContinueDrawingClick },
|
||||||
}
|
{ text: 'Upscale', on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale },
|
||||||
// if (!req.use_upscale) {
|
{ text: 'Fix Faces', on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction }
|
||||||
// buttons.upscaleBtn = { html: 'Upscale', click: getStartNewTaskHandler(req, imageItemElem, 'upscale') }
|
]
|
||||||
// }
|
|
||||||
|
// include the plugins
|
||||||
|
buttons = buttons.concat(PLUGINS['IMAGE_INFO_BUTTONS'])
|
||||||
|
|
||||||
const imgItemInfo = imageItemElem.querySelector('.imgItemInfo')
|
const imgItemInfo = imageItemElem.querySelector('.imgItemInfo')
|
||||||
const createButton = function(name, btnInfo) {
|
const img = imageItemElem.querySelector('img')
|
||||||
|
const createButton = function(btnInfo) {
|
||||||
const newButton = document.createElement('button')
|
const newButton = document.createElement('button')
|
||||||
newButton.classList.add(name)
|
|
||||||
newButton.classList.add('tasksBtns')
|
newButton.classList.add('tasksBtns')
|
||||||
newButton.innerHTML = btnInfo.html
|
newButton.innerText = btnInfo.text
|
||||||
newButton.addEventListener('click', btnInfo.click)
|
newButton.addEventListener('click', function() {
|
||||||
|
btnInfo.on_click(req, img)
|
||||||
|
})
|
||||||
imgItemInfo.appendChild(newButton)
|
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) {
|
function onUseAsInputClick(req, img) {
|
||||||
return function() {
|
const imgData = img.src
|
||||||
const imageElem = imageItemElem.querySelector('img')
|
|
||||||
const imgData = imageElem.src
|
|
||||||
const imageSeed = imageElem.getAttribute('data-seed')
|
|
||||||
|
|
||||||
initImageSelector.value = null
|
initImageSelector.value = null
|
||||||
initImagePreview.src = imgData
|
initImagePreview.src = imgData
|
||||||
|
|
||||||
initImagePreviewContainer.style.display = 'block'
|
initImagePreviewContainer.style.display = 'block'
|
||||||
inpaintingEditorContainer.style.display = 'none'
|
inpaintingEditorContainer.style.display = 'none'
|
||||||
promptStrengthContainer.style.display = 'table-row'
|
promptStrengthContainer.style.display = 'table-row'
|
||||||
maskSetting.checked = false
|
maskSetting.checked = false
|
||||||
samplerSelectionContainer.style.display = 'none'
|
samplerSelectionContainer.style.display = 'none'
|
||||||
|
|
||||||
// maskSetting.style.display = 'block'
|
|
||||||
|
|
||||||
// randomSeedField.checked = false
|
|
||||||
// seedField.value = imageSeed
|
|
||||||
// seedField.disabled = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSaveImageHandler(imageItemElem, outputFormat) {
|
function onDownloadImageClick(req, img) {
|
||||||
return function() {
|
const imgData = img.src
|
||||||
const imageElem = imageItemElem.querySelector('img')
|
const imageSeed = img.getAttribute('data-seed')
|
||||||
const imgData = imageElem.src
|
const imagePrompt = img.getAttribute('data-prompt')
|
||||||
const imageSeed = imageElem.getAttribute('data-seed')
|
const imageInferenceSteps = img.getAttribute('data-steps')
|
||||||
const imagePrompt = imageElem.getAttribute('data-prompt')
|
const imageGuidanceScale = img.getAttribute('data-guidance')
|
||||||
const imageInferenceSteps = imageElem.getAttribute('data-steps')
|
|
||||||
const imageGuidanceScale = imageElem.getAttribute('data-guidance')
|
|
||||||
|
|
||||||
const imgDownload = document.createElement('a')
|
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.href = imgData
|
||||||
imgDownload.click()
|
imgDownload.click()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function getStartNewTaskHandler(reqBody, imageItemElem, mode) {
|
|
||||||
return function() {
|
function modifyCurrentRequest(req, reqDiff) {
|
||||||
if (!isServerAvailable()) {
|
const newTaskRequest = getCurrentUserRequest()
|
||||||
alert('The server is not available.')
|
|
||||||
return
|
newTaskRequest.reqBody = Object.assign({}, req, reqDiff, {
|
||||||
}
|
use_cpu: useCPUField.checked
|
||||||
const imageElem = imageItemElem.querySelector('img')
|
})
|
||||||
const newTaskRequest = getCurrentUserRequest()
|
newTaskRequest.seed = newTaskRequest.reqBody.seed
|
||||||
switch (mode) {
|
|
||||||
case 'img2img':
|
return newTaskRequest
|
||||||
case 'img2img_X2':
|
}
|
||||||
newTaskRequest.reqBody = Object.assign({}, reqBody, {
|
|
||||||
num_outputs: 1,
|
function onMakeSimilarClick(req, img) {
|
||||||
use_cpu: useCPUField.checked,
|
const newTaskRequest = modifyCurrentRequest(req, {
|
||||||
})
|
num_outputs: 1,
|
||||||
if (!newTaskRequest.reqBody.init_image || mode === 'img2img_X2') {
|
num_inference_steps: 50,
|
||||||
newTaskRequest.reqBody.sampler = 'ddim'
|
guidance_scale: 7.5,
|
||||||
newTaskRequest.reqBody.prompt_strength = '0.5'
|
prompt_strength: 0.7,
|
||||||
newTaskRequest.reqBody.init_image = imageElem.src
|
init_image: img.src,
|
||||||
delete newTaskRequest.reqBody.mask
|
seed: Math.floor(Math.random() * 10000000)
|
||||||
} else {
|
})
|
||||||
newTaskRequest.reqBody.seed = 1 + newTaskRequest.reqBody.seed
|
|
||||||
}
|
newTaskRequest.numOutputsTotal = 5
|
||||||
if (mode === 'img2img_X2') {
|
newTaskRequest.batchCount = 5
|
||||||
newTaskRequest.reqBody.width = reqBody.width * 2
|
|
||||||
newTaskRequest.reqBody.height = reqBody.height * 2
|
delete newTaskRequest.reqBody.mask
|
||||||
newTaskRequest.reqBody.num_inference_steps = Math.min(100, reqBody.num_inference_steps * 2)
|
|
||||||
if (useUpscalingField.checked) {
|
createTask(newTaskRequest)
|
||||||
newTaskRequest.reqBody.use_upscale = upscaleModelField.value
|
}
|
||||||
} else {
|
|
||||||
delete newTaskRequest.reqBody.use_upscale
|
function enqueueImageVariationTask(req, img, reqDiff) {
|
||||||
}
|
const imageSeed = img.getAttribute('data-seed')
|
||||||
}
|
|
||||||
break
|
const newTaskRequest = modifyCurrentRequest(req, reqDiff, {
|
||||||
case 'upscale':
|
num_outputs: 1, // this can be user-configurable in the future
|
||||||
newTaskRequest.reqBody = Object.assign({}, reqBody, {
|
seed: imageSeed
|
||||||
num_outputs: 1,
|
})
|
||||||
//use_face_correction: 'GFPGANv1.3',
|
|
||||||
use_upscale: upscaleModelField.value,
|
newTaskRequest.numOutputsTotal = 1 // this can be user-configurable in the future
|
||||||
})
|
newTaskRequest.batchCount = 1
|
||||||
break
|
|
||||||
default:
|
createTask(newTaskRequest)
|
||||||
throw new Error("Unknown upscale mode: " + mode)
|
}
|
||||||
}
|
|
||||||
newTaskRequest.seed = newTaskRequest.reqBody.seed
|
function onUpscaleClick(req, img) {
|
||||||
newTaskRequest.numOutputsTotal = 1
|
enqueueImageVariationTask(req, img, {
|
||||||
newTaskRequest.batchCount = 1
|
use_upscale: upscaleModelField.value
|
||||||
createTask(newTaskRequest)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// makes a single image. don't call this directly, use makeImage() instead
|
||||||
@ -801,7 +752,7 @@ async function checkTasks() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (genSeeds) {
|
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
|
newTask.seed = newTask.reqBody.seed
|
||||||
} else if (newTask.seed !== newTask.reqBody.seed) {
|
} else if (newTask.seed !== newTask.reqBody.seed) {
|
||||||
newTask.seed = newTask.reqBody.seed
|
newTask.seed = newTask.reqBody.seed
|
||||||
@ -998,14 +949,34 @@ function getPrompts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prompts = prompts.split('\n')
|
prompts = prompts.split('\n')
|
||||||
|
prompts = prompts.map(prompt => prompt.trim())
|
||||||
|
prompts = prompts.filter(prompt => prompt !== '')
|
||||||
|
|
||||||
|
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 = []
|
let promptsToMake = []
|
||||||
prompts.forEach(prompt => {
|
prompts.forEach(prompt => {
|
||||||
prompt = prompt.trim()
|
|
||||||
if (prompt === '') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let promptMatrix = prompt.split('|')
|
let promptMatrix = prompt.split('|')
|
||||||
prompt = promptMatrix.shift().trim()
|
prompt = promptMatrix.shift().trim()
|
||||||
promptsToMake.push(prompt)
|
promptsToMake.push(prompt)
|
||||||
@ -1018,11 +989,8 @@ function getPrompts() {
|
|||||||
promptsToMake = promptsToMake.concat(promptPermutations)
|
promptsToMake = promptsToMake.concat(promptPermutations)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (activeTags.length <= 0) {
|
|
||||||
return promptsToMake
|
return promptsToMake
|
||||||
}
|
|
||||||
const promptTags = activeTags.map(x => x.name).join(", ")
|
|
||||||
return promptsToMake.map((prompt) => `${prompt}, ${promptTags}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function permutePrompts(promptBase, promptMatrix) {
|
function permutePrompts(promptBase, promptMatrix) {
|
||||||
@ -1046,28 +1014,6 @@ function permutePrompts(promptBase, promptMatrix) {
|
|||||||
return prompts
|
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
|
// create a file name with embedded prompt and metadata
|
||||||
// for easier cateloging and comparison
|
// for easier cateloging and comparison
|
||||||
function createFileName(prompt, seed, steps, guidance, outputFormat) {
|
function createFileName(prompt, seed, steps, guidance, outputFormat) {
|
||||||
@ -1170,8 +1116,18 @@ outputFormatField.addEventListener('change', handleStringSettingChange(OUTPUT_FO
|
|||||||
outputFormatField.value = getOutputFormat()
|
outputFormatField.value = getOutputFormat()
|
||||||
|
|
||||||
diskPathField.addEventListener('change', handleStringSettingChange(DISK_PATH_KEY))
|
diskPathField.addEventListener('change', handleStringSettingChange(DISK_PATH_KEY))
|
||||||
widthField.addEventListener('change', resizeInpaintingEditor)
|
widthField.addEventListener('change', onDimensionChange)
|
||||||
heightField.addEventListener('change', resizeInpaintingEditor)
|
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) {
|
saveToDiskField.addEventListener('click', function(e) {
|
||||||
diskPathField.disabled = !this.checked
|
diskPathField.disabled = !this.checked
|
||||||
@ -1216,6 +1172,7 @@ function updateGuidanceScaleSlider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
guidanceScaleSlider.value = guidanceScaleField.value * 10
|
guidanceScaleSlider.value = guidanceScaleField.value * 10
|
||||||
|
guidanceScaleSlider.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
guidanceScaleSlider.addEventListener('input', updateGuidanceScale)
|
guidanceScaleSlider.addEventListener('input', updateGuidanceScale)
|
||||||
@ -1234,6 +1191,7 @@ function updatePromptStrengthSlider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
promptStrengthSlider.value = promptStrengthField.value * 100
|
promptStrengthSlider.value = promptStrengthField.value * 100
|
||||||
|
promptStrengthSlider.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
promptStrengthSlider.addEventListener('input', updatePromptStrength)
|
promptStrengthSlider.addEventListener('input', updatePromptStrength)
|
||||||
@ -1380,7 +1338,7 @@ initImageClearBtn.addEventListener('click', function() {
|
|||||||
|
|
||||||
maskSetting.addEventListener('click', function() {
|
maskSetting.addEventListener('click', function() {
|
||||||
inpaintingEditorContainer.style.display = (this.checked ? 'block' : 'none')
|
inpaintingEditorContainer.style.display = (this.checked ? 'block' : 'none')
|
||||||
resizeInpaintingEditor()
|
onDimensionChange()
|
||||||
})
|
})
|
||||||
|
|
||||||
promptsFromFileBtn.addEventListener('click', function() {
|
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() {
|
async function getDiskPath() {
|
||||||
try {
|
try {
|
||||||
let diskPath = getSavedDiskPath()
|
let diskPath = getSavedDiskPath()
|
||||||
@ -1569,246 +1383,4 @@ async function getDiskPath() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createModifierCard(name, previews) {
|
createCollapsibles()
|
||||||
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);
|
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -206,6 +206,7 @@ def get_base_path(disk_path, session_id, prompt, img_id, ext, suffix=None):
|
|||||||
os.makedirs(session_out_path, exist_ok=True)
|
os.makedirs(session_out_path, exist_ok=True)
|
||||||
|
|
||||||
prompt_flattened = filename_regex.sub('_', prompt)[:50]
|
prompt_flattened = filename_regex.sub('_', prompt)[:50]
|
||||||
|
|
||||||
|
|
||||||
if suffix is not None:
|
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}_{suffix}.{ext}")
|
||||||
@ -505,7 +506,7 @@ def do_mk_img(req: Request):
|
|||||||
if (len(filters_applied) > 0):
|
if (len(filters_applied) > 0):
|
||||||
filtered_image = Image.fromarray(x_sample)
|
filtered_image = Image.fromarray(x_sample)
|
||||||
filtered_img_data = img_to_base64_str(filtered_image, req.output_format)
|
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)
|
res.images.append(response_image)
|
||||||
if req.save_to_disk_path is not None:
|
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))
|
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'))
|
CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, '..', 'scripts'))
|
||||||
MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'models'))
|
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
|
OUTPUT_DIRNAME = "Stable Diffusion UI" # in the user's home folder
|
||||||
TASK_TTL = 15 * 60 # Discard last session's task timeout
|
TASK_TTL = 15 * 60 # Discard last session's task timeout
|
||||||
@ -31,11 +32,15 @@ app = FastAPI()
|
|||||||
modifiers_cache = None
|
modifiers_cache = None
|
||||||
outpath = os.path.join(os.path.expanduser("~"), OUTPUT_DIRNAME)
|
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
|
# don't show access log entries for URLs that start with the given prefix
|
||||||
ACCESS_LOG_SUPPRESS_PATH_PREFIXES = ['/ping', '/image', '/modifier-thumbnails']
|
ACCESS_LOG_SUPPRESS_PATH_PREFIXES = ['/ping', '/image', '/modifier-thumbnails']
|
||||||
|
|
||||||
NOCACHE_HEADERS={"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"}
|
NOCACHE_HEADERS={"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"}
|
||||||
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):
|
class SetAppConfigRequest(BaseModel):
|
||||||
update_branch: str = "main"
|
update_branch: str = "main"
|
||||||
@ -251,6 +256,15 @@ def getModels():
|
|||||||
|
|
||||||
return models
|
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}')
|
@app.get('/get/{key:path}')
|
||||||
def read_web_data(key:str=None):
|
def read_web_data(key:str=None):
|
||||||
if not key: # /get without parameters, stable-diffusion easter egg.
|
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)
|
return JSONResponse(getModels(), headers=NOCACHE_HEADERS)
|
||||||
elif key == 'modifiers': return FileResponse(os.path.join(SD_UI_DIR, 'modifiers.json'), 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 == 'output_dir': return JSONResponse({ 'output_dir': outpath }, headers=NOCACHE_HEADERS)
|
||||||
|
elif key == 'ui_plugins': return JSONResponse(getUIPlugins(), headers=NOCACHE_HEADERS)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=404, detail=f'Request for unknown {key}') # HTTP404 Not Found
|
raise HTTPException(status_code=404, detail=f'Request for unknown {key}') # HTTP404 Not Found
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user