<!DOCTYPE html> <html> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <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; } #guidance_scale { transform: translateY(30%); } #outputMsg { 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); } .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; } </style> </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.05 (beta)</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" /> </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> </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> </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><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><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> </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> </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> </li> <li><input id="save_to_disk" name="save_to_disk" type="checkbox"> <label for="save_to_disk">Automatically save to disk <span id="diskPath"></span></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. Use this for NVIDIA 1650 and 1660)</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> --> </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="current-images" class="img-preview"> </div> </div> </div> <div class="line-separator"> </div> <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> <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 USE_CPU_KEY = "useCPU" const USE_FULL_PRECISION_KEY = "useFullPrecision" const USE_TURBO_MODE_KEY = "useTurboMode" const HEALTH_PING_INTERVAL = 5 // seconds 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 guidanceScaleField = document.querySelector('#guidance_scale') let guidanceScaleValueLabel = document.querySelector('#guidance_scale_value') 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 allowNSFWField = document.querySelector("#allow_nsfw") let promptStrengthField = document.querySelector('#prompt_strength') let promptStrengthValueLabel = document.querySelector('#prompt_strength_value') let makeImageBtn = document.querySelector('#makeImage') 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('#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 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 = [] let lastPromptUsed = '' 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 isSoundEnabled() { return getLocalStorageBoolItem(SOUND_ENABLED_KEY, true) } 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 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 logError(msg, res) { outputMsg.innerHTML = '<span style="color: red">Error: ' + msg + '</span>' 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') } } // makes a single image. don't call this directly, use makeImage() instead async function doMakeImage(reqBody) { let res = '' let seed = reqBody['seed'] try { res = await fetch('/image', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(reqBody) }) if (res.status != 200) { if (serverStatus === 'online') { logError('Stable Diffusion had an error: ' + await res.text() + '. This happens sometimes. Maybe modify the prompt or seed a little bit?', res) } else { logError("Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed.", res) } res = undefined } else { res = await res.json() if (res.status !== 'succeeded') { let msg = '' if (res.detail !== undefined) { msg = res.detail } else { msg = res } logError(msg, res) res = undefined } } } catch (e) { console.log('request error', e) setStatus('request', 'error', 'error') } if (!res) { return false } lastPromptUsed = reqBody['prompt'] for (let idx in res.output) { let imgBody = '' try { let imgData = res.output[idx] imgBody = imgData.data } catch (e) { console.log(imgBody) setStatus('request', 'invalid image', 'error') continue } let imgItem = document.createElement('div') imgItem.className = 'imgItem' let img = document.createElement('img') img.width = parseInt(reqBody.width) 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' let imgSaveBtn = document.createElement('button') imgSaveBtn.className = 'imgSaveBtn' imgSaveBtn.innerHTML = 'Download' imgItem.appendChild(img) imgItem.appendChild(imgItemInfo) imgItemInfo.appendChild(imgSeedLabel) imgItemInfo.appendChild(imgUseBtn) imgItemInfo.appendChild(imgSaveBtn) imagesContainer.appendChild(imgItem) imgUseBtn.addEventListener('click', function() { initImageSelector.value = null initImagePreview.src = imgBody initImagePreviewContainer.style.display = 'block' promptStrengthContainer.style.display = 'block' // 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 } 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, // allow_nsfw: allowNSFWField.checked, save_to_disk: saveToDiskField.checked, turbo: turboField.checked, use_cpu: useCPUField.checked, use_full_precision: useFullPrecisionField.checked } 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 } } // create a file name with embedded prompt and metadata // for easier cateloging and comparison function createFileName() { // Most important information is the prompt const underscoreName = lastPromptUsed.replace(/[^a-zA-Z0-9]/g, '_'); const seed = seedField.value; const steps = numInferenceStepsField.value; const guidance = guidanceScaleField.value; // name and the top level metadata let fileName = `sd_${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; } soundToggle.addEventListener('click', handleBoolSettingChange(SOUND_ENABLED_KEY)) soundToggle.checked = isSoundEnabled() 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() makeImageBtn.addEventListener('click', makeImage) function updateGuidanceScale() { guidanceScaleValueLabel.innerHTML = guidanceScaleField.value / 10 } guidanceScaleField.addEventListener('input', updateGuidanceScale) updateGuidanceScale() function updatePromptStrength() { promptStrengthValueLabel.innerHTML = promptStrengthField.value / 10 } promptStrengthField.addEventListener('input', updatePromptStrength) updatePromptStrength() function checkRandomSeed() { if (randomSeedField.checked) { seedField.disabled = true seedField.value = "random" } else { seedField.disabled = false } } randomSeedField.addEventListener('input', checkRandomSeed) checkRandomSeed() function showInitImagePreview() { if (initImageSelector.files.length === 0) { initImagePreviewContainer.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' promptStrengthContainer.style.display = 'block' // maskSetting.style.display = 'block' }) if (file) { reader.readAsDataURL(file) } } initImageSelector.addEventListener('change', showInitImagePreview) showInitImagePreview() initImageClearBtn.addEventListener('click', function() { initImageSelector.value = null // maskImageSelector.value = null initImagePreview.src = '' // maskImagePreview.src = '' initImagePreviewContainer.style.display = 'none' // maskImagePreviewContainer.style.display = 'none' // maskSetting.style.display = 'none' promptStrengthContainer.style.display = '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' // }) </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' handle.innerHTML = '➕' 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 } }) }) } 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 res = await fetch('/output_dir') if (res.status === 200) { res = await res.json() res = res[0] document.querySelector('#diskPath').innerHTML = '(to ' + 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() setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000) healthCheck() } init() </script> </html>