From f7b513dff2ab92cf6bbf014c56ede22b272f85f3 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 18 Oct 2022 20:18:56 +0530 Subject: [PATCH 01/27] Refactor the CSS and JS into separate files, attempt 1 --- ui/index.html | 24 +-- ui/media/css/auto-save.css | 49 +++++ ui/media/{ => css}/drawingboard.min.css | 0 ui/media/{ => css}/main.css | 218 +-------------------- ui/media/{ => css}/modifier-thumbnails.css | 0 ui/media/css/themes.css | 146 ++++++++++++++ ui/media/{ => images}/favicon-16x16.png | Bin ui/media/{ => images}/favicon-32x32.png | Bin ui/media/{ => images}/kofi.png | Bin ui/media/{ => js}/auto-save.js | 0 ui/media/{ => js}/drawingboard.min.js | 0 ui/media/{ => js}/jquery-3.6.1.min.js | 0 ui/media/{ => js}/main.js | 62 ------ ui/media/js/themes.js | 61 ++++++ 14 files changed, 270 insertions(+), 290 deletions(-) create mode 100644 ui/media/css/auto-save.css rename ui/media/{ => css}/drawingboard.min.css (100%) rename ui/media/{ => css}/main.css (62%) rename ui/media/{ => css}/modifier-thumbnails.css (100%) create mode 100644 ui/media/css/themes.css rename ui/media/{ => images}/favicon-16x16.png (100%) rename ui/media/{ => images}/favicon-32x32.png (100%) rename ui/media/{ => images}/kofi.png (100%) rename ui/media/{ => js}/auto-save.js (100%) rename ui/media/{ => js}/drawingboard.min.js (100%) rename ui/media/{ => js}/jquery-3.6.1.min.js (100%) rename ui/media/{ => js}/main.js (96%) create mode 100644 ui/media/js/themes.js diff --git a/ui/index.html b/ui/index.html index c0dbc2fa..1570aceb 100644 --- a/ui/index.html +++ b/ui/index.html @@ -2,21 +2,22 @@ - - - - + + + + + - + - - + +
+ + diff --git a/ui/media/js/image-modifiers.js b/ui/media/js/image-modifiers.js new file mode 100644 index 00000000..cb051583 --- /dev/null +++ b/ui/media/js/image-modifiers.js @@ -0,0 +1,230 @@ +let activeTags = [] +let modifiers = [] + +let editorModifierEntries = document.querySelector('#editor-modifiers-entries') +let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list') +let editorTagsContainer = document.querySelector('#editor-inputs-tags-container') +let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider') +let previewImageField = document.querySelector('#preview-image') + +const modifierThumbnailPath = 'media/modifier-thumbnails' +const activeCardClass = 'modifier-card-active' + +function createModifierCard(name, previews) { + const modifierCard = document.createElement('div') + modifierCard.className = 'modifier-card' + modifierCard.innerHTML = ` +
+
+
+
+

+ Modifier Image +
+
+

+
` + + const image = modifierCard.querySelector('.modifier-card-image') + const errorText = modifierCard.querySelector('.modifier-card-error-label') + const label = modifierCard.querySelector('.modifier-card-label') + + errorText.innerText = 'No Image' + + if (typeof previews == 'object') { + image.src = previews[0]; // portrait + image.setAttribute('preview-type', 'portrait') + } else { + image.remove() + } + + const maxLabelLength = 30 + const nameWithoutBy = name.replace('by ', '') + + if(nameWithoutBy.length <= maxLabelLength) { + label.querySelector('p').innerText = nameWithoutBy + } else { + const tooltipText = document.createElement('span') + tooltipText.className = 'tooltip-text' + tooltipText.innerText = name + + label.classList.add('tooltip') + label.appendChild(tooltipText) + + label.querySelector('p').innerText = nameWithoutBy.substring(0, maxLabelLength) + '...' + } + + return modifierCard +} + +async function loadModifiers() { + try { + let res = await fetch('/get/modifiers') + if (res.status === 200) { + res = await res.json() + + modifiers = res; // update global variable + + res.forEach((modifierGroup, idx) => { + const title = modifierGroup.category + const modifiers = modifierGroup.modifiers + + const titleEl = document.createElement('h5') + titleEl.className = 'collapsible' + titleEl.innerText = title + + const modifiersEl = document.createElement('div') + modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf') + + if (idx == 0) { + titleEl.className += ' active' + modifiersEl.style.display = 'block' + } + + modifiers.forEach(modObj => { + const modifierName = modObj.modifier + const modifierPreviews = modObj?.previews?.map(preview => `${modifierThumbnailPath}/${preview.path}`) + + const modifierCard = createModifierCard(modifierName, modifierPreviews) + + if(typeof modifierCard == 'object') { + modifiersEl.appendChild(modifierCard) + + modifierCard.addEventListener('click', () => { + if (activeTags.map(x => x.name).includes(modifierName)) { + // remove modifier from active array + activeTags = activeTags.filter(x => x.name != modifierName) + modifierCard.classList.remove(activeCardClass) + + modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' + } else { + // add modifier to active array + activeTags.push({ + 'name': modifierName, + 'element': modifierCard.cloneNode(true), + 'originElement': modifierCard, + 'previews': modifierPreviews + }) + + modifierCard.classList.add(activeCardClass) + + modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-' + } + + refreshTagsList() + }) + } + }) + + let brk = document.createElement('br') + brk.style.clear = 'both' + modifiersEl.appendChild(brk) + + let e = document.createElement('div') + e.appendChild(titleEl) + e.appendChild(modifiersEl) + + editorModifierEntries.appendChild(e) + }) + + createCollapsibles(editorModifierEntries) + } + } catch (e) { + console.log('error fetching modifiers', e) + } +} + +function refreshTagsList() { + editorModifierTagsList.innerHTML = '' + + if (activeTags.length == 0) { + editorTagsContainer.style.display = 'none' + return + } else { + editorTagsContainer.style.display = 'block' + } + + activeTags.forEach((tag, index) => { + tag.element.querySelector('.modifier-card-image-overlay').innerText = '-' + tag.element.classList.add('modifier-card-tiny') + + editorModifierTagsList.appendChild(tag.element) + + tag.element.addEventListener('click', () => { + let idx = activeTags.indexOf(tag) + + if (idx !== -1) { + activeTags[idx].originElement.classList.remove(activeCardClass) + activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+' + + activeTags.splice(idx, 1) + refreshTagsList() + } + }) + }) + + let brk = document.createElement('br') + brk.style.clear = 'both' + editorModifierTagsList.appendChild(brk) +} + +function changePreviewImages(val) { + const previewImages = document.querySelectorAll('.modifier-card-image-container img') + + let previewArr = [] + + modifiers.map(x => x.modifiers).forEach(x => previewArr.push(...x.map(m => m.previews))) + + previewArr = previewArr.map(x => { + let obj = {} + + x.forEach(preview => { + obj[preview.name] = preview.path + }) + + return obj + }) + + previewImages.forEach(previewImage => { + const currentPreviewType = previewImage.getAttribute('preview-type') + const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + '/').pop() + + const previews = previewArr.find(preview => relativePreviewPath == preview[currentPreviewType]) + + if(typeof previews == 'object') { + let preview = null + + if (val == 'portrait') { + preview = previews.portrait + } + else if (val == 'landscape') { + preview = previews.landscape + } + + if(preview != null) { + previewImage.src = `${modifierThumbnailPath}/${preview}` + previewImage.setAttribute('preview-type', val) + } + } + }) +} + +function resizeModifierCards(val) { + const cardSizePrefix = 'modifier-card-size_' + const modifierCardClass = 'modifier-card' + + const modifierCards = document.querySelectorAll(`.${modifierCardClass}`) + const cardSize = n => `${cardSizePrefix}${n}` + + modifierCards.forEach(card => { + // remove existing size classes + const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix)) + card.className = classes.join(' ').trim() + + if(val != 0) { + card.classList.add(cardSize(val)) + } + }) +} + +modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value) +previewImageField.onchange = () => changePreviewImages(previewImageField.value) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 99248468..35c43041 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -79,16 +79,7 @@ let clearAllPreviewsBtn = document.querySelector("#clear-all-previews") let maskSetting = document.querySelector('#enable_mask') let negativePromptPanelHandle = document.querySelector('#negative_prompt_handle') -let editorModifierEntries = document.querySelector('#editor-modifiers-entries') -let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list') -let editorTagsContainer = document.querySelector('#editor-inputs-tags-container') - let imagePreview = document.querySelector("#preview") -let previewImageField = document.querySelector('#preview-image') -previewImageField.onchange = () => changePreviewImages(previewImageField.value) - -let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider') -modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value) // let previewPrompt = document.querySelector('#preview-prompt') @@ -114,17 +105,12 @@ maskResetButton.style.fontWeight = 'normal' maskResetButton.style.fontSize = '10pt' let serverState = {'status': 'Offline', 'time': Date.now()} -let activeTags = [] -let modifiers = [] let lastPromptUsed = '' let bellPending = false let taskQueue = [] let currentTask = null -const modifierThumbnailPath = 'media/modifier-thumbnails' -const activeCardClass = 'modifier-card-active' - function getLocalStorageItem(key, fallback) { let item = localStorage.getItem(key) if (item === null) { @@ -1002,28 +988,6 @@ function permutePrompts(promptBase, promptMatrix) { return prompts } -function permute(arr) { - let permutations = [] - let n = arr.length - let n_permutations = Math.pow(2, n) - for (let i = 0; i < n_permutations; i++) { - let perm = [] - let mask = Number(i).toString(2).padStart(n, '0') - - for (let idx = 0; idx < mask.length; idx++) { - if (mask[idx] === '1' && arr[idx].trim() !== '') { - perm.push(arr[idx]) - } - } - - if (perm.length > 0) { - permutations.push(perm) - } - } - - return permutations -} - // create a file name with embedded prompt and metadata // for easier cateloging and comparison function createFileName(prompt, seed, steps, guidance, outputFormat) { @@ -1370,150 +1334,6 @@ promptsFromFileSelector.addEventListener('change', function() { } }) -// function showMaskImagePreview() { -// if (maskImageSelector.files.length === 0) { -// // maskImagePreviewContainer.style.display = 'none' -// return -// } - -// let reader = new FileReader() -// let file = maskImageSelector.files[0] - -// reader.addEventListener('load', function() { -// // maskImagePreview.src = reader.result -// // maskImagePreviewContainer.style.display = 'block' -// }) - -// if (file) { -// reader.readAsDataURL(file) -// } -// } -// maskImageSelector.addEventListener('change', showMaskImagePreview) -// showMaskImagePreview() - -// maskImageClearBtn.addEventListener('click', function() { -// maskImageSelector.value = null -// maskImagePreview.src = '' -// // maskImagePreviewContainer.style.display = 'none' -// }) - -// https://stackoverflow.com/a/8212878 -function millisecondsToStr(milliseconds) { - function numberEnding (number) { - return (number > 1) ? 's' : '' - } - - var temp = Math.floor(milliseconds / 1000) - var hours = Math.floor((temp %= 86400) / 3600) - var s = '' - if (hours) { - s += hours + ' hour' + numberEnding(hours) + ' ' - } - var minutes = Math.floor((temp %= 3600) / 60) - if (minutes) { - s += minutes + ' minute' + numberEnding(minutes) + ' ' - } - var seconds = temp % 60 - if (!hours && minutes < 4 && seconds) { - s += seconds + ' second' + numberEnding(seconds) - } - - return s -} - -// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/ -function getNextSibling(elem, selector) { - // Get the next sibling element - var sibling = elem.nextElementSibling - - // If there's no selector, return the first sibling - if (!selector) return sibling - - // If the sibling matches our selector, use it - // If not, jump to the next sibling and continue the loop - while (sibling) { - if (sibling.matches(selector)) return sibling - sibling = sibling.nextElementSibling - } -} - -function createCollapsibles(node) { - if (!node) { - node = document - } - - let collapsibles = node.querySelectorAll(".collapsible") - collapsibles.forEach(function(c) { - let handle = document.createElement('span') - handle.className = 'collapsible-handle' - - if (c.className.indexOf('active') !== -1) { - handle.innerHTML = '➖' // minus - } else { - handle.innerHTML = '➕' // plus - } - c.insertBefore(handle, c.firstChild) - - c.addEventListener('click', function() { - this.classList.toggle("active") - let content = getNextSibling(this, '.collapsible-content') - if (content.style.display === "block") { - content.style.display = "none" - handle.innerHTML = '➕' // plus - } else { - content.style.display = "block" - handle.innerHTML = '➖' // minus - } - - if (this == advancedPanelHandle) { - let state = (content.style.display === 'block' ? 'true' : 'false') - localStorage.setItem(ADVANCED_PANEL_OPEN_KEY, state) - } else if (this == modifiersPanelHandle) { - let state = (content.style.display === 'block' ? 'true' : 'false') - localStorage.setItem(MODIFIERS_PANEL_OPEN_KEY, state) - } else if (this == negativePromptPanelHandle) { - let state = (content.style.display === 'block' ? 'true' : 'false') - localStorage.setItem(NEGATIVE_PROMPT_PANEL_OPEN_KEY, state) - } - }) - }) -} -createCollapsibles() - -function refreshTagsList() { - editorModifierTagsList.innerHTML = '' - - if (activeTags.length == 0) { - editorTagsContainer.style.display = 'none' - return - } else { - editorTagsContainer.style.display = 'block' - } - - activeTags.forEach((tag, index) => { - tag.element.querySelector('.modifier-card-image-overlay').innerText = '-' - tag.element.classList.add('modifier-card-tiny') - - editorModifierTagsList.appendChild(tag.element) - - tag.element.addEventListener('click', () => { - let idx = activeTags.indexOf(tag) - - if (idx !== -1) { - activeTags[idx].originElement.classList.remove(activeCardClass) - activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+' - - activeTags.splice(idx, 1) - refreshTagsList() - } - }) - }) - - let brk = document.createElement('br') - brk.style.clear = 'both' - editorModifierTagsList.appendChild(brk) -} - async function getDiskPath() { try { let diskPath = getSavedDiskPath() @@ -1535,184 +1355,4 @@ async function getDiskPath() { } } -function createModifierCard(name, previews) { - const modifierCard = document.createElement('div') - modifierCard.className = 'modifier-card' - modifierCard.innerHTML = ` -
-
-
+
-

- Modifier Image -
-
-

-
` - - const image = modifierCard.querySelector('.modifier-card-image') - const errorText = modifierCard.querySelector('.modifier-card-error-label') - const label = modifierCard.querySelector('.modifier-card-label') - - errorText.innerText = 'No Image' - - if (typeof previews == 'object') { - image.src = previews[0]; // portrait - image.setAttribute('preview-type', 'portrait') - } else { - image.remove() - } - - const maxLabelLength = 30 - const nameWithoutBy = name.replace('by ', '') - - if(nameWithoutBy.length <= maxLabelLength) { - label.querySelector('p').innerText = nameWithoutBy - } else { - const tooltipText = document.createElement('span') - tooltipText.className = 'tooltip-text' - tooltipText.innerText = name - - label.classList.add('tooltip') - label.appendChild(tooltipText) - - label.querySelector('p').innerText = nameWithoutBy.substring(0, maxLabelLength) + '...' - } - - return modifierCard -} - -function changePreviewImages(val) { - const previewImages = document.querySelectorAll('.modifier-card-image-container img') - - let previewArr = [] - - modifiers.map(x => x.modifiers).forEach(x => previewArr.push(...x.map(m => m.previews))) - - previewArr = previewArr.map(x => { - let obj = {} - - x.forEach(preview => { - obj[preview.name] = preview.path - }) - - return obj - }) - - previewImages.forEach(previewImage => { - const currentPreviewType = previewImage.getAttribute('preview-type') - const relativePreviewPath = previewImage.src.split(modifierThumbnailPath + '/').pop() - - const previews = previewArr.find(preview => relativePreviewPath == preview[currentPreviewType]) - - if(typeof previews == 'object') { - let preview = null - - if (val == 'portrait') { - preview = previews.portrait - } - else if (val == 'landscape') { - preview = previews.landscape - } - - if(preview != null) { - previewImage.src = `${modifierThumbnailPath}/${preview}` - previewImage.setAttribute('preview-type', val) - } - } - }) -} - -function resizeModifierCards(val) { - const cardSizePrefix = 'modifier-card-size_' - const modifierCardClass = 'modifier-card' - - const modifierCards = document.querySelectorAll(`.${modifierCardClass}`) - const cardSize = n => `${cardSizePrefix}${n}` - - modifierCards.forEach(card => { - // remove existing size classes - const classes = card.className.split(' ').filter(c => !c.startsWith(cardSizePrefix)) - card.className = classes.join(' ').trim() - - if(val != 0) { - card.classList.add(cardSize(val)) - } - }) -} - -async function loadModifiers() { - try { - let res = await fetch('/get/modifiers') - if (res.status === 200) { - res = await res.json() - - modifiers = res; // update global variable - - res.forEach((modifierGroup, idx) => { - const title = modifierGroup.category - const modifiers = modifierGroup.modifiers - - const titleEl = document.createElement('h5') - titleEl.className = 'collapsible' - titleEl.innerText = title - - const modifiersEl = document.createElement('div') - modifiersEl.classList.add('collapsible-content', 'editor-modifiers-leaf') - - if (idx == 0) { - titleEl.className += ' active' - modifiersEl.style.display = 'block' - } - - modifiers.forEach(modObj => { - const modifierName = modObj.modifier - const modifierPreviews = modObj?.previews?.map(preview => `${modifierThumbnailPath}/${preview.path}`) - - const modifierCard = createModifierCard(modifierName, modifierPreviews) - - if(typeof modifierCard == 'object') { - modifiersEl.appendChild(modifierCard) - - modifierCard.addEventListener('click', () => { - if (activeTags.map(x => x.name).includes(modifierName)) { - // remove modifier from active array - activeTags = activeTags.filter(x => x.name != modifierName) - modifierCard.classList.remove(activeCardClass) - - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '+' - } else { - // add modifier to active array - activeTags.push({ - 'name': modifierName, - 'element': modifierCard.cloneNode(true), - 'originElement': modifierCard, - 'previews': modifierPreviews - }) - - modifierCard.classList.add(activeCardClass) - - modifierCard.querySelector('.modifier-card-image-overlay').innerText = '-' - } - - refreshTagsList() - }) - } - }) - - let brk = document.createElement('br') - brk.style.clear = 'both' - modifiersEl.appendChild(brk) - - let e = document.createElement('div') - e.appendChild(titleEl) - e.appendChild(modifiersEl) - - editorModifierEntries.appendChild(e) - }) - - createCollapsibles(editorModifierEntries) - } - } catch (e) { - console.log('error fetching modifiers', e) - } -} +createCollapsibles() \ No newline at end of file diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js new file mode 100644 index 00000000..05c6d4ab --- /dev/null +++ b/ui/media/js/utils.js @@ -0,0 +1,103 @@ +// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/ +function getNextSibling(elem, selector) { + // Get the next sibling element + var sibling = elem.nextElementSibling + + // If there's no selector, return the first sibling + if (!selector) return sibling + + // If the sibling matches our selector, use it + // If not, jump to the next sibling and continue the loop + while (sibling) { + if (sibling.matches(selector)) return sibling + sibling = sibling.nextElementSibling + } +} + +function createCollapsibles(node) { + if (!node) { + node = document + } + + let collapsibles = node.querySelectorAll(".collapsible") + collapsibles.forEach(function(c) { + let handle = document.createElement('span') + handle.className = 'collapsible-handle' + + if (c.className.indexOf('active') !== -1) { + handle.innerHTML = '➖' // minus + } else { + handle.innerHTML = '➕' // plus + } + c.insertBefore(handle, c.firstChild) + + c.addEventListener('click', function() { + this.classList.toggle("active") + let content = getNextSibling(this, '.collapsible-content') + if (content.style.display === "block") { + content.style.display = "none" + handle.innerHTML = '➕' // plus + } else { + content.style.display = "block" + handle.innerHTML = '➖' // minus + } + + if (this == advancedPanelHandle) { + let state = (content.style.display === 'block' ? 'true' : 'false') + localStorage.setItem(ADVANCED_PANEL_OPEN_KEY, state) + } else if (this == modifiersPanelHandle) { + let state = (content.style.display === 'block' ? 'true' : 'false') + localStorage.setItem(MODIFIERS_PANEL_OPEN_KEY, state) + } else if (this == negativePromptPanelHandle) { + let state = (content.style.display === 'block' ? 'true' : 'false') + localStorage.setItem(NEGATIVE_PROMPT_PANEL_OPEN_KEY, state) + } + }) + }) +} + +function permute(arr) { + let permutations = [] + let n = arr.length + let n_permutations = Math.pow(2, n) + for (let i = 0; i < n_permutations; i++) { + let perm = [] + let mask = Number(i).toString(2).padStart(n, '0') + + for (let idx = 0; idx < mask.length; idx++) { + if (mask[idx] === '1' && arr[idx].trim() !== '') { + perm.push(arr[idx]) + } + } + + if (perm.length > 0) { + permutations.push(perm) + } + } + + return permutations +} + +// https://stackoverflow.com/a/8212878 +function millisecondsToStr(milliseconds) { + function numberEnding (number) { + return (number > 1) ? 's' : '' + } + + var temp = Math.floor(milliseconds / 1000) + var hours = Math.floor((temp %= 86400) / 3600) + var s = '' + if (hours) { + s += hours + ' hour' + numberEnding(hours) + ' ' + } + var minutes = Math.floor((temp %= 3600) / 60) + if (minutes) { + s += minutes + ' minute' + numberEnding(minutes) + ' ' + } + var seconds = temp % 60 + if (!hours && minutes < 4 && seconds) { + s += seconds + ' second' + numberEnding(seconds) + } + + return s +} \ No newline at end of file From 946dfdf7b8a9a78f82311cf4f849525b08154fdb Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 18 Oct 2022 22:29:15 +0530 Subject: [PATCH 06/27] Bring back the upscale/double/redo buttons --- ui/media/js/main.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 35c43041..403c1252 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -369,12 +369,12 @@ function showImages(reqBody, res, outputContainer, livePreview) { const buttons = { 'imgUseBtn': { html: 'Use as Input', click: getUseAsInputHandler(imageItemElem) }, 'imgSaveBtn': { html: 'Download', click: getSaveImageHandler(imageItemElem, req['output_format']) }, - // 'imgX2Btn': { html: 'Double Size', click: getStartNewTaskHandler(req, imageItemElem, 'img2img_X2') }, - // 'imgRedoBtn': { html: 'Redo', click: getStartNewTaskHandler(req, imageItemElem, 'img2img') }, + 'imgX2Btn': { html: 'Double Size', click: getStartNewTaskHandler(req, imageItemElem, 'img2img_X2') }, + 'imgRedoBtn': { html: 'Redo', click: getStartNewTaskHandler(req, imageItemElem, 'img2img') }, + } + if (!req.use_upscale) { + buttons.upscaleBtn = { html: 'Upscale', click: getStartNewTaskHandler(req, imageItemElem, 'upscale') } } - // if (!req.use_upscale) { - // buttons.upscaleBtn = { html: 'Upscale', click: getStartNewTaskHandler(req, imageItemElem, 'upscale') } - // } const imgItemInfo = imageItemElem.querySelector('.imgItemInfo') const createButton = function(name, btnInfo) { const newButton = document.createElement('button') From 4241fb9386c13afa561cbb427fff70a66ea6a511 Mon Sep 17 00:00:00 2001 From: rbertus2000 <91765399+rbertus2000@users.noreply.github.com> Date: Tue, 18 Oct 2022 22:38:37 +0200 Subject: [PATCH 07/27] fixed img_id for parallel renders --- ui/sd_internal/runtime.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/sd_internal/runtime.py b/ui/sd_internal/runtime.py index efad434d..a7d28b81 100644 --- a/ui/sd_internal/runtime.py +++ b/ui/sd_internal/runtime.py @@ -197,7 +197,7 @@ def load_model_real_esrgan(real_esrgan_to_use): print('loaded ', real_esrgan_to_use, 'to', device, 'precision', precision) -def get_base_path(disk_path, session_id, prompt, img_id, ext, suffix=None): +def get_base_path(disk_path, session_id, prompt, ext, suffix=None): if disk_path is None: return None if session_id is None: return None if ext is None: raise Exception('Missing ext') @@ -206,6 +206,8 @@ def get_base_path(disk_path, session_id, prompt, img_id, ext, suffix=None): os.makedirs(session_out_path, exist_ok=True) prompt_flattened = filename_regex.sub('_', prompt)[:50] + img_id = base64.b64encode(int(time.time()).to_bytes(8, 'big')).decode() # Generate unique ID based on time. + img_id = img_id.translate({43:None, 47:None, 61:None})[-8:] # Remove + / = and keep last 8 chars. if suffix is not None: return os.path.join(session_out_path, f"{prompt_flattened}_{img_id}_{suffix}.{ext}") @@ -316,8 +318,7 @@ def do_mk_img(req: Request): opt_f = 8 opt_ddim_eta = 0.0 opt_init_img = req.init_image - img_id = base64.b64encode(int(time.time()).to_bytes(8, 'big')).decode() # Generate unique ID based on time. - img_id = img_id.translate({43:None, 47:None, 61:None})[-8:] # Remove + / = and keep last 8 chars. + print(req.to_string(), '\n device', device) @@ -479,9 +480,9 @@ def do_mk_img(req: Request): if req.save_to_disk_path is not None: if return_orig_img: - img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format) + img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], req.output_format) save_image(img, img_out_path) - meta_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, 'txt') + meta_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], 'txt') save_metadata(meta_out_path, req, prompts[0], opt_seed) if return_orig_img: @@ -508,7 +509,7 @@ def do_mk_img(req: Request): response_image = ResponseImage(data=filtered_img_data, seed=req.seed) res.images.append(response_image) if req.save_to_disk_path is not None: - filtered_img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format, "_".join(filters_applied)) + filtered_img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], req.output_format, "_".join(filters_applied)) save_image(filtered_image, filtered_img_out_path) response_image.path_abs = filtered_img_out_path del filtered_image From d2d9c2dd0f5dcd2919339350ae573b683920fc00 Mon Sep 17 00:00:00 2001 From: rbertus2000 <91765399+rbertus2000@users.noreply.github.com> Date: Wed, 19 Oct 2022 01:17:44 +0200 Subject: [PATCH 08/27] fixed corresponding txt file id --- ui/sd_internal/runtime.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/sd_internal/runtime.py b/ui/sd_internal/runtime.py index a7d28b81..9deb31d2 100644 --- a/ui/sd_internal/runtime.py +++ b/ui/sd_internal/runtime.py @@ -197,7 +197,7 @@ def load_model_real_esrgan(real_esrgan_to_use): print('loaded ', real_esrgan_to_use, 'to', device, 'precision', precision) -def get_base_path(disk_path, session_id, prompt, ext, suffix=None): +def get_base_path(disk_path, session_id, prompt, img_id, ext, suffix=None): if disk_path is None: return None if session_id is None: return None if ext is None: raise Exception('Missing ext') @@ -206,8 +206,7 @@ def get_base_path(disk_path, session_id, prompt, ext, suffix=None): os.makedirs(session_out_path, exist_ok=True) prompt_flattened = filename_regex.sub('_', prompt)[:50] - img_id = base64.b64encode(int(time.time()).to_bytes(8, 'big')).decode() # Generate unique ID based on time. - img_id = img_id.translate({43:None, 47:None, 61:None})[-8:] # Remove + / = and keep last 8 chars. + if suffix is not None: return os.path.join(session_out_path, f"{prompt_flattened}_{img_id}_{suffix}.{ext}") @@ -463,6 +462,8 @@ def do_mk_img(req: Request): print("saving images") for i in range(batch_size): + img_id = base64.b64encode(int(time.time()+i).to_bytes(8, 'big')).decode() # Generate unique ID based on time. + img_id = img_id.translate({43:None, 47:None, 61:None})[-8:] # Remove + / = and keep last 8 chars. x_samples_ddim = modelFS.decode_first_stage(x_samples[i].unsqueeze(0)) x_sample = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0) @@ -480,9 +481,9 @@ def do_mk_img(req: Request): if req.save_to_disk_path is not None: if return_orig_img: - img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], req.output_format) + img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format) save_image(img, img_out_path) - meta_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], 'txt') + meta_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, 'txt') save_metadata(meta_out_path, req, prompts[0], opt_seed) if return_orig_img: @@ -509,7 +510,7 @@ def do_mk_img(req: Request): response_image = ResponseImage(data=filtered_img_data, seed=req.seed) res.images.append(response_image) if req.save_to_disk_path is not None: - filtered_img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], req.output_format, "_".join(filters_applied)) + filtered_img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format, "_".join(filters_applied)) save_image(filtered_image, filtered_img_out_path) response_image.path_abs = filtered_img_out_path del filtered_image From 105f07184716b3ad6599e2401a938c873141ea20 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 19 Oct 2022 13:50:05 +0530 Subject: [PATCH 09/27] Expand curly braces in prompts, for e.g. 'hello {foo,bar}' => 'hello foo' and 'hello bar' --- ui/index.html | 2 +- ui/media/js/main.js | 37 ++++++--- ui/media/js/utils.js | 173 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 11 deletions(-) diff --git a/ui/index.html b/ui/index.html index 7a5abee7..b779616d 100644 --- a/ui/index.html +++ b/ui/index.html @@ -17,7 +17,7 @@
- + - + diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 78969875..e0f77636 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -376,6 +376,10 @@ function showImages(reqBody, res, outputContainer, livePreview) { if (!req.use_upscale) { buttons.upscaleBtn = { text: 'Upscale', on_click: getStartNewTaskHandler('upscale') } } + + // include the plugins + Object.assign(buttons, PLUGINS['IMAGE_INFO_BUTTONS']) + const imgItemInfo = imageItemElem.querySelector('.imgItemInfo') const createButton = function(name, btnInfo) { const newButton = document.createElement('button') @@ -383,7 +387,8 @@ function showImages(reqBody, res, outputContainer, livePreview) { newButton.classList.add('tasksBtns') newButton.innerText = btnInfo.text newButton.addEventListener('click', function() { - btnInfo.on_click(req, imageItemElem) + let img = imageItemElem.querySelector('img') + btnInfo.on_click(req, img) }) imgItemInfo.appendChild(newButton) } @@ -392,9 +397,8 @@ function showImages(reqBody, res, outputContainer, livePreview) { }) } -function onUseAsInputClick(req, imageItemElem) { - const imageElem = imageItemElem.querySelector('img') - const imgData = imageElem.src +function onUseAsInputClick(req, img) { + const imgData = img.src initImageSelector.value = null initImagePreview.src = imgData @@ -406,13 +410,12 @@ function onUseAsInputClick(req, imageItemElem) { samplerSelectionContainer.style.display = 'none' } -function onDownloadImageClick(req, imageItemElem) { - const imageElem = imageItemElem.querySelector('img') - const imgData = imageElem.src - const imageSeed = imageElem.getAttribute('data-seed') - const imagePrompt = imageElem.getAttribute('data-prompt') - const imageInferenceSteps = imageElem.getAttribute('data-steps') - const imageGuidanceScale = imageElem.getAttribute('data-guidance') +function onDownloadImageClick(req, img) { + const imgData = img.src + const imageSeed = img.getAttribute('data-seed') + const imagePrompt = img.getAttribute('data-prompt') + const imageInferenceSteps = img.getAttribute('data-steps') + const imageGuidanceScale = img.getAttribute('data-guidance') const imgDownload = document.createElement('a') imgDownload.download = createFileName(imagePrompt, imageSeed, imageInferenceSteps, imageGuidanceScale, req['output_format']) @@ -421,12 +424,11 @@ function onDownloadImageClick(req, imageItemElem) { } function getStartNewTaskHandler(mode) { - return function(reqBody, imageItemElem) { + return function(reqBody, img) { if (!isServerAvailable()) { alert('The server is not available.') return } - const imageElem = imageItemElem.querySelector('img') const newTaskRequest = getCurrentUserRequest() switch (mode) { case 'img2img': @@ -438,7 +440,7 @@ function getStartNewTaskHandler(mode) { if (!newTaskRequest.reqBody.init_image || mode === 'img2img_X2') { newTaskRequest.reqBody.sampler = 'ddim' newTaskRequest.reqBody.prompt_strength = '0.5' - newTaskRequest.reqBody.init_image = imageElem.src + newTaskRequest.reqBody.init_image = img.src delete newTaskRequest.reqBody.mask } else { newTaskRequest.reqBody.seed = 1 + newTaskRequest.reqBody.seed @@ -471,10 +473,7 @@ function getStartNewTaskHandler(mode) { } } -function onMakeSimilarClick(req, imageItemElem) { - const similarImagesCount = 5 - const imageElem = imageItemElem.querySelector('img') - +function onMakeSimilarClick(req, img) { let newTaskRequest = getCurrentUserRequest() newTaskRequest.reqBody = Object.assign({}, req, { @@ -483,7 +482,7 @@ function onMakeSimilarClick(req, imageItemElem) { num_inference_steps: 50, guidance_scale: 7.5, prompt_strength: 0.7, - init_image: imageElem.src, + init_image: img.src, seed: Math.floor(Math.random() * 10000000) }) diff --git a/ui/media/js/plugins.js b/ui/media/js/plugins.js new file mode 100644 index 00000000..91d16c3c --- /dev/null +++ b/ui/media/js/plugins.js @@ -0,0 +1,23 @@ +const PLUGIN_API_VERSION = "1.0" + +const PLUGINS = { + /** + * Register new buttons to show on each output image. + * + * Example: + * PLUGINS['IMAGE_INFO_BUTTONS']['myCustomVariationButton'] = { + * text: 'Make a Similar Image', + * on_click: function(origRequest, image) { + * let newTaskRequest = getCurrentUserRequest() + * newTaskRequest.reqBody = Object.assign({}, origRequest, { + * init_image: image.src, + * prompt_strength: 0.7, + * seed: Math.floor(Math.random() * 10000000) + * }) + * newTaskRequest.seed = newTaskRequest.reqBody.seed + * createTask(newTaskRequest) + * } + * } + */ + IMAGE_INFO_BUTTONS: {} +} From af05d9419833e198760140d66cf1ee2a8bd50b7a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 19 Oct 2022 20:10:45 +0530 Subject: [PATCH 15/27] Allow plugin buttons for image overlay to decide whether they should be displayed or not --- ui/media/js/main.js | 11 +++++++++-- ui/media/js/plugins.js | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index e0f77636..95b6ade1 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -381,18 +381,25 @@ function showImages(reqBody, res, outputContainer, livePreview) { Object.assign(buttons, PLUGINS['IMAGE_INFO_BUTTONS']) const imgItemInfo = imageItemElem.querySelector('.imgItemInfo') + const img = imageItemElem.querySelector('img') const createButton = function(name, btnInfo) { const newButton = document.createElement('button') newButton.classList.add(name) newButton.classList.add('tasksBtns') newButton.innerText = btnInfo.text newButton.addEventListener('click', function() { - let img = imageItemElem.querySelector('img') btnInfo.on_click(req, img) }) imgItemInfo.appendChild(newButton) } - Object.keys(buttons).forEach((name) => createButton(name, buttons[name])) + Object.keys(buttons).forEach((name) => { + const btn = buttons[name] + if (btn.filter && btn.filter(req, img) === false) { + return + } + + createButton(name, btn) + }) } }) } diff --git a/ui/media/js/plugins.js b/ui/media/js/plugins.js index 91d16c3c..51c30ecf 100644 --- a/ui/media/js/plugins.js +++ b/ui/media/js/plugins.js @@ -16,6 +16,11 @@ const PLUGINS = { * }) * newTaskRequest.seed = newTaskRequest.reqBody.seed * createTask(newTaskRequest) + * }, + * filter: function(origRequest, image) { + * // this is an optional function. return true/false to show/hide the button + * // if this function isn't set, the button will always be visible + * return true * } * } */ From 602686a5d27c4c6b71b4dd38d87a4f27d84aace5 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 19 Oct 2022 20:27:06 +0530 Subject: [PATCH 16/27] Move the current implementation of upscale/redo/double size into a custom plugin --- ui/media/js/main.js | 5 ----- ui/media/js/plugins.js | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 95b6ade1..c266dd7b 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -369,13 +369,8 @@ function showImages(reqBody, res, outputContainer, livePreview) { const buttons = { 'imgUseBtn': { text: 'Use as Input', on_click: onUseAsInputClick }, 'imgSaveBtn': { text: 'Download', on_click: onDownloadImageClick }, - 'imgX2Btn': { text: 'Double Size', on_click: getStartNewTaskHandler('img2img_X2') }, - 'imgRedoBtn': { text: 'Redo', on_click: getStartNewTaskHandler('img2img') }, 'makeSimilarBtn': { text: 'Make Similar Images', on_click: onMakeSimilarClick }, } - if (!req.use_upscale) { - buttons.upscaleBtn = { text: 'Upscale', on_click: getStartNewTaskHandler('upscale') } - } // include the plugins Object.assign(buttons, PLUGINS['IMAGE_INFO_BUTTONS']) diff --git a/ui/media/js/plugins.js b/ui/media/js/plugins.js index 51c30ecf..92ca296c 100644 --- a/ui/media/js/plugins.js +++ b/ui/media/js/plugins.js @@ -26,3 +26,54 @@ const PLUGINS = { */ IMAGE_INFO_BUTTONS: {} } + + +PLUGINS['IMAGE_INFO_BUTTONS']['custom_imgX2Btn'] = { text: 'Double Size', on_click: getStartNewTaskHandler('img2img_X2') } +PLUGINS['IMAGE_INFO_BUTTONS']['custom_imgRedoBtn'] = { text: 'Redo', on_click: getStartNewTaskHandler('img2img') } +PLUGINS['IMAGE_INFO_BUTTONS']['custom_upscaleBtn'] = { text: 'Upscale', on_click: getStartNewTaskHandler('upscale'), filter: (req, img) => !req.use_upscale } + +function getStartNewTaskHandler(mode) { + return function(reqBody, img) { + const newTaskRequest = getCurrentUserRequest() + switch (mode) { + case 'img2img': + case 'img2img_X2': + newTaskRequest.reqBody = Object.assign({}, reqBody, { + num_outputs: 1, + use_cpu: useCPUField.checked, + }) + if (!newTaskRequest.reqBody.init_image || mode === 'img2img_X2') { + newTaskRequest.reqBody.sampler = 'ddim' + newTaskRequest.reqBody.prompt_strength = '0.5' + newTaskRequest.reqBody.init_image = img.src + delete newTaskRequest.reqBody.mask + } else { + newTaskRequest.reqBody.seed = 1 + newTaskRequest.reqBody.seed + } + if (mode === 'img2img_X2') { + newTaskRequest.reqBody.width = reqBody.width * 2 + newTaskRequest.reqBody.height = reqBody.height * 2 + newTaskRequest.reqBody.num_inference_steps = Math.min(100, reqBody.num_inference_steps * 2) + if (useUpscalingField.checked) { + newTaskRequest.reqBody.use_upscale = upscaleModelField.value + } else { + delete newTaskRequest.reqBody.use_upscale + } + } + break + case 'upscale': + newTaskRequest.reqBody = Object.assign({}, reqBody, { + num_outputs: 1, + //use_face_correction: 'GFPGANv1.3', + use_upscale: upscaleModelField.value, + }) + break + default: + throw new Error("Unknown upscale mode: " + mode) + } + newTaskRequest.seed = newTaskRequest.reqBody.seed + newTaskRequest.numOutputsTotal = 1 + newTaskRequest.batchCount = 1 + createTask(newTaskRequest) + } +} From bae0bec1cc9c64a603887479572715814900005c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 19 Oct 2022 21:21:19 +0530 Subject: [PATCH 17/27] Change the image buttons plugins to a list instead of a dict --- ui/media/js/main.js | 20 +++++++++----------- ui/media/js/plugins.js | 12 ++++++------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index c266dd7b..609e3cbe 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -366,20 +366,19 @@ function showImages(reqBody, res, outputContainer, livePreview) { const imageSeedLabel = imageItemElem.querySelector('.imgSeedLabel') imageSeedLabel.innerText = 'Seed: ' + req.seed - const buttons = { - 'imgUseBtn': { text: 'Use as Input', on_click: onUseAsInputClick }, - 'imgSaveBtn': { text: 'Download', on_click: onDownloadImageClick }, - 'makeSimilarBtn': { text: 'Make Similar Images', on_click: onMakeSimilarClick }, - } + let buttons = [ + { text: 'Use as Input', on_click: onUseAsInputClick }, + { text: 'Download', on_click: onDownloadImageClick }, + { text: 'Make Similar Images', on_click: onMakeSimilarClick }, + ] // include the plugins - Object.assign(buttons, PLUGINS['IMAGE_INFO_BUTTONS']) + buttons = buttons.concat(PLUGINS['IMAGE_INFO_BUTTONS']) const imgItemInfo = imageItemElem.querySelector('.imgItemInfo') const img = imageItemElem.querySelector('img') - const createButton = function(name, btnInfo) { + const createButton = function(btnInfo) { const newButton = document.createElement('button') - newButton.classList.add(name) newButton.classList.add('tasksBtns') newButton.innerText = btnInfo.text newButton.addEventListener('click', function() { @@ -387,13 +386,12 @@ function showImages(reqBody, res, outputContainer, livePreview) { }) imgItemInfo.appendChild(newButton) } - Object.keys(buttons).forEach((name) => { - const btn = buttons[name] + buttons.forEach(btn => { if (btn.filter && btn.filter(req, img) === false) { return } - createButton(name, btn) + createButton(btn) }) } }) diff --git a/ui/media/js/plugins.js b/ui/media/js/plugins.js index 92ca296c..bbbfe333 100644 --- a/ui/media/js/plugins.js +++ b/ui/media/js/plugins.js @@ -5,7 +5,7 @@ const PLUGINS = { * Register new buttons to show on each output image. * * Example: - * PLUGINS['IMAGE_INFO_BUTTONS']['myCustomVariationButton'] = { + * PLUGINS['IMAGE_INFO_BUTTONS'].push({ * text: 'Make a Similar Image', * on_click: function(origRequest, image) { * let newTaskRequest = getCurrentUserRequest() @@ -22,15 +22,15 @@ const PLUGINS = { * // if this function isn't set, the button will always be visible * return true * } - * } + * }) */ - IMAGE_INFO_BUTTONS: {} + IMAGE_INFO_BUTTONS: [] } -PLUGINS['IMAGE_INFO_BUTTONS']['custom_imgX2Btn'] = { text: 'Double Size', on_click: getStartNewTaskHandler('img2img_X2') } -PLUGINS['IMAGE_INFO_BUTTONS']['custom_imgRedoBtn'] = { text: 'Redo', on_click: getStartNewTaskHandler('img2img') } -PLUGINS['IMAGE_INFO_BUTTONS']['custom_upscaleBtn'] = { text: 'Upscale', on_click: getStartNewTaskHandler('upscale'), filter: (req, img) => !req.use_upscale } +PLUGINS['IMAGE_INFO_BUTTONS'].push({ text: 'Double Size', on_click: getStartNewTaskHandler('img2img_X2') }) +PLUGINS['IMAGE_INFO_BUTTONS'].push({ text: 'Redo', on_click: getStartNewTaskHandler('img2img') }) +PLUGINS['IMAGE_INFO_BUTTONS'].push({ text: 'Upscale', on_click: getStartNewTaskHandler('upscale'), filter: (req, img) => !req.use_upscale }) function getStartNewTaskHandler(mode) { return function(reqBody, img) { From e287df1320b14079af2171b6093dbc365257a135 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 19 Oct 2022 21:34:40 +0530 Subject: [PATCH 18/27] Allow loading UI plugins from a /plugins/ URL path, which loads files ending with .plugin.js inside the plugins/ui folder --- ui/index.html | 1 + ui/media/js/main.js | 52 +------------------------------------ ui/media/js/plugins.js | 58 ++++++++++-------------------------------- ui/server.py | 17 ++++++++++++- 4 files changed, 31 insertions(+), 97 deletions(-) diff --git a/ui/index.html b/ui/index.html index 07a1876c..0fcf28ca 100644 --- a/ui/index.html +++ b/ui/index.html @@ -266,6 +266,7 @@ async function init() { await getAppConfig() await getModels() await initSettings() + await loadUIPlugins() setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000) healthCheck() diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 609e3cbe..cb76fb35 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -369,7 +369,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { let buttons = [ { text: 'Use as Input', on_click: onUseAsInputClick }, { text: 'Download', on_click: onDownloadImageClick }, - { text: 'Make Similar Images', on_click: onMakeSimilarClick }, + { text: 'Make Similar Images', on_click: onMakeSimilarClick } ] // include the plugins @@ -423,56 +423,6 @@ function onDownloadImageClick(req, img) { imgDownload.click() } -function getStartNewTaskHandler(mode) { - return function(reqBody, img) { - if (!isServerAvailable()) { - alert('The server is not available.') - return - } - const newTaskRequest = getCurrentUserRequest() - switch (mode) { - case 'img2img': - case 'img2img_X2': - newTaskRequest.reqBody = Object.assign({}, reqBody, { - num_outputs: 1, - use_cpu: useCPUField.checked, - }) - if (!newTaskRequest.reqBody.init_image || mode === 'img2img_X2') { - newTaskRequest.reqBody.sampler = 'ddim' - newTaskRequest.reqBody.prompt_strength = '0.5' - newTaskRequest.reqBody.init_image = img.src - delete newTaskRequest.reqBody.mask - } else { - newTaskRequest.reqBody.seed = 1 + newTaskRequest.reqBody.seed - } - if (mode === 'img2img_X2') { - newTaskRequest.reqBody.width = reqBody.width * 2 - newTaskRequest.reqBody.height = reqBody.height * 2 - newTaskRequest.reqBody.num_inference_steps = Math.min(100, reqBody.num_inference_steps * 2) - if (useUpscalingField.checked) { - newTaskRequest.reqBody.use_upscale = upscaleModelField.value - } else { - delete newTaskRequest.reqBody.use_upscale - } - } - break - case 'upscale': - newTaskRequest.reqBody = Object.assign({}, reqBody, { - num_outputs: 1, - //use_face_correction: 'GFPGANv1.3', - use_upscale: upscaleModelField.value, - }) - break - default: - throw new Error("Unknown upscale mode: " + mode) - } - newTaskRequest.seed = newTaskRequest.reqBody.seed - newTaskRequest.numOutputsTotal = 1 - newTaskRequest.batchCount = 1 - createTask(newTaskRequest) - } -} - function onMakeSimilarClick(req, img) { let newTaskRequest = getCurrentUserRequest() diff --git a/ui/media/js/plugins.js b/ui/media/js/plugins.js index bbbfe333..dce65e09 100644 --- a/ui/media/js/plugins.js +++ b/ui/media/js/plugins.js @@ -27,53 +27,21 @@ const PLUGINS = { IMAGE_INFO_BUTTONS: [] } +async function loadUIPlugins() { + try { + let res = await fetch('/get/ui_plugins') + if (res.status === 200) { + res = await res.json() + res.forEach(pluginPath => { + let script = document.createElement('script') + script.src = pluginPath -PLUGINS['IMAGE_INFO_BUTTONS'].push({ text: 'Double Size', on_click: getStartNewTaskHandler('img2img_X2') }) -PLUGINS['IMAGE_INFO_BUTTONS'].push({ text: 'Redo', on_click: getStartNewTaskHandler('img2img') }) -PLUGINS['IMAGE_INFO_BUTTONS'].push({ text: 'Upscale', on_click: getStartNewTaskHandler('upscale'), filter: (req, img) => !req.use_upscale }) + console.log('loading plugin', pluginPath) -function getStartNewTaskHandler(mode) { - return function(reqBody, img) { - const newTaskRequest = getCurrentUserRequest() - switch (mode) { - case 'img2img': - case 'img2img_X2': - newTaskRequest.reqBody = Object.assign({}, reqBody, { - num_outputs: 1, - use_cpu: useCPUField.checked, - }) - if (!newTaskRequest.reqBody.init_image || mode === 'img2img_X2') { - newTaskRequest.reqBody.sampler = 'ddim' - newTaskRequest.reqBody.prompt_strength = '0.5' - newTaskRequest.reqBody.init_image = img.src - delete newTaskRequest.reqBody.mask - } else { - newTaskRequest.reqBody.seed = 1 + newTaskRequest.reqBody.seed - } - if (mode === 'img2img_X2') { - newTaskRequest.reqBody.width = reqBody.width * 2 - newTaskRequest.reqBody.height = reqBody.height * 2 - newTaskRequest.reqBody.num_inference_steps = Math.min(100, reqBody.num_inference_steps * 2) - if (useUpscalingField.checked) { - newTaskRequest.reqBody.use_upscale = upscaleModelField.value - } else { - delete newTaskRequest.reqBody.use_upscale - } - } - break - case 'upscale': - newTaskRequest.reqBody = Object.assign({}, reqBody, { - num_outputs: 1, - //use_face_correction: 'GFPGANv1.3', - use_upscale: upscaleModelField.value, - }) - break - default: - throw new Error("Unknown upscale mode: " + mode) + document.head.appendChild(script) + }) } - newTaskRequest.seed = newTaskRequest.reqBody.seed - newTaskRequest.numOutputsTotal = 1 - newTaskRequest.batchCount = 1 - createTask(newTaskRequest) + } catch (e) { + console.log('error fetching plugin paths', e) } } diff --git a/ui/server.py b/ui/server.py index 48b364f1..61bc7f4d 100644 --- a/ui/server.py +++ b/ui/server.py @@ -12,6 +12,7 @@ sys.path.append(os.path.dirname(SD_UI_DIR)) CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, '..', 'scripts')) MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'models')) +UI_PLUGINS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'plugins', 'ui')) OUTPUT_DIRNAME = "Stable Diffusion UI" # in the user's home folder TASK_TTL = 15 * 60 # Discard last session's task timeout @@ -31,11 +32,15 @@ app = FastAPI() modifiers_cache = None outpath = os.path.join(os.path.expanduser("~"), OUTPUT_DIRNAME) +os.makedirs(UI_PLUGINS_DIR, exist_ok=True) + # don't show access log entries for URLs that start with the given prefix ACCESS_LOG_SUPPRESS_PATH_PREFIXES = ['/ping', '/image', '/modifier-thumbnails'] NOCACHE_HEADERS={"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"} -app.mount('/media', StaticFiles(directory=os.path.join(SD_UI_DIR, 'media/')), name="media") + +app.mount('/media', StaticFiles(directory=os.path.join(SD_UI_DIR, 'media')), name="media") +app.mount('/plugins', StaticFiles(directory=UI_PLUGINS_DIR), name="plugins") class SetAppConfigRequest(BaseModel): update_branch: str = "main" @@ -251,6 +256,15 @@ def getModels(): return models +def getUIPlugins(): + plugins = [] + + for file in os.listdir(UI_PLUGINS_DIR): + if file.endswith('.plugin.js'): + plugins.append(f'/plugins/{file}') + + return plugins + @app.get('/get/{key:path}') def read_web_data(key:str=None): if not key: # /get without parameters, stable-diffusion easter egg. @@ -264,6 +278,7 @@ def read_web_data(key:str=None): return JSONResponse(getModels(), headers=NOCACHE_HEADERS) elif key == 'modifiers': return FileResponse(os.path.join(SD_UI_DIR, 'modifiers.json'), headers=NOCACHE_HEADERS) elif key == 'output_dir': return JSONResponse({ 'output_dir': outpath }, headers=NOCACHE_HEADERS) + elif key == 'ui_plugins': return JSONResponse(getUIPlugins(), headers=NOCACHE_HEADERS) else: raise HTTPException(status_code=404, detail=f'Request for unknown {key}') # HTTP404 Not Found From 253d355bd2a7c8cfc2ff5ecfa8804721267dbce9 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 19 Oct 2022 21:58:51 +0530 Subject: [PATCH 19/27] New upscale button for images; Fix a bug where the string seed would get appended with numbers --- ui/index.html | 2 +- ui/media/js/main.js | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/ui/index.html b/ui/index.html index 0fcf28ca..915ca350 100644 --- a/ui/index.html +++ b/ui/index.html @@ -108,7 +108,7 @@
  • Image Settings - +