<!DOCTYPE html> <html> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" type="image/png" href="/media/favicon-16x16.png" sizes="16x16"> <link rel="icon" type="image/png" href="/media/favicon-32x32.png" sizes="32x32"> <style> body { font-family: Arial, Helvetica, sans-serif; font-size: 11pt; background-color: rgb(32, 33, 36); color: #eee; } a { color: rgb(0, 102, 204); } a:visited { color: rgb(0, 102, 204); } label { font-size: 10pt; } #prompt { width: 100%; height: 50pt; } @media screen and (max-width: 600px) { #prompt { width: 95%; } } .image_preview_container { /* display: none; */ margin-top: 10pt; } .image_clear_btn { position: absolute; transform: translateX(-50%) translateY(-35%); background: black; color: white; border: 2pt solid #ccc; padding: 0; cursor: pointer; outline: inherit; border-radius: 8pt; width: 16pt; height: 16pt; font-family: Verdana; font-size: 8pt; } #editor-settings-entries { font-size: 9pt; margin-bottom: 5px; padding-left: 10px; list-style-type: none; } #editor-settings-entries li { padding-bottom: 3pt; } .editor-slider { transform: translateY(30%); } #outputMsg { font-size: small; } #progressBar { font-size: small; } #footer { 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: 30pt; margin-left: -5pt; } .imgSaveBtn { position: absolute; transform: translateX(-100%); 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); } #stopImage { flex: 0 0 70px; background: rgb(132, 8, 0); border: 2px solid rgb(122, 29, 0); color: rgb(255, 221, 255); width: 100%; height: 30pt; border-radius: 6px; display: none; } #stopImage:hover { background: rgb(214, 32, 0); } .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: 8pt; height: 8pt; border-radius: 4pt; 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; font-size: 10pt; } #preview-prompt { font-size: 16pt; margin-bottom: 10pt; } #coffeeButton { height: 23px; transform: translateY(25%); } #inpaintingEditor { width: 300pt; height: 300pt; margin-top: 5pt; } .drawing-board-canvas-wrapper { background-size: 100% 100%; } #inpaintingEditor canvas { opacity: 0.6; } #enable_mask { margin-top: 8pt; } </style> <link rel="stylesheet" href="/media/drawingboard.min.css"> <script src="/media/jquery-3.6.1.min.js"></script> <script src="/media/drawingboard.min.js"></script> </html> <body> <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"> </div> <span id="server-status-msg">Stable Diffusion is starting..</span> </div> <h1>Stable Diffusion UI <small>v2.15 <span id="updateBranchLabel"></span></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> <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" /><br/> <div id="init_image_preview_container" class="image_preview_container"> <img id="init_image_preview" src="" width="100" height="100" /> <button class="init_image_clear image_clear_btn">X</button> <br/> <input id="enable_mask" name="enable_mask" type="checkbox"> <label for="enable_mask">In-Painting (select the area which the AI will paint into)</label> <div id="inpaintingEditor"></div> </div> </div> <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> <button id="makeImage">Make Image</button> <button id="stopImage">Stop</button> </div> <div class="line-separator"> </div> <div id="editor-settings" class="panel-box"> <h4 class="collapsible">Advanced Settings</h4> <ul id="editor-settings-entries" class="collapsible-content"> <li><input id="stream_image_progress" name="stream_image_progress" type="checkbox"> <label for="stream_image_progress">Show a live preview of the image (consumes more VRAM, slightly slower image generation)</label></li> <li><input id="use_face_correction" name="use_face_correction" type="checkbox" checked> <label for="use_face_correction">Fix incorrect faces and eyes (uses GFPGAN)</label></li> <li> <input id="use_upscale" name="use_upscale" type="checkbox"> <label for="use_upscale">Upscale the image to 4x resolution using </label> <select id="upscale_model" name="upscale_model"> <option value="RealESRGAN_x4plus" selected>RealESRGAN_x4plus</option> <option value="RealESRGAN_x4plus_anime_6B">RealESRGAN_x4plus_anime_6B</option> </select> </li> <li><input id="show_only_filtered_image" name="show_only_filtered_image" type="checkbox" checked> <label for="show_only_filtered_image">Show only the corrected/upscaled image</label></li> <br/> <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 images to make:</label> <input id="num_outputs_total" name="num_outputs_total" value="1" size="4"> <label for="num_outputs_parallel">Generate in parallel:</label> <input id="num_outputs_parallel" name="num_outputs_parallel" value="1" size="4"> (images at once)</li> <li id="samplerSelection"><label for="sampler">Sampler:</label> <select id="sampler" name="sampler"> <option value="plms" selected>plms</option> <option value="ddim">ddim</option> <option value="heun">heun</option> <option value="euler">euler</option> <option value="euler_a">euler_a</option> <option value="dpm2">dpm2</option> <option value="dpm2_a">dpm2_a</option> <option value="lms">lms</option> </select> </li> <li><label for="width">Width:</label> <select id="width" name="width" value="512"> <option value="128">128 (*)</option> <option value="192">192</option> <option value="256">256 (*)</option> <option value="320">320</option> <option value="384">384</option> <option value="448">448</option> <option value="512" selected>512 (*)</option> <option value="576">576</option> <option value="640">640</option> <option value="704">704</option> <option value="768">768 (*)</option> <option value="832">832</option> <option value="896">896</option> <option value="960">960</option> <option value="1024">1024 (*)</option> <option value="1280">1280</option> <option value="1536">1536</option> <option value="1792">1792</option> <option value="2048">2048</option> </select> </li> <li><label for="height">Height:</label> <select id="height" name="height" value="512"> <option value="128">128 (*)</option> <option value="192">192</option> <option value="256">256 (*)</option> <option value="320">320</option> <option value="384">384</option> <option value="448">448</option> <option value="512" selected>512 (*)</option> <option value="576">576</option> <option value="640">640</option> <option value="704">704</option> <option value="768">768 (*)</option> <option value="832">832</option> <option value="896">896</option> <option value="960">960</option> <option value="1024">1024 (*)</option> <option value="1280">1280</option> <option value="1536">1536</option> <option value="1792">1792</option> <option value="2048">2048</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_slider">Guidance Scale:</label> <input id="guidance_scale_slider" name="guidance_scale_slider" class="editor-slider" value="75" type="range" min="10" max="200"> <input id="guidance_scale" name="guidance_scale" size="4"></li> <li><span id="prompt_strength_container"><label for="prompt_strength_slider">Prompt Strength:</label> <input id="prompt_strength_slider" name="prompt_strength_slider" class="editor-slider" value="80" type="range" min="0" max="99"> <input id="prompt_strength" name="prompt_strength" size="4"><br/></span></li> <li> </li> <li><input id="save_to_disk" name="save_to_disk" type="checkbox"> <label for="save_to_disk">Automatically save to <input id="diskPath" name="diskPath" size="40" disabled></label></li> <li><input id="sound_toggle" name="sound_toggle" type="checkbox" checked> <label for="sound_toggle">Play sound on task completion</label></li> <li><input id="turbo" name="turbo" type="checkbox" checked> <label for="turbo">Turbo mode (generates images faster, but uses an additional 1 GB of GPU memory)</label></li> <li><input id="use_cpu" name="use_cpu" type="checkbox"> <label for="use_cpu">Use CPU instead of GPU (warning: this will be *very* slow)</label></li> <li><input id="use_full_precision" name="use_full_precision" type="checkbox"> <label for="use_full_precision">Use full precision (for GPU-only. warning: this will consume more VRAM)</label></li> <!-- <li><input id="allow_nsfw" name="allow_nsfw" type="checkbox"> <label for="allow_nsfw">Allow NSFW Content (You confirm you are above 18 years of age)</label></li> --> <br/> <li><input id="use_beta_channel" name="use_beta_channel" type="checkbox"> <label for="use_beta_channel">🔥Beta channel. Get the latest features immediately (but could be less stable). Please restart the program after changing this.</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 set an "Initial Image" if you want to guide the AI.<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="progressBar"></div> <div id="current-images" class="img-preview"> </div> </div> </div> <div class="line-separator"> </div> <div id="footer" class="panel-box"> <p>If you found this project useful and want to help keep it alive, please <a href="https://ko-fi.com/cmdr2_stablediffusion_ui" target="_blank"><img src="media/kofi.png" id="coffeeButton"></a> to help cover the cost of development and maintenance! Thank you for your support!</p> <p>Please feel free to join the <a href="https://discord.com/invite/u9yhsFmEkB" target="_blank">discord community</a> or <a href="https://github.com/cmdr2/stable-diffusion-ui/issues" target="_blank">file an issue</a> if you have any problems or suggestions in using this interface.</p> <div id="footer-legal"> <p><b>Disclaimer:</b> The authors of this project are not responsible for any content generated using this interface.</p> <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> const SOUND_ENABLED_KEY = "soundEnabled" const SAVE_TO_DISK_KEY = "saveToDisk" const USE_CPU_KEY = "useCPU" const USE_FULL_PRECISION_KEY = "useFullPrecision" const USE_TURBO_MODE_KEY = "useTurboMode" const DISK_PATH_KEY = "diskPath" const ADVANCED_PANEL_OPEN_KEY = "advancedPanelOpen" const MODIFIERS_PANEL_OPEN_KEY = "modifiersPanelOpen" const USE_FACE_CORRECTION_KEY = "useFaceCorrection" const USE_UPSCALING_KEY = "useUpscaling" const SHOW_ONLY_FILTERED_IMAGE_KEY = "showOnlyFilteredImage" const STREAM_IMAGE_PROGRESS_KEY = "streamImageProgress" const HEALTH_PING_INTERVAL = 5 // seconds const MAX_INIT_IMAGE_DIMENSION = 768 const IMAGE_REGEX = new RegExp('data:image/[A-Za-z]+;base64') let sessionId = new Date().getTime() let promptField = document.querySelector('#prompt') let numOutputsTotalField = document.querySelector('#num_outputs_total') let numOutputsParallelField = document.querySelector('#num_outputs_parallel') let numInferenceStepsField = document.querySelector('#num_inference_steps') let guidanceScaleSlider = document.querySelector('#guidance_scale_slider') let guidanceScaleField = document.querySelector('#guidance_scale') let randomSeedField = document.querySelector("#random_seed") let seedField = document.querySelector('#seed') let widthField = document.querySelector('#width') let heightField = document.querySelector('#height') let initImageSelector = document.querySelector("#init_image") let initImagePreview = document.querySelector("#init_image_preview") let maskImageSelector = document.querySelector("#mask") let maskImagePreview = document.querySelector("#mask_preview") let turboField = document.querySelector('#turbo') let useCPUField = document.querySelector('#use_cpu') let useFullPrecisionField = document.querySelector('#use_full_precision') let saveToDiskField = document.querySelector('#save_to_disk') let diskPathField = document.querySelector('#diskPath') // let allowNSFWField = document.querySelector("#allow_nsfw") let useBetaChannelField = document.querySelector("#use_beta_channel") let promptStrengthSlider = document.querySelector('#prompt_strength_slider') let promptStrengthField = document.querySelector('#prompt_strength') let samplerField = document.querySelector('#sampler') let samplerSelectionContainer = document.querySelector("#samplerSelection") let useFaceCorrectionField = document.querySelector("#use_face_correction") let useUpscalingField = document.querySelector("#use_upscale") let upscaleModelField = document.querySelector("#upscale_model") let showOnlyFilteredImageField = document.querySelector("#show_only_filtered_image") let updateBranchLabel = document.querySelector("#updateBranchLabel") let streamImageProgressField = document.querySelector("#stream_image_progress") let makeImageBtn = document.querySelector('#makeImage') let stopImageBtn = document.querySelector('#stopImage') 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') // let maskSetting = document.querySelector('#editor-inputs-mask_setting') // let maskImagePreviewContainer = document.querySelector('#mask_preview_container') // let maskImageClearBtn = document.querySelector('#mask_clear') let maskSetting = document.querySelector('#enable_mask') 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 outputMsg = document.querySelector('#outputMsg') let progressBar = document.querySelector("#progressBar") let soundToggle = document.querySelector('#sound_toggle') let serverStatusColor = document.querySelector('#server-status-color') let serverStatusMsg = document.querySelector('#server-status-msg') let advancedPanelHandle = document.querySelector("#editor-settings .collapsible") let modifiersPanelHandle = document.querySelector("#editor-modifiers .collapsible") let inpaintingEditorContainer = document.querySelector('#inpaintingEditor') let inpaintingEditor = new DrawingBoard.Board('inpaintingEditor', { color: "#ffffff", background: false, size: 30, webStorage: false, controls: [{'DrawingMode': {'filler': false}}, 'Size', 'Navigation'] }) let inpaintingEditorCanvasBackground = document.querySelector('.drawing-board-canvas-wrapper') // let inpaintingEditorControls = document.querySelector('.drawing-board-controls') // let inpaintingEditorMetaControl = document.createElement('div') // inpaintingEditorMetaControl.className = 'drawing-board-control' // let initImageClearBtnToolbar = document.createElement('button') // initImageClearBtnToolbar.className = 'init_image_clear' // initImageClearBtnToolbar.innerHTML = 'Remove Image' // inpaintingEditorMetaControl.appendChild(initImageClearBtnToolbar) // inpaintingEditorControls.appendChild(inpaintingEditorMetaControl) let maskResetButton = document.querySelector('.drawing-board-control-navigation-reset') maskResetButton.innerHTML = 'Clear' maskResetButton.style.fontWeight = 'normal' maskResetButton.style.fontSize = '10pt' let serverStatus = 'offline' let activeTags = [] let lastPromptUsed = '' let taskStopped = true let batchesDone = 0 function getLocalStorageItem(key, fallback) { let item = localStorage.getItem(key) if (item === null) { return fallback } return item } function getLocalStorageBoolItem(key, fallback) { let item = localStorage.getItem(key) if (item === null) { return fallback } return (item === 'true' ? true : false) } function handleBoolSettingChange(key) { return function(e) { localStorage.setItem(key, e.target.checked.toString()) } } function handleStringSettingChange(key) { return function(e) { localStorage.setItem(key, e.target.value.toString()) } } function isSoundEnabled() { return getLocalStorageBoolItem(SOUND_ENABLED_KEY, true) } function isFaceCorrectionEnabled() { return getLocalStorageBoolItem(USE_FACE_CORRECTION_KEY, false) } function isUpscalingEnabled() { return getLocalStorageBoolItem(USE_UPSCALING_KEY, false) } function isShowOnlyFilteredImageEnabled() { return getLocalStorageBoolItem(SHOW_ONLY_FILTERED_IMAGE_KEY, true) } function isSaveToDiskEnabled() { return getLocalStorageBoolItem(SAVE_TO_DISK_KEY, false) } function isUseCPUEnabled() { return getLocalStorageBoolItem(USE_CPU_KEY, false) } function isUseFullPrecisionEnabled() { return getLocalStorageBoolItem(USE_FULL_PRECISION_KEY, false) } function isUseTurboModeEnabled() { return getLocalStorageBoolItem(USE_TURBO_MODE_KEY, true) } function getSavedDiskPath() { return getLocalStorageItem(DISK_PATH_KEY, '') } function isAdvancedPanelOpenEnabled() { return getLocalStorageBoolItem(ADVANCED_PANEL_OPEN_KEY, false) } function isModifiersPanelOpenEnabled() { return getLocalStorageBoolItem(MODIFIERS_PANEL_OPEN_KEY, false) } function isStreamImageProgressEnabled() { return getLocalStorageBoolItem(STREAM_IMAGE_PROGRESS_KEY, false) } function setStatus(statusType, msg, msgType) { if (statusType !== 'server') { return; } if (msgType == 'error') { // msg = '<span style="color: red">' + msg + '<span>' serverStatusColor.style.backgroundColor = 'red' serverStatusMsg.style.color = 'red' serverStatusMsg.innerHTML = 'Stable Diffusion has stopped' } else if (msgType == 'success') { // msg = '<span style="color: green">' + msg + '<span>' serverStatusColor.style.backgroundColor = 'green' serverStatusMsg.style.color = 'green' serverStatusMsg.innerHTML = 'Stable Diffusion is ready' serverStatus = 'online' } } function logMsg(msg, level) { if (level === 'error') { outputMsg.innerHTML = '<span style="color: red">Error: ' + msg + '</span>' } else if (level === 'warn') { outputMsg.innerHTML = '<span style="color: orange">Warning: ' + msg + '</span>' } else { outputMsg.innerHTML = msg } console.log(level, msg) } function logError(msg, res) { logMsg(msg, 'error') console.log('request error', res) setStatus('request', 'error', 'error') } function playSound() { const audio = new Audio('/media/ding.mp3') audio.volume = 0.2 audio.play() } async function healthCheck() { try { let res = await fetch('/ping') res = await res.json() if (res[0] == 'OK') { setStatus('server', 'online', 'success') } else { setStatus('server', 'offline', 'error') } } catch (e) { setStatus('server', 'offline', 'error') } } function makeImageElement(width, height) { let imgItem = document.createElement('div') imgItem.className = 'imgItem' let img = document.createElement('img') img.width = parseInt(width) img.height = parseInt(height) imgItem.appendChild(img) imagesContainer.appendChild(imgItem) return imgItem } // makes a single image. don't call this directly, use makeImage() instead async function doMakeImage(reqBody, batchCount) { if (taskStopped) { return } let res = '' let seed = reqBody['seed'] let numOutputs = parseInt(reqBody['num_outputs']) let images = [] function makeImageContainers(numImages) { for (let i = images.length; i < numImages; i++) { images.push(makeImageElement(reqBody.width, reqBody.height)) } } try { res = await fetch('/image', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(reqBody) }) let reader = res.body.getReader() let textDecoder = new TextDecoder() let finalJSON = '' let prevTime = -1 while (true) { try { let t = new Date().getTime() const {value, done} = await reader.read() if (done) { break } let timeTaken = (prevTime === -1 ? -1 : t - prevTime) let jsonStr = textDecoder.decode(value) try { let stepUpdate = JSON.parse(jsonStr) if (stepUpdate.step === undefined) { finalJSON += jsonStr } else { let batchSize = parseInt(reqBody['num_inference_steps']) let overallStepCount = stepUpdate.step + batchesDone * batchSize let totalSteps = batchCount * batchSize let percent = 100 * (overallStepCount / totalSteps) percent = (percent > 100 ? 100 : percent) percent = percent.toFixed(0) stepsRemaining = totalSteps - overallStepCount stepsRemaining = (stepsRemaining < 0 ? 0 : stepsRemaining) timeRemaining = (timeTaken === -1 ? '' : stepsRemaining * timeTaken) // ms outputMsg.innerHTML = `Batch ${batchesDone+1} of ${batchCount}` progressBar.innerHTML = `Generating image(s): ${percent}%` if (timeTaken !== -1) { progressBar.innerHTML += `<br>Time remaining (approx): ${millisecondsToStr(timeRemaining)}` } progressBar.style.display = 'block' if (stepUpdate.output !== undefined) { makeImageContainers(numOutputs) for (idx in stepUpdate.output) { let imgItem = images[idx] let img = imgItem.firstChild let tmpImageData = stepUpdate.output[idx] img.src = tmpImageData['path'] + '?t=' + new Date().getTime() } } } } catch (e) { finalJSON += jsonStr } prevTime = t } catch (e) { logError('Stable Diffusion had an error. Please check the logs in the command-line window.', res) res = undefined throw e } } if (res.status != 200) { if (serverStatus === 'online') { logError('Stable Diffusion had an error: ' + await res.text(), res) } else { logError("Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.", res) } res = undefined progressBar.style.display = 'none' } else { res = JSON.parse(finalJSON) progressBar.style.display = 'none' if (res.status !== 'succeeded') { let msg = '' if (res.detail !== undefined) { msg = res.detail if (msg.toLowerCase().includes('out of memory')) { msg += `<br/><br/> <b>Suggestions</b>: <br/> 1. If you have set an initial image, please try reducing its dimension to ${MAX_INIT_IMAGE_DIMENSION}x${MAX_INIT_IMAGE_DIMENSION} or smaller.<br/> 2. Try disabling the '<em>Turbo mode</em>' under '<em>Advanced Settings</em>'.<br/> 3. Try generating a smaller image.<br/>` } } else { msg = res } logError(msg, res) res = undefined } } } catch (e) { console.log('request error', e) logError('Stable Diffusion had an error. Please check the logs in the command-line window. <br/><br/>' + e + '<br/><pre>' + e.stack + '</pre>', res) setStatus('request', 'error', 'error') progressBar.style.display = 'none' res = undefined } if (!res) { return false } lastPromptUsed = reqBody['prompt'] makeImageContainers(res.output.length) for (let idx in res.output) { let imgBody = '' let seed = 0 try { let imgData = res.output[idx] imgBody = imgData.data seed = imgData.seed } catch (e) { console.log(imgBody) setStatus('request', 'invalid image', 'error') continue } let imgItem = images[idx] let img = imgItem.firstChild 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' let imgSaveBtn = document.createElement('button') imgSaveBtn.className = 'imgSaveBtn' imgSaveBtn.innerHTML = 'Download' imgItem.appendChild(imgItemInfo) imgItemInfo.appendChild(imgSeedLabel) imgItemInfo.appendChild(imgUseBtn) imgItemInfo.appendChild(imgSaveBtn) imgUseBtn.addEventListener('click', function() { initImageSelector.value = null initImagePreview.src = imgBody initImagePreviewContainer.style.display = 'block' inpaintingEditorContainer.style.display = 'none' promptStrengthContainer.style.display = 'block' maskSetting.checked = false // maskSetting.style.display = 'block' randomSeedField.checked = false seedField.value = seed seedField.disabled = false }) imgSaveBtn.addEventListener('click', function() { let imgDownload = document.createElement('a') imgDownload.download = createFileName(); imgDownload.href = imgBody imgDownload.click() }) imgItem.addEventListener('mouseenter', function() { imgItemInfo.style.opacity = 1 }) imgItem.addEventListener('mouseleave', function() { imgItemInfo.style.opacity = 0.5 }) } return true } function validateInput() { let width = parseInt(widthField.value) let height = parseInt(heightField.value) if (IMAGE_REGEX.test(initImagePreview.src)) { if (initImagePreview.naturalWidth > MAX_INIT_IMAGE_DIMENSION || initImagePreview.naturalHeight > MAX_INIT_IMAGE_DIMENSION) { return {'isValid': false, 'warning': `The dimensions of your initial image are very large, and can cause 'Out of Memory' errors! Please ensure that its dimensions are equal (or smaller) than the desired output image. <br/><br/> Your initial image size is ${initImagePreview.naturalWidth}x${initImagePreview.naturalHeight} pixels. Please try to keep it smaller than ${MAX_INIT_IMAGE_DIMENSION}x${MAX_INIT_IMAGE_DIMENSION}.`} } } return {'isValid': true} } async function makeImage() { if (serverStatus !== 'online') { logError('The server is still starting up..') return } let validation = validateInput() if (validation['isValid']) { outputMsg.innerHTML = 'Starting..' } else { if (validation['error']) { logError(validation['error']) return } else if (validation['warning']) { logMsg(validation['warning'], 'warn') } } setStatus('request', 'fetching..') makeImageBtn.innerHTML = 'Processing..' makeImageBtn.disabled = true makeImageBtn.style.display = 'none' stopImageBtn.style.display = 'block' taskStopped = false batchesDone = 0 let seed = (randomSeedField.checked ? Math.floor(Math.random() * 10000000) : parseInt(seedField.value)) let numOutputsTotal = parseInt(numOutputsTotalField.value) let numOutputsParallel = parseInt(numOutputsParallelField.value) let batchCount = Math.ceil(numOutputsTotal / numOutputsParallel) let batchSize = numOutputsParallel let streamImageProgress = (numOutputsTotal > 50 ? false : streamImageProgressField.checked) let prompt = promptField.value if (activeTags.length > 0) { let promptTags = activeTags.join(", ") prompt += ", " + promptTags } previewPrompt.innerHTML = prompt let reqBody = { session_id: sessionId, prompt: prompt, num_outputs: batchSize, num_inference_steps: numInferenceStepsField.value, guidance_scale: guidanceScaleField.value, width: widthField.value, height: heightField.value, // allow_nsfw: allowNSFWField.checked, turbo: turboField.checked, use_cpu: useCPUField.checked, use_full_precision: useFullPrecisionField.checked, stream_progress_updates: true, stream_image_progress: streamImageProgress, show_only_filtered_image: showOnlyFilteredImageField.checked } if (IMAGE_REGEX.test(initImagePreview.src)) { reqBody['init_image'] = initImagePreview.src reqBody['prompt_strength'] = promptStrengthField.value // if (IMAGE_REGEX.test(maskImagePreview.src)) { // reqBody['mask'] = maskImagePreview.src // } if (maskSetting.checked) { reqBody['mask'] = inpaintingEditor.getImg() } reqBody['sampler'] = 'ddim' } else { reqBody['sampler'] = samplerField.value } if (saveToDiskField.checked && diskPathField.value.trim() !== '') { reqBody['save_to_disk_path'] = diskPathField.value.trim() } if (useFaceCorrectionField.checked) { reqBody['use_face_correction'] = 'GFPGANv1.3' } if (useUpscalingField.checked) { reqBody['use_upscale'] = upscaleModelField.value } let time = new Date().getTime() imagesContainer.innerHTML = '' let successCount = 0 for (let i = 0; i < batchCount; i++) { reqBody['seed'] = seed + (i * batchSize) let success = await doMakeImage(reqBody, batchCount) batchesDone++ if (success) { outputMsg.innerHTML = 'Processed batch ' + (i+1) + '/' + batchCount successCount++ } } makeImageBtn.innerHTML = 'Make Image' makeImageBtn.disabled = false makeImageBtn.style.display = 'block' stopImageBtn.style.display = 'none' 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 } } // create a file name with embedded prompt and metadata // for easier cateloging and comparison function createFileName() { // Most important information is the prompt let underscoreName = lastPromptUsed.replace(/[^a-zA-Z0-9]/g, '_') underscoreName = underscoreName.substring(0, 100) const seed = seedField.value const steps = numInferenceStepsField.value const guidance = guidanceScaleField.value // name and the top level metadata let fileName = `${underscoreName}_Seed-${seed}_Steps-${steps}_Guidance-${guidance}` // add the tags // let tags = []; // let tagString = ''; // document.querySelectorAll(modifyTagsSelector).forEach(function(tag) { // tags.push(tag.innerHTML); // }) // join the tags with a pipe // if (activeTags.length > 0) { // tagString = '_Tags-'; // tagString += tags.join('|'); // } // // append empty or populated tags // fileName += `${tagString}`; // add the file extension fileName += `.png` return fileName } stopImageBtn.addEventListener('click', async function() { try { let res = await fetch('/image/stop') } catch (e) { console.log(e) } stopImageBtn.style.display = 'none' makeImageBtn.style.display = 'block' taskStopped = true }) soundToggle.addEventListener('click', handleBoolSettingChange(SOUND_ENABLED_KEY)) soundToggle.checked = isSoundEnabled() saveToDiskField.checked = isSaveToDiskEnabled() diskPathField.disabled = !saveToDiskField.checked useFaceCorrectionField.addEventListener('click', handleBoolSettingChange(USE_FACE_CORRECTION_KEY)) useFaceCorrectionField.checked = isFaceCorrectionEnabled() useUpscalingField.checked = isUpscalingEnabled() upscaleModelField.disabled = !useUpscalingField.checked showOnlyFilteredImageField.addEventListener('click', handleBoolSettingChange(SHOW_ONLY_FILTERED_IMAGE_KEY)) showOnlyFilteredImageField.checked = isShowOnlyFilteredImageEnabled() useCPUField.addEventListener('click', handleBoolSettingChange(USE_CPU_KEY)) useCPUField.checked = isUseCPUEnabled() useFullPrecisionField.addEventListener('click', handleBoolSettingChange(USE_FULL_PRECISION_KEY)) useFullPrecisionField.checked = isUseFullPrecisionEnabled() turboField.addEventListener('click', handleBoolSettingChange(USE_TURBO_MODE_KEY)) turboField.checked = isUseTurboModeEnabled() streamImageProgressField.addEventListener('click', handleBoolSettingChange(STREAM_IMAGE_PROGRESS_KEY)) streamImageProgressField.checked = isStreamImageProgressEnabled() diskPathField.addEventListener('change', handleStringSettingChange(DISK_PATH_KEY)) saveToDiskField.addEventListener('click', function(e) { diskPathField.disabled = !this.checked handleBoolSettingChange(SAVE_TO_DISK_KEY)(e) }) useUpscalingField.addEventListener('click', function(e) { upscaleModelField.disabled = !this.checked handleBoolSettingChange(USE_UPSCALING_KEY)(e) }) function setPanelOpen(panelHandle) { let panelContents = panelHandle.nextElementSibling panelHandle.classList.add('active') panelContents.style.display = 'block' } if (isAdvancedPanelOpenEnabled()) { setPanelOpen(advancedPanelHandle) } if (isModifiersPanelOpenEnabled()) { setPanelOpen(modifiersPanelHandle) } makeImageBtn.addEventListener('click', makeImage) function updateGuidanceScale() { guidanceScaleField.value = guidanceScaleSlider.value / 10 } function updateGuidanceScaleSlider() { if (guidanceScaleField.value < 0) { guidanceScaleField.value = 0 } else if (guidanceScaleField.value > 20) { guidanceScaleField.value = 20 } guidanceScaleSlider.value = guidanceScaleField.value * 10 } guidanceScaleSlider.addEventListener('input', updateGuidanceScale) guidanceScaleField.addEventListener('input', updateGuidanceScaleSlider) updateGuidanceScale() function updatePromptStrength() { promptStrengthField.value = promptStrengthSlider.value / 100 } function updatePromptStrengthSlider() { if (promptStrengthField.value < 0) { promptStrengthField.value = 0 } else if (promptStrengthField.value > 0.99) { promptStrengthField.value = 0.99 } promptStrengthSlider.value = promptStrengthField.value * 100 } promptStrengthSlider.addEventListener('input', updatePromptStrength) promptStrengthField.addEventListener('input', updatePromptStrengthSlider) updatePromptStrength() useBetaChannelField.addEventListener('click', async function(e) { if (serverStatus !== 'online') { logError('The server is still starting up..') alert('The server is still starting up..') e.preventDefault() return false } let updateBranch = (this.checked ? 'beta' : 'main') try { let res = await fetch('/app_config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ 'update_branch': updateBranch }) }) res = await res.json() console.log('set config status response', res) } catch (e) { console.log('set config status error', e) } }) async function getAppConfig() { try { let res = await fetch('/app_config') config = await res.json() if (config.update_branch === 'beta') { useBetaChannelField.checked = true updateBranchLabel.innerHTML = "(beta)" } console.log('get config status response', config) } catch (e) { console.log('get config status error', e) } } function checkRandomSeed() { if (randomSeedField.checked) { seedField.disabled = true seedField.value = "0" } else { seedField.disabled = false } } randomSeedField.addEventListener('input', checkRandomSeed) checkRandomSeed() function showInitImagePreview() { if (initImageSelector.files.length === 0) { initImagePreviewContainer.style.display = 'none' // inpaintingEditorContainer.style.display = 'none' promptStrengthContainer.style.display = 'none' // maskSetting.style.display = 'none' return } let reader = new FileReader() let file = initImageSelector.files[0] reader.addEventListener('load', function() { // console.log(file.name, reader.result) initImagePreview.src = reader.result initImagePreviewContainer.style.display = 'block' inpaintingEditorContainer.style.display = 'none' promptStrengthContainer.style.display = 'block' samplerSelectionContainer.style.display = 'none' // maskSetting.checked = false }) if (file) { reader.readAsDataURL(file) } } initImageSelector.addEventListener('change', showInitImagePreview) showInitImagePreview() initImagePreview.addEventListener('load', function() { inpaintingEditorCanvasBackground.style.backgroundImage = "url('" + this.src + "')" // maskSetting.style.display = 'block' // inpaintingEditorContainer.style.display = 'block' }) initImageClearBtn.addEventListener('click', function() { initImageSelector.value = null // maskImageSelector.value = null initImagePreview.src = '' // maskImagePreview.src = '' maskSetting.checked = false initImagePreviewContainer.style.display = 'none' // inpaintingEditorContainer.style.display = 'none' // maskImagePreviewContainer.style.display = 'none' // maskSetting.style.display = 'none' promptStrengthContainer.style.display = 'none' samplerSelectionContainer.style.display = 'block' }) maskSetting.addEventListener('click', function() { inpaintingEditorContainer.style.display = (this.checked ? 'block' : 'none') }) // 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; } </script> <script> 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 = this.nextElementSibling 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) } }) }) } 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 getDiskPath() { try { let diskPath = getSavedDiskPath() if (diskPath !== '') { diskPathField.value = diskPath return } let res = await fetch('/output_dir') if (res.status === 200) { res = await res.json() res = res[0] document.querySelector('#diskPath').value = res } } catch (e) { console.log('error fetching output dir path', e) } } 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() await getDiskPath() await getAppConfig() setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000) healthCheck() playSound() } init() </script> </html>