Compare commits

...

21 Commits
v1.22 ... v1.25

Author SHA1 Message Date
cc14ac0bac Merge branch 'main' of github.com:cmdr2/stable-diffusion-ui 2022-09-01 12:44:01 +05:30
2e0d7fdbb8 Discord link for dev 2022-09-01 12:43:56 +05:30
2284eea2d8 Update README.md 2022-09-01 12:13:48 +05:30
867b5b2ee4 Update README.md 2022-09-01 12:01:56 +05:30
85e29fffc9 Update README.md 2022-09-01 11:59:46 +05:30
fa84d812f1 Update config screenshot 2022-08-31 12:25:03 +05:30
4ffa8420dd New UI Screenshot with image modifier tags 2022-08-31 12:18:19 +05:30
7dc1c54578 New UI; A library of image modifier tags like 'realistic', 'artstation', 'pencil sketch' etc 2022-08-31 12:16:25 +05:30
05434d3575 Update README.md 2022-08-31 08:06:02 +05:30
ddea9b9f38 Update README.md 2022-08-30 22:00:52 +05:30
9445ee41cf Update README.md 2022-08-30 22:00:41 +05:30
8325b4e5aa Beta notice for v2 2022-08-30 22:00:01 +05:30
1ff9db3714 Use port 9000 2022-08-29 09:29:08 +05:30
12ff102a21 Merge pull request #24 from ChrisAcrobat/pr-1
Update docker-compose.yml
2022-08-28 21:47:25 +05:30
f660111751 Merge pull request #19 from dsmmcken/dsmmcken-patch-1
Allow other image file types
2022-08-27 22:15:14 +05:30
Don
bac08306fb Allow other image file types
underlying library accepts a wide range of filetypes, no need to restrict it to just png. I wanted to use a jpg.
2022-08-27 11:55:00 -04:00
b860dbd9a6 Revert to using docker-compose commands, since ./server wasn't working reliably on some platforms 2022-08-27 14:14:24 +05:30
c7713f559d Update docker-compose.yml 2022-08-27 10:34:29 +02:00
7c60189f29 Reduce opacity of the labels over the image until the mouse moves over it 2022-08-27 11:21:10 +05:30
35dc13ffcf Update screenshot for the new num_outputs config 2022-08-27 10:55:59 +05:30
6b55f385c7 Allow generating an arbitrary number of output images, with parallel batches of 1 or 4 depending on the GPU's capabilities 2022-08-27 10:53:46 +05:30
11 changed files with 630 additions and 180 deletions

View File

@ -1,9 +1,9 @@
# Stable Diffusion UI
### A simple way to install and use [Stable Diffusion](https://replicate.com/stability-ai/stable-diffusion) on your own computer
---
[![Discord Server](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.com/invite/u9yhsFmEkB) (for support, and development discussion)
🎉 **New!** `img2img` and `inpaint` (masking) are now supported! You can provide an image to generate new images based on it (and an optional text prompt). You can also use the generated image as the new input image in 1-click, to refine it further. (Thanks [Andreas](https://github.com/andreasjansson)!)
---
# What does this do?
Two things:
@ -12,10 +12,7 @@ Two things:
All the processing will happen on your computer locally, it does not transmit your prompts or process on any remote server.
<p float="left">
<img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/shot-v3a.jpg" height="500" />
<img src="https://github.com/cmdr2/stable-diffusion-ui/raw/main/media/shot-v6a.jpg" height="500" />
</p>
![Screenshot](media/shot-v8.jpg?raw=true)
# System Requirements
@ -27,15 +24,15 @@ All the processing will happen on your computer locally, it does not transmit yo
# Installation
1. Clone this repository: `git clone https://github.com/cmdr2/stable-diffusion-ui.git` or [download the zip file](https://github.com/cmdr2/stable-diffusion-ui/archive/refs/heads/main.zip) and unzip.
2. Open your terminal, and in the project directory run: `./server` (warning: this will take some time during the first run, since it'll download Stable Diffusion's [docker image](https://replicate.com/stability-ai/stable-diffusion), nearly 17 GiB)
2. Open your terminal, and in the project directory run: `docker-compose up &` (warning: this will take some time during the first run, since it'll download Stable Diffusion's [docker image](https://replicate.com/stability-ai/stable-diffusion), nearly 17 GiB)
3. Open http://localhost:9000 in your browser. That's it!
If you're getting errors, please check the [Troubleshooting](https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting) page.
To stop the server, please run `./server stop`
To stop the server, please run `docker-compose down`
# Usage
Open http://localhost:9000 in your browser (after running `./server` from step 2 previously).
Open http://localhost:9000 in your browser (after running `docker-compose up &` from step 2 previously).
## With a text description
1. Enter a text prompt, like `a photograph of an astronaut riding a horse` in the textbox.
@ -61,7 +58,7 @@ You can also set the configuration like `seed`, `width`, `height`, `num_outputs`
Use the same `seed` number to get the same image for a certain prompt. This is useful for refining a prompt without losing the basic image design. Enable the `random images` checkbox to get random images.
![Screenshot of advanced settings](media/config-v2.jpg?raw=true)
![Screenshot of advanced settings](media/config-v4.jpg?raw=true)
# Troubleshooting
The [Troubleshooting wiki page](https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting) contains some common errors and their solutions. Please check that, and if it doesn't work, feel free to [file an issue](https://github.com/cmdr2/stable-diffusion-ui/issues).
@ -74,7 +71,7 @@ This project runs Stable Diffusion in a docker container behind the scenes, usin
# Bugs reports and code contributions welcome
If there are any problems or suggestions, please feel free to [file an issue](https://github.com/cmdr2/stable-diffusion-ui/issues).
Also, please feel free to submit a pull request, if you have any code contributions in mind.
Also, please feel free to submit a pull request, if you have any code contributions in mind. Join the [discord server](https://discord.com/invite/u9yhsFmEkB) for development-related discussions, and for helping other users.
# Disclaimer
The authors of this project are not responsible for any content generated using this interface.

View File

@ -24,15 +24,5 @@ services:
depends_on:
- stability-ai
stable-diffusion-old-port-redirect:
container_name: sd-old-port-redirect
ports:
- '8000:8000'
build:
context: .
dockerfile: OldPortDockerfile
volumes:
- .:/app
networks:
default:
default:

View File

@ -5,6 +5,8 @@
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 11pt;
background-color: rgb(32, 33, 36);
color: #eee;
}
a {
color: rgb(0, 102, 204);
@ -12,17 +14,11 @@
a:visited {
color: rgb(0, 102, 204);
}
@media (prefers-color-scheme: dark) {
body {
background-color: rgb(32, 33, 36);
color: #eee;
}
}
label {
font-size: 10pt;
}
#prompt {
width: 50vw;
width: 100%;
height: 50pt;
}
@media screen and (max-width: 600px) {
@ -32,10 +28,11 @@
}
.image_preview_container {
display: none;
margin-top: 10pt;
}
.image_clear_btn {
position: absolute;
transform: translateX(-50%);
transform: translateX(-50%) translateY(-35%);
background: black;
color: white;
border: 2pt solid #ccc;
@ -45,91 +42,305 @@
border-radius: 8pt;
width: 16pt;
height: 16pt;
font-size: 10pt;
font-family: Verdana;
font-size: 8pt;
}
#configHeader {
margin-top: 5px;
margin-bottom: 5px;
font-size: 10pt;
}
#config {
#editor-settings-entries {
font-size: 9pt;
margin-bottom: 5px;
padding-left: 10px;
list-style-type: none;
}
#editor-settings-entries li {
padding-bottom: 3pt;
}
#guidance_scale {
transform: translateY(30%);
}
#outputMsg {
font-size: small;
}
#footer {
border-top: 1px solid #999;
margin-top: 10px;
padding-top: 10px;
font-size: small;
padding-left: 10pt;
background: none;
}
#footer-legal {
font-size: 8pt;
}
.imgSeedLabel {
position: absolute;
transform: translateX(-100%);
margin-top: 5pt;
margin-left: -5pt;
font-size: 10pt;
background-color: #333;
opacity: 0.8;
color: #ddd;
border-radius: 3pt;
padding: 1pt 3pt;
}
.imgUseBtn {
position: absolute;
transform: translateX(-100%);
margin-top: 5pt;
margin-top: 30pt;
margin-left: -5pt;
}
.imgSaveBtn {
position: absolute;
transform: translateX(-100%);
margin-top: 30pt;
margin-top: 55pt;
margin-left: -5pt;
}
.imgItem {
display: inline;
padding-right: 10px;
}
.imgItemInfo {
opacity: 0.5;
}
#container {
width: 75%;
margin-left: auto;
margin-right: auto;
}
@media screen and (max-width: 1400px) {
#container {
width: 100%;
}
}
#meta small {
font-size: 11pt;
}
#editor {
padding: 5px;
}
#editor label {
font-weight: bold;
}
#preview {
padding: 5px;
}
#editor-inputs {
margin-bottom: 20px;
}
#editor-inputs-prompt {
flex: 1;
}
#editor-inputs .row {
padding-bottom: 10px;
}
#makeImage {
border-radius: 6px;
}
#editor-modifiers h5 {
padding: 5pt 0;
margin: 0;
}
#makeImage {
flex: 0 0 70px;
background: rgb(80, 0, 185);
border: 2px solid rgb(40, 0, 78);
color: rgb(255, 221, 255);
width: 100%;
height: 30pt;
}
#makeImage:hover {
background: rgb(93, 0, 214);
}
.flex-container {
display: flex;
}
.col-50 {
flex: 50%;
}
.col-free {
flex: 1;
}
.collapsible {
cursor: pointer;
}
.collapsible-content {
display: none;
padding-left: 15px;
}
.collapsible-content h5 {
padding: 5pt 0pt;
margin: 0;
font-size: 10pt;
}
.collapsible-handle {
color: white;
padding-right: 5px;
}
.panel-box {
background: rgb(44, 45, 48);
border: 1px solid rgb(47, 49, 53);
border-radius: 7px;
padding: 5px;
margin-bottom: 15px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15);
}
.panel-box h4 {
margin: 0;
padding: 2px 0;
}
.prompt-modifier-tag {
border: 1px solid rgb(10, 0, 24);
border-radius: 4px;
padding: 0pt 3pt;
margin-right: 2pt;
cursor: pointer;
display: inline;
background: rgb(163, 163, 163);
color: black;
line-height: 25pt;
float: left;
font-size: 9pt;
}
.prompt-modifier-tag:hover {
background: black;
color: white;
}
#editor-modifiers-entries .prompt-modifier-tag {
background: #110f0f;
color: rgb(212, 212, 212);
margin-bottom: 4pt;
font-size: 10pt;
}
#editor-modifiers-entries .prompt-modifier-tag:hover {
background: rgb(163, 163, 163);
color: black;
}
#editor-modifiers .editor-modifiers-leaf {
padding-top: 10pt;
padding-bottom: 10pt;
}
#preview {
margin-left: 20pt;
}
img {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15);
}
.line-separator {
background: rgb(56, 56, 56);
height: 1pt;
margin: 15pt 0;
}
#editor-inputs-tags-container {
margin-top: 5pt;
display: none;
}
#server-status {
float: right;
}
#server-status-color {
width: 10pt;
height: 10pt;
border-radius: 5pt;
background-color: rgb(128, 87, 0);
/* background-color: rgb(197, 1, 1); */
float: left;
transform: translateY(15%);
}
#server-status-msg {
color: rgb(128, 87, 0);
padding-left: 2pt;
}
#preview-prompt {
font-size: 16pt;
margin-bottom: 10pt;
}
</style>
</html>
<body>
<div id="status">Server status: <span id="serverStatus">checking..</span> | Request status: <span id="reqStatus">n/a</span></div>
<div id="container">
<div class="flex-container">
<div id="editor" class="col-50">
<div id="meta">
<div id="server-status">
<div id="server-status-color">&nbsp;</div>
<span id="server-status-msg">server starting..</span>
</div>
<h1>Stable Diffusion UI <small>v1</small></h1>
</div>
<div id="editor-inputs">
<div id="editor-inputs-prompt" class="row">
<label for="prompt">Prompt</label>
<textarea id="prompt" class="col-free">a photograph of an astronaut riding a horse</textarea>
</div>
<br/>
<div id="editor-inputs-init-image" class="row">
<label for="init_image"><b>Initial Image:</b> (optional) </label> <input id="init_image" name="init_image" type="file" /> </button><br/>
<div id="init_image_preview_container" class="image_preview_container">
<img id="init_image_preview" src="" width="100" height="100" />
<button id="init_image_clear" class="image_clear_btn">X</button>
</div>
<b>Prompt:</b><br/>
<textarea id="prompt">a photograph of an astronaut riding a horse</textarea><br/>
<div id="mask_setting">
<label for="mask"><b>Image Mask:</b> (optional) </label> <input id="mask" name="mask" type="file" /> </button><br/>
<div id="mask_preview_container" class="image_preview_container">
<img id="mask_preview" src="" width="100" height="100" />
<button id="mask_clear" class="image_clear_btn">X</button>
</div>
</div>
</div>
<label for="init_image"><b>Initial Image:</b> (optional) </label> <input id="init_image" name="init_image" type="file" /> </button><br/>
<div id="init_image_preview_container" class="image_preview_container">
<img id="init_image_preview" src="" width="100" height="100" />
<button id="init_image_clear" class="image_clear_btn">X</button>
</div><br/>
<div id="editor-inputs-tags-container" class="row">
<label>Tags: <small>(click a tag to remove it)</small></label>
<div id="editor-inputs-tags-list">
</div>
</div>
<div id="mask_setting">
<label for="mask"><b>Image Mask:</b> (optional) </label> <input id="mask" name="mask" type="file" /> </button><br/>
<div id="mask_preview_container" class="image_preview_container">
<img id="mask_preview" src="" width="100" height="100" />
<button id="mask_clear" class="image_clear_btn">X</button>
<button id="makeImage">Make Image</button>
</div>
<div class="line-separator">&nbsp;</div>
<div id="editor-settings" class="panel-box">
<h4 class="collapsible">Advanced Settings</h4>
<ul id="editor-settings-entries" class="collapsible-content">
<li><label for="seed">Seed:</label> <input id="seed" name="seed" size="10" value="30000"> <input id="random_seed" name="random_seed" type="checkbox" checked> <label for="random_seed">Random Image</label></li>
<li><label for="num_outputs_total">Number of outputs:</label> <input id="num_outputs_total" name="num_outputs_total" value="1" size="4"> <label for="num_outputs_parallel">Generate in parallel:</label> <select id="num_outputs_parallel" name="num_outputs_parallel" value="1"><option value="1" selected>1 image at a time</option><option value="4">4 images at a time</option></select></li>
<li><label for="width">Width:</label> <select id="width" name="width" value="512"><option value="128">128</option><option value="256">256</option><option value="512" selected>512</option><option value="768">768</option><option value="1024">1024</option></select></li>
<li><label for="height">Height:</label> <select id="height" name="height" value="512"><option value="128">128</option><option value="256">256</option><option value="512" selected>512</option><option value="768">768</option></select></li>
<li><label for="num_inference_steps">Number of inference steps:</label> <input id="num_inference_steps" name="num_inference_steps" size="4" value="50"></li>
<li><label for="guidance_scale">Guidance Scale:</label> <input id="guidance_scale" name="guidance_scale" value="75" type="range" min="10" max="200"> <span id="guidance_scale_value"></span></li>
<li><span id="prompt_strength_container"><label for="prompt_strength">Prompt Strength:</label> <input id="prompt_strength" name="prompt_strength" value="8" type="range" min="0" max="10"> <span id="prompt_strength_value"></span><br/></span></li>
<li>&nbsp;</li>
<li><input id="sound_toggle" name="sound_toggle" type="checkbox" checked> <label for="sound_toggle">Play sound on task completion</label></li>
</ul>
</div>
<div id="editor-modifiers" class="panel-box">
<h4 class="collapsible">Image Modifiers (art styles, tags etc)</h4>
<div id="editor-modifiers-entries" class="collapsible-content">
</div>
</div>
</div>
<div id="preview" class="col-50">
<div id="preview-prompt">Type a prompt and press the "Make Image" button.<br/><br/>You can also add modifiers like "Realistic", "Pencil Sketch", "ArtStation" etc by browsing through the "Image Modifiers" section and selecting the desired modifiers.<br/><br/>Click "Advanced Settings" for additional settings like seed, image size, number of images to generate etc.<br/><br/>Enjoy! :)</div>
<div id="outputMsg"></div>
<div id="current-images" class="img-preview">
</div>
</div>
</div>
<div id="configHeader"><b>Advanced settings:</b> [<a id="configToggleBtn" href="#">show</a>]</div>
<div id="config">
<label for="seed">Seed:</label> <input id="seed" name="seed" value="30000"> <input id="random_seed" name="random_seed" type="checkbox" checked> <label for="random_seed">Random Image</label> <br/>
<label for="num_outputs">Number of outputs:</label> <select id="num_outputs" name="num_outputs" value="1"><option value="1" selected>1</option><option value="4">4</option></select><br/>
<label for="width">Width:</label> <select id="width" name="width" value="512"><option value="128">128</option><option value="256">256</option><option value="512" selected>512</option><option value="768">768</option><option value="1024">1024</option></select><br/>
<label for="height">Height:</label> <select id="height" name="height" value="512"><option value="128">128</option><option value="256">256</option><option value="512" selected>512</option><option value="768">768</option></select><br/>
<label for="num_inference_steps">Number of inference steps:</label> <input id="num_inference_steps" name="num_inference_steps" value="50"><br/>
<label for="guidance_scale">Guidance Scale:</label> <input id="guidance_scale" name="guidance_scale" value="75" type="range" min="10" max="200"> <span id="guidance_scale_value"></span><br/>
<span id="prompt_strength_container"><label for="prompt_strength">Prompt Strength:</label> <input id="prompt_strength" name="prompt_strength" value="8" type="range" min="0" max="10"> <span id="prompt_strength_value"></span><br/></span><br/>
<input id="sound_toggle" name="sound_toggle" type="checkbox" checked> <label for="sound_toggle">Play sound on task completion</label><br/>
</div>
<div class="line-separator">&nbsp;</div>
<button id="makeImage">Make Image</button> <br/><br/>
<div id="outputMsg"></div>
<div id="images"></div>
<div id="footer">
<div id="footer" class="panel-box">
<p>Please feel free to <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><b>Disclaimer:</b> The authors of this project are not responsible for any content generated using this interface.</p>
<p>This license of this software forbids you from sharing any content that violates any laws, produce any harm to a person, disseminate any personal information that would be meant for harm, <br/>spread misinformation and target vulnerable groups. For the full list of restrictions please read <a href="https://github.com/cmdr2/stable-diffusion-ui/blob/main/LICENSE" target="_blank">the license</a>.</p>
<p>By using this software, you consent to the terms and conditions of the license.</p>
<div id="footer-legal">
<p><b>Disclaimer:</b> The authors of this project are not responsible for any content generated using this interface.</p>
<p>This license of this software forbids you from sharing any content that violates any laws, produce any harm to a person, disseminate any personal information that would be meant for harm, <br/>spread misinformation and target vulnerable groups. For the full list of restrictions please read <a href="https://github.com/cmdr2/stable-diffusion-ui/blob/main/LICENSE" target="_blank">the license</a>.</p>
<p>By using this software, you consent to the terms and conditions of the license.</p>
</div>
</div>
</div>
</body>
<script>
@ -137,7 +348,8 @@ const SOUND_ENABLED_KEY = "soundEnabled"
const HEALTH_PING_INTERVAL = 5 // seconds
let promptField = document.querySelector('#prompt')
let numOutputsField = document.querySelector('#num_outputs')
let numOutputsTotalField = document.querySelector('#num_outputs_total')
let numOutputsParallelField = document.querySelector('#num_outputs_parallel')
let numInferenceStepsField = document.querySelector('#num_inference_steps')
let guidanceScaleField = document.querySelector('#guidance_scale')
let guidanceScaleValueLabel = document.querySelector('#guidance_scale_value')
@ -154,7 +366,7 @@ let promptStrengthValueLabel = document.querySelector('#prompt_strength_value')
let makeImageBtn = document.querySelector('#makeImage')
let imagesContainer = document.querySelector('#images')
let imagesContainer = document.querySelector('#current-images')
let initImagePreviewContainer = document.querySelector('#init_image_preview_container')
let initImageClearBtn = document.querySelector('#init_image_clear')
let promptStrengthContainer = document.querySelector('#prompt_strength_container')
@ -163,13 +375,23 @@ let maskSetting = document.querySelector('#mask_setting')
let maskImagePreviewContainer = document.querySelector('#mask_preview_container')
let maskImageClearBtn = document.querySelector('#mask_clear')
let editorModifierEntries = document.querySelector('#editor-modifiers-entries')
let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list')
let editorTagsContainer = document.querySelector('#editor-inputs-tags-container')
let previewPrompt = document.querySelector('#preview-prompt')
let showConfigToggle = document.querySelector('#configToggleBtn')
let configBox = document.querySelector('#config')
// let configBox = document.querySelector('#config')
let outputMsg = document.querySelector('#outputMsg')
let soundToggle = document.querySelector('#sound_toggle')
let serverStatusColor = document.querySelector('#server-status-color')
let serverStatusMsg = document.querySelector('#server-status-msg')
let serverStatus = 'offline'
let activeTags = []
function isSoundEnabled() {
if (localStorage.getItem(SOUND_ENABLED_KEY) === 'false') {
@ -179,24 +401,28 @@ function isSoundEnabled() {
}
function setStatus(statusType, msg, msgType) {
let el = ''
if (statusType === 'server') {
el = '#serverStatus'
serverStatus = msg
} else if (statusType === 'request') {
el = '#reqStatus'
if (statusType !== 'server') {
return;
}
if (msgType == 'error') {
msg = '<span style="color: red">' + msg + '<span>'
// msg = '<span style="color: red">' + msg + '<span>'
serverStatusColor.style.backgroundColor = 'red'
serverStatusMsg.style.color = 'red'
serverStatusMsg.innerHTML = 'server offline'
} else if (msgType == 'success') {
msg = '<span style="color: green">' + msg + '<span>'
// msg = '<span style="color: green">' + msg + '<span>'
serverStatusColor.style.backgroundColor = 'green'
serverStatusMsg.style.color = 'green'
serverStatusMsg.innerHTML = 'server online'
serverStatus = 'online'
}
}
if (el) {
document.querySelector(el).innerHTML = msg
}
function logError(msg, res) {
outputMsg.innerHTML = '<span style="color: red">Error: ' + msg + '</span>'
console.log('request error', res)
setStatus('request', 'error', 'error')
}
function playSound() {
@ -220,43 +446,10 @@ async function healthCheck() {
}
}
async function makeImage() {
setStatus('request', 'fetching..')
makeImageBtn.innerHTML = 'Processing..'
makeImageBtn.disabled = true
outputMsg.innerHTML = 'Fetching..'
function logError(msg, res) {
outputMsg.innerHTML = '<span style="color: red">Error: ' + msg + '</span>'
console.log('request error', res)
setStatus('request', 'error', 'error')
}
let seed = (randomSeedField.checked ? Math.floor(Math.random() * 10000) : seedField.value)
let reqBody = {
prompt: promptField.value,
num_outputs: numOutputsField.value,
num_inference_steps: numInferenceStepsField.value,
guidance_scale: guidanceScaleField.value / 10,
width: widthField.value,
height: heightField.value,
seed: seed,
}
if (initImagePreview.src.indexOf('data:image/png;base64') !== -1) {
reqBody['init_image'] = initImagePreview.src
reqBody['prompt_strength'] = promptStrengthField.value / 10
if (maskImagePreview.src.indexOf('data:image/png;base64') !== -1) {
reqBody['mask'] = maskImagePreview.src
}
}
// makes a single image. don't call this directly, use makeImage() instead
async function doMakeImage(reqBody) {
let res = ''
let time = new Date().getTime()
let seed = reqBody['seed']
try {
res = await fetch('/image', {
@ -293,24 +486,10 @@ async function makeImage() {
setStatus('request', 'error', 'error')
}
makeImageBtn.innerHTML = 'Make Image'
makeImageBtn.disabled = false
if (isSoundEnabled()) {
playSound()
}
if (!res) {
return
return false
}
time = new Date().getTime() - time
time /= 1000
outputMsg.innerHTML = 'Processed in ' + time + ' seconds. Seed: ' + seed
imagesContainer.innerHTML = ''
for (let idx in res.output) {
let imgBody = ''
@ -319,7 +498,7 @@ async function makeImage() {
} catch (e) {
console.log(imgBody)
setStatus('request', 'invalid image', 'error')
return
continue
}
let imgItem = document.createElement('div')
@ -330,6 +509,13 @@ async function makeImage() {
img.height = parseInt(reqBody.height)
img.src = imgBody
let imgItemInfo = document.createElement('span')
imgItemInfo.className = 'imgItemInfo'
let imgSeedLabel = document.createElement('span')
imgSeedLabel.className = 'imgSeedLabel'
imgSeedLabel.innerHTML = 'Seed: ' + seed
let imgUseBtn = document.createElement('button')
imgUseBtn.className = 'imgUseBtn'
imgUseBtn.innerHTML = 'Use as Input'
@ -339,8 +525,10 @@ async function makeImage() {
imgSaveBtn.innerHTML = 'Download'
imgItem.appendChild(img)
imgItem.appendChild(imgUseBtn)
imgItem.appendChild(imgSaveBtn)
imgItem.appendChild(imgItemInfo)
imgItemInfo.appendChild(imgSeedLabel)
imgItemInfo.appendChild(imgUseBtn)
imgItemInfo.appendChild(imgSaveBtn)
imagesContainer.appendChild(imgItem)
imgUseBtn.addEventListener('click', function() {
@ -363,9 +551,96 @@ async function makeImage() {
imgDownload.href = imgBody
imgDownload.click()
})
imgItem.addEventListener('mouseenter', function() {
imgItemInfo.style.opacity = 1
})
imgItem.addEventListener('mouseleave', function() {
imgItemInfo.style.opacity = 0.5
})
}
setStatus('request', 'done', 'success')
return true
}
async function makeImage() {
if (serverStatus !== 'online') {
logError('The server is still starting up..')
return
}
setStatus('request', 'fetching..')
makeImageBtn.innerHTML = 'Processing..'
makeImageBtn.disabled = true
outputMsg.innerHTML = 'Fetching..'
const imageRegex = new RegExp('data:image/[A-Za-z]+;base64')
let seed = (randomSeedField.checked ? Math.floor(Math.random() * 10000) : parseInt(seedField.value))
let numOutputsTotal = parseInt(numOutputsTotalField.value)
let numOutputsParallel = parseInt(numOutputsParallelField.value)
let batchCount = Math.ceil(numOutputsTotal / numOutputsParallel)
let batchSize = numOutputsParallel
let prompt = promptField.value
if (activeTags.length > 0) {
let promptTags = activeTags.join(", ")
prompt += ", " + promptTags
}
previewPrompt.innerHTML = prompt
let reqBody = {
prompt: prompt,
num_outputs: batchSize,
num_inference_steps: numInferenceStepsField.value,
guidance_scale: parseInt(guidanceScaleField.value) / 10,
width: widthField.value,
height: heightField.value
}
if (imageRegex.test(initImagePreview.src)) {
reqBody['init_image'] = initImagePreview.src
reqBody['prompt_strength'] = parseInt(promptStrengthField.value) / 10
if (imageRegex.test(maskImagePreview.src)) {
reqBody['mask'] = maskImagePreview.src
}
}
let time = new Date().getTime()
imagesContainer.innerHTML = ''
let successCount = 0
for (let i = 0; i < batchCount; i++) {
reqBody['seed'] = seed + i
let success = await doMakeImage(reqBody)
if (success) {
outputMsg.innerHTML = 'Processed batch ' + (i+1) + '/' + batchCount
successCount++
}
}
makeImageBtn.innerHTML = 'Make Image'
makeImageBtn.disabled = false
if (isSoundEnabled()) {
playSound()
}
time = new Date().getTime() - time
time /= 1000
if (successCount === batchCount) {
outputMsg.innerHTML = 'Processed ' + numOutputsTotal + ' images in ' + time + ' seconds'
setStatus('request', 'done', 'success')
}
if (randomSeedField.checked) {
seedField.value = seed
@ -397,13 +672,13 @@ soundToggle.checked = isSoundEnabled();
makeImageBtn.addEventListener('click', makeImage)
configBox.style.display = 'none'
// configBox.style.display = 'none'
showConfigToggle.addEventListener('click', function() {
configBox.style.display = (configBox.style.display === 'none' ? 'block' : 'none')
showConfigToggle.innerHTML = (configBox.style.display === 'none' ? 'show' : 'hide')
return false
})
// showConfigToggle.addEventListener('click', function() {
// configBox.style.display = (configBox.style.display === 'none' ? 'block' : 'none')
// showConfigToggle.innerHTML = (configBox.style.display === 'none' ? 'show' : 'hide')
// return false
// })
function updateGuidanceScale() {
guidanceScaleValueLabel.innerHTML = guidanceScaleField.value / 10
@ -498,8 +773,125 @@ maskImageClearBtn.addEventListener('click', function() {
maskImagePreview.src = ''
maskImagePreviewContainer.style.display = 'none'
})
</script>
<script>
function createCollapsibles(node) {
if (!node) {
node = document
}
setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000)
let collapsibles = node.querySelectorAll(".collapsible")
collapsibles.forEach(function(c) {
let handle = document.createElement('span')
handle.className = 'collapsible-handle'
handle.innerHTML = '&#x2795;'
c.insertBefore(handle, c.firstChild)
c.addEventListener('click', function() {
this.classList.toggle("active")
let content = this.nextElementSibling
if (content.style.display === "block") {
content.style.display = "none"
handle.innerHTML = '&#x2795;' // plus
} else {
content.style.display = "block"
handle.innerHTML = '&#x2796;' // minus
}
})
})
}
createCollapsibles()
function refreshTagsList() {
editorModifierTagsList.innerHTML = ''
if (activeTags.length == 0) {
editorTagsContainer.style.display = 'none'
return
} else {
editorTagsContainer.style.display = 'block'
}
activeTags.forEach(function(tag) {
let el = document.createElement('div')
el.className = 'prompt-modifier-tag'
el.innerHTML = tag
editorModifierTagsList.appendChild(el)
el.addEventListener('click', function() {
let idx = activeTags.indexOf(tag)
if (idx !== -1) {
activeTags.splice(idx, 1)
refreshTagsList()
}
})
})
let brk = document.createElement('br')
brk.style.clear = 'both'
editorModifierTagsList.appendChild(brk)
}
async function loadModifiers() {
try {
let res = await fetch('/modifiers.json')
if (res.status === 200) {
res = await res.json()
res.forEach(function(m) {
let title = m[0]
let modifiers = m[1]
let titleEl = document.createElement('h5')
titleEl.className = 'collapsible'
titleEl.innerHTML = title
let modifiersEl = document.createElement('div')
modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf')
modifiers.forEach(function(modifier) {
let tagEl = document.createElement('div')
tagEl.className = 'prompt-modifier-tag'
tagEl.innerHTML = modifier
modifiersEl.appendChild(tagEl)
tagEl.addEventListener('click', function() {
if (activeTags.includes(modifier)) {
return
}
activeTags.push(modifier)
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)
}
}
async function init() {
await loadModifiers()
setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000)
healthCheck()
}
init()
</script>
</html>

View File

@ -67,3 +67,7 @@ async def image(req : ImageRequest):
@app.get('/media/ding.mp3')
def read_root():
return FileResponse('media/ding.mp3')
@app.get('/modifiers.json')
def read_modifiers():
return FileResponse('modifiers.json')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
media/config-v3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
media/config-v4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
media/shot-v8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

92
modifiers.json Normal file
View File

@ -0,0 +1,92 @@
[
["Drawing Style", [
"Sketch",
"Doodle",
"Children's Drawing",
"Line Art",
"Dot Art",
"Crosshatch",
"Detailed and Intricate",
"Cel Shading"
]],
["Visual Style", [
"2D",
"Cartoon",
"8-bit",
"16-bit",
"Graphic Novel",
"Visual Novel",
"Street Art",
"Fantasy",
"Realistic",
"Photo",
"Hard Edge Painting",
"Mural",
"Mosaic",
"Hydrodipped",
"Modern Art",
"Concept Art",
"Digital Art",
"CGI",
"Anaglyph",
"Comic Book",
"Lithography"
]],
["Pen", [
"Graphite",
"Colored Pencil",
"Ink",
"Chalk",
"Pastel Art",
"Oil Paint"
]],
["Carving and Etching", [
"Etching",
"Wood-Carving",
"Papercutting",
"Paper-Mache",
"Paper Model",
"Linocut",
"Pyrography"
]],
["Camera", [
"HD",
"Color Grading",
"Film Grain",
"White Balance",
"Golden Hour",
"Glamor Shot",
"War Photography",
"Lens Flare",
"Polaroid",
"Vintage"
]],
["Color", [
"Colorful",
"Electric Colors",
"Warm Color Palette",
"Infrared",
"Beautiful Lighting"
]],
["Emotions", [
"Happy",
"Excited",
"Sad",
"Lonely",
"Angry",
"Good",
"Evil"
]],
["Style of an artist or community", [
"by Andy Warhol",
"Artstation",
"by Asaf Hanuka",
"by Aubrey Beardsley",
"by H.R. Giger",
"by Hayao Mizaki",
"by Salvador Dali",
"by Tivadar Csontváry Kosztka",
"by Lisa Frank",
"by Pablo Piccaso"
]]
]

View File

@ -30,8 +30,6 @@ def read_root():
</style>
<h4>The UI has moved to <a href="http://localhost:9000">http://localhost:9000</a>. The current address that you used (ending with :8000) will be removed in the future, so please use <a href="http://localhost:9000">http://localhost:9000</a> going ahead (and in any bookmarks you've saved).</h4>
<h4>Also, please use <pre>./server</pre> instead of <pre>docker-compose up &amp;</pre>. To stop, please use <pre>./server stop</pre>. This will help the project better manage the startup process in the future.</h4>
<h3>Why has the address changed?</h3>
<p>The previously used port (8000) is often used by other servers, which results in port conflicts. So the project's port number has been changed, while the project is still young. Otherwise port-conflicts with 8000 will be a common source of new-user issues in the future.</p>
<p>Sorry about this, and apologies for the inconvenience :)</p>

25
server
View File

@ -1,26 +1,3 @@
#!/bin/bash
CMD="$1"
if [ -z "$1" ]; then
CMD="start"
fi
start_server() {
docker-compose up -d stable-diffusion-old-port-redirect # old port 8000 server, show redirect notice
docker-compose up stability-ai stable-diffusion-ui
}
stop_server() {
docker-compose down
}
if [ "$CMD" == "start" ]; then
start_server
elif [ "$CMD" == "stop" ]; then
stop_server
elif [ "$CMD" == "restart" ]; then
stop_server
start_server
else
echo "Unknown option: $1 (Expected start or stop)"
fi
echo "Please use 'docker-compose up &' to start the server, and 'docker-compose down' to stop the server"