From ed32259bf796501824bfe6af20a9923da4f6969d Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 18 Apr 2023 12:10:30 -0400 Subject: [PATCH] reconnect ui on browser restart --- TODO.md | 6 +- javascript/black-orange.css | 2 +- javascript/extensions.js | 21 +----- javascript/progressbar.js | 130 ++++++++++++------------------------ javascript/ui.js | 128 ++++++++++------------------------- modules/postprocessing.py | 4 +- modules/shared.py | 2 +- setup.py | 2 + 8 files changed, 91 insertions(+), 204 deletions(-) diff --git a/TODO.md b/TODO.md index 1f82ea050..bd6571c94 100644 --- a/TODO.md +++ b/TODO.md @@ -92,7 +92,11 @@ Tech that can be integrated as part of the core workflow... ### Update +- reconnect ui to active session on browser restart + works for text and image generation, but not for process as there is no progress bar reported there to start with - force unload `xformers` when not used, improves compatibility with AMD/M1 - add `styles.csv` to UI settings to allow customizing path -- add `--disable-queue` to cmd flags that disables Gradio queues and forces it to use HTTP instead of WebSockets +- add `--disable-queue` to cmd flags that disables Gradio queues (experimental) + this forces it to use HTTP instead of WebSockets and can help on unreliable network connections - allow scripts & extensions to set loading priority, fixes `ScuNet` +- improve html loading order diff --git a/javascript/black-orange.css b/javascript/black-orange.css index 8c1e7de1e..9b50d30df 100644 --- a/javascript/black-orange.css +++ b/javascript/black-orange.css @@ -107,7 +107,7 @@ svg.feather.feather-image, .feather .feather-image { display: none } #steps-animation, #controlnet { border-width: 0; } /* based on gradio built-in dark theme */ -:root { +.dark { --body-background-fill: black; --body-text-color: var(--neutral-100); --color-accent-soft: var(--neutral-700); diff --git a/javascript/extensions.js b/javascript/extensions.js index 72924a28c..1c2483b86 100644 --- a/javascript/extensions.js +++ b/javascript/extensions.js @@ -2,48 +2,33 @@ function extensions_apply(_, _, disable_all){ var disable = [] var update = [] - gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x){ - if(x.name.startsWith("enable_") && ! x.checked) - disable.push(x.name.substr(7)) - - if(x.name.startsWith("update_") && x.checked) - update.push(x.name.substr(7)) + if(x.name.startsWith("enable_") && ! x.checked) disable.push(x.name.substr(7)) + if(x.name.startsWith("update_") && x.checked) update.push(x.name.substr(7)) }) - restart_reload() - return [JSON.stringify(disable), JSON.stringify(update), disable_all] } function extensions_check(_, _){ var disable = [] - gradioApp().querySelectorAll('#extensions input[type="checkbox"]').forEach(function(x){ - if(x.name.startsWith("enable_") && ! x.checked) - disable.push(x.name.substr(7)) + if(x.name.startsWith("enable_") && ! x.checked) disable.push(x.name.substr(7)) }) - gradioApp().querySelectorAll('#extensions .extension_status').forEach(function(x){ x.innerHTML = "Loading..." }) - - var id = randomId() requestProgress(id, gradioApp().getElementById('extensions_installed_top'), null, function(){ - }) - return [id, JSON.stringify(disable)] } function install_extension_from_index(button, url){ button.disabled = "disabled" button.value = "Installing..." - textarea = gradioApp().querySelector('#extension_to_install textarea') textarea.value = url updateInput(textarea) - gradioApp().querySelector('#install_extension_button').click() } diff --git a/javascript/progressbar.js b/javascript/progressbar.js index 77fdd4fd2..c020fcda7 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -1,14 +1,8 @@ -// code related to showing and updating progressbar shown as the image is being made +function rememberGallerySelection(id_gallery) {} -function rememberGallerySelection(id_gallery){ +function getGallerySelectedIndex(id_gallery) {} -} - -function getGallerySelectedIndex(id_gallery){ - -} - -function request(url, data, handler, errorHandler){ +function request(url, data, handler, errorHandler) { var xhr = new XMLHttpRequest(); var url = url; xhr.open("POST", url, true); @@ -32,148 +26,108 @@ function request(url, data, handler, errorHandler){ xhr.send(js); } -function pad2(x){ +function pad2(x) { return x<10 ? '0'+x : x } -function formatTime(secs){ - if(secs > 3600){ - return pad2(Math.floor(secs/60/60)) + ":" + pad2(Math.floor(secs/60)%60) + ":" + pad2(Math.floor(secs)%60) - } else if(secs > 60){ - return pad2(Math.floor(secs/60)) + ":" + pad2(Math.floor(secs)%60) - } else{ - return Math.floor(secs) + "s" - } +function formatTime(secs) { + if(secs > 3600) return pad2(Math.floor(secs/60/60)) + ":" + pad2(Math.floor(secs/60)%60) + ":" + pad2(Math.floor(secs)%60) + else if(secs > 60) return pad2(Math.floor(secs/60)) + ":" + pad2(Math.floor(secs)%60) + else return Math.floor(secs) + "s" } -function setTitle(progress){ +function setTitle(progress) { var title = 'Stable Diffusion' - - if(opts.show_progress_in_title && progress){ - title = '[' + progress.trim() + '] ' + title; - } - - if(document.title != title){ - document.title = title; - } + if(opts.show_progress_in_title && progress) title = '[' + progress.trim() + '] ' + title; + if(document.title != title) document.title = title; } - -function randomId(){ +function randomId() { return "task(" + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7) + Math.random().toString(36).slice(2, 7)+")" } // starts sending progress requests to "/internal/progress" uri, creating progressbar above progressbarContainer element and // preview inside gallery element. Cleans up all created stuff when the task is over and calls atEnd. // calls onProgress every time there is a progress update -function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgress){ +function requestProgress(id_task, progressbarContainer, gallery, atEnd = null, onProgress = null, once = false) { + var hasStarted = false var dateStart = new Date() - var wasEverActive = false + var prevProgress = null var parentProgressbar = progressbarContainer.parentNode var parentGallery = gallery ? gallery.parentNode : null - var divProgress = document.createElement('div') divProgress.className='progressDiv' divProgress.id = 'progressbar' divProgress.style.display = opts.show_progressbar ? "block" : "none" var divInner = document.createElement('div') divInner.className='progress' - divProgress.appendChild(divInner) parentProgressbar.insertBefore(divProgress, progressbarContainer) - - if(parentGallery){ + localStorage.setItem('task', id_task); + console.debug('task active:', id_task) + if (parentGallery) { var livePreview = document.createElement('div') livePreview.className='livePreview' parentGallery.insertBefore(livePreview, gallery) } - var removeProgressBar = function(){ + var removeProgressBar = function() { + console.debug('task end: ', id_task) + localStorage.removeItem('task'); setTitle("") - parentProgressbar.removeChild(divProgress) - if(parentGallery) parentGallery.removeChild(livePreview) + if (divProgress) parentProgressbar.removeChild(divProgress) + if (parentGallery) parentGallery.removeChild(livePreview) atEnd() } var fun = function(id_task, id_live_preview){ - request("./internal/progress", {"id_task": id_task, "id_live_preview": id_live_preview}, function(res){ - if(res.completed){ + request("./internal/progress", {"id_task": id_task, "id_live_preview": id_live_preview}, function(res){ + var elapsedFromStart = (new Date() - dateStart) / 1000 + if (res.completed) { removeProgressBar() return } - var rect = progressbarContainer.getBoundingClientRect() - - if(rect.width){ - divProgress.style.width = rect.width + "px"; - } - + if (rect.width) divProgress.style.width = rect.width + "px"; progressText = "" - divInner.style.width = ((res.progress || 0) * 100.0) + '%' divInner.style.background = res.progress ? "" : "transparent" - - if(res.progress > 0){ - progressText = ((res.progress || 0) * 100.0).toFixed(0) + '%' - } - - if(res.eta){ - progressText += " ETA: " + formatTime(res.eta) - } - - + if (res.progress > 0) progressText = ((res.progress || 0) * 100.0).toFixed(0) + '%' + if (res.eta) progressText += " ETA: " + formatTime(res.eta) setTitle(progressText) - - if(res.textinfo && res.textinfo.indexOf("\n") == -1){ - progressText = res.textinfo + " " + progressText - } - + if (res.textinfo && res.textinfo.indexOf("\n") == -1) progressText = res.textinfo + " " + progressText divInner.textContent = progressText - - var elapsedFromStart = (new Date() - dateStart) / 1000 - - if(res.active) wasEverActive = true; - - if(! res.active && wasEverActive){ + hasStarted |= res.active + if (!res.active && (hasStarted || once)) { removeProgressBar() return } - - if(elapsedFromStart > 5 && !res.queued && !res.active){ + if (res.completed) { + removeProgressBar() + return + } + if (elapsedFromStart > 3 && !res.queued && res.progress == prevProgress) { removeProgressBar() return } - - - if(res.live_preview && gallery){ + if (res.live_preview && gallery) { var rect = gallery.getBoundingClientRect() if(rect.width){ livePreview.style.width = rect.width + "px" livePreview.style.height = rect.height + "px" } - var img = new Image(); img.onload = function() { livePreview.appendChild(img) - if(livePreview.childElementCount > 2){ - livePreview.removeChild(livePreview.firstElementChild) - } + if (livePreview.childElementCount > 2) livePreview.removeChild(livePreview.firstElementChild) } img.src = res.live_preview; } - - - if(onProgress){ - onProgress(res) - } - - setTimeout(() => { - fun(id_task, res.id_live_preview); - }, opts.live_preview_refresh_period || 500) - }, function(){ + if (onProgress) onProgress(res) + setTimeout(() => fun(id_task, res.id_live_preview), opts.live_preview_refresh_period || 250) + }, function() { removeProgressBar() }) } - fun(id_task, 0) } diff --git a/javascript/ui.js b/javascript/ui.js index 654e6a435..c849ea585 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -11,9 +11,7 @@ function all_gallery_buttons() { var allGalleryButtons = gradioApp().querySelectorAll('[style="display: block;"].tabitem div[id$=_gallery].gradio-gallery .thumbnails > .thumbnail-item.thumbnail-small'); var visibleGalleryButtons = []; allGalleryButtons.forEach(function(elem) { - if (elem.parentElement.offsetParent) { - visibleGalleryButtons.push(elem); - } + if (elem.parentElement.offsetParent) visibleGalleryButtons.push(elem); }) return visibleGalleryButtons; } @@ -22,9 +20,7 @@ function selected_gallery_button() { var allCurrentButtons = gradioApp().querySelectorAll('[style="display: block;"].tabitem div[id$=_gallery].gradio-gallery .thumbnail-item.thumbnail-small.selected'); var visibleCurrentButton = null; allCurrentButtons.forEach(function(elem) { - if (elem.parentElement.offsetParent) { - visibleCurrentButton = elem; - } + if (elem.parentElement.offsetParent) visibleCurrentButton = elem; }) return visibleCurrentButton; } @@ -32,42 +28,27 @@ function selected_gallery_button() { function selected_gallery_index(){ var buttons = all_gallery_buttons(); var button = selected_gallery_button(); - var result = -1 buttons.forEach(function(v, i){ if(v==button) { result = i } }) - return result } function extract_image_from_gallery(gallery){ - if (gallery.length == 0){ - return [null]; - } - if (gallery.length == 1){ - return [gallery[0]]; - } - + if (gallery.length == 0) return [null]; + if (gallery.length == 1) return [gallery[0]]; index = selected_gallery_index() - - if (index < 0 || index >= gallery.length){ - // Use the first image in the gallery as the default - index = 0; - } - + if (index < 0 || index >= gallery.length) index = 0; return [gallery[index]]; } function args_to_array(args){ res = [] - for(var i=0;i showSubmitButtons('txt2img', true) + requestProgress(id, gradioApp().getElementById('txt2img_gallery_container'), gradioApp().getElementById('txt2img_gallery'), atEnd, null, once) var res = create_submit_args(arguments) - res[0] = id - return res } -function submit_img2img(){ +function submit_img2img(id){ rememberGallerySelection('img2img_gallery') showSubmitButtons('img2img', false) - - var id = randomId() + if (!id) id = randomId() requestProgress(id, gradioApp().getElementById('img2img_gallery_container'), gradioApp().getElementById('img2img_gallery'), function(){ showSubmitButtons('img2img', true) }) - var res = create_submit_args(arguments) - res[0] = id res[1] = get_tab_index('mode_img2img') - return res } -function modelmerger(){ - var id = randomId() +function modelmerger(id){ + if (!id) id = randomId() requestProgress(id, gradioApp().getElementById('modelmerger_results_panel'), null, function(){}) - var res = create_submit_args(arguments) res[0] = id return res } - function ask_for_style_name(_, prompt_text, negative_prompt_text) { name_ = prompt('Style name:') return [name_, prompt_text, negative_prompt_text] @@ -215,7 +170,6 @@ function confirm_clear_prompt(prompt, negative_prompt) { prompt = "" negative_prompt = "" } - return [prompt, negative_prompt] } @@ -243,26 +197,19 @@ function recalculate_prompts_img2img(){ opts = {} onUiUpdate(function(){ - if(Object.keys(opts).length != 0) return; - - json_elem = gradioApp().getElementById('settings_json') - if(json_elem == null) return; - + if(Object.keys(opts).length != 0) return; + json_elem = gradioApp().getElementById('settings_json') + if(json_elem == null) return; var textarea = json_elem.querySelector('textarea') var jsdata = textarea.value opts = JSON.parse(jsdata) executeCallbacks(optionsChangedCallbacks); - Object.defineProperty(textarea, 'value', { set: function(newValue) { var valueProp = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); var oldValue = valueProp.get.call(textarea); valueProp.set.call(textarea, newValue); - - if (oldValue != newValue) { - opts = JSON.parse(textarea.value) - } - + if (oldValue != newValue) opts = JSON.parse(textarea.value) executeCallbacks(optionsChangedCallbacks); }, get: function() { @@ -270,30 +217,21 @@ onUiUpdate(function(){ return valueProp.get.call(textarea); } }); - json_elem.parentElement.style.display="none" - function registerTextarea(id, id_counter, id_button){ var prompt = gradioApp().getElementById(id) var counter = gradioApp().getElementById(id_counter) var textarea = gradioApp().querySelector("#" + id + " > label > textarea"); - - if(counter.parentElement == prompt.parentElement){ - return - } - + if(counter.parentElement == prompt.parentElement) return prompt.parentElement.insertBefore(counter, prompt) prompt.parentElement.style.position = "relative" - - promptTokecountUpdateFuncs[id] = function(){ update_token_counter(id_button); } - textarea.addEventListener("input", promptTokecountUpdateFuncs[id]); + promptTokecountUpdateFuncs[id] = function(){ update_token_counter(id_button); } + textarea.addEventListener("input", promptTokecountUpdateFuncs[id]); } - registerTextarea('txt2img_prompt', 'txt2img_token_counter', 'txt2img_token_button') registerTextarea('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button') registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button') registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button') - show_all_pages = gradioApp().getElementById('settings_show_all_pages') settings_tabs = gradioApp().querySelector('#settings div') if(show_all_pages && settings_tabs){ @@ -349,7 +287,6 @@ function update_token_counter(button_id) { function restart_reload(){ document.body.innerHTML='

Reloading...

'; setTimeout(function(){location.reload()},8000) - return [] } @@ -361,7 +298,6 @@ function updateInput(target){ target.dispatchEvent(e); } - var desiredCheckpointName = null; function selectCheckpoint(name){ desiredCheckpointName = name; @@ -379,7 +315,6 @@ function create_theme_element() { function preview_theme() { const name = gradioApp().getElementById('setting_gradio_theme').querySelectorAll('span')[1].innerText; // ugly but we want current value without the need to set apply - console.log('PREVIEW', name); if (name === 'black-orange' || name === 'gradio/default') { el = document.getElementById('theme-preview') || create_theme_element(); el.style.display = el.style.display === 'block' ? 'none' : 'block'; @@ -390,8 +325,17 @@ function preview_theme() { .then((r) => r.json()) .then(themes => { theme = themes.find((t)=> t.id === name); - console.log('FOUND', theme); window.open(theme.subdomain, '_blank'); }); } } + +function reconnect_ui() { + const el = gradioApp().getElementById('txt2img_generate') + if (!el) return + else clearInterval(start_check) + const task_id = localStorage.getItem('task') + if (task_id) submit(task_id, true) +} + +var start_check = setInterval(reconnect_ui, 50) diff --git a/modules/postprocessing.py b/modules/postprocessing.py index 10c443137..0e688a8e0 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -34,8 +34,6 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, image_data.append(image) image_names.append(filename) else: - assert image, 'image not selected' - image_data.append(image) image_names.append(None) @@ -77,7 +75,7 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, return outputs, ui_common.plaintext_to_html(infotext), '' -def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, show_extras_results, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, upscale_first: bool, save_output: bool = True): +def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, show_extras_results, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, _upscale_first: bool, save_output: bool = True): """old handler for API""" args = scripts.scripts_postproc.create_args_for_run({ diff --git a/modules/shared.py b/modules/shared.py index 1c5003032..ebf886c2d 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -412,7 +412,7 @@ options_templates.update(options_section(('ui', "Live previews"), { "show_progress_every_n_steps": OptionInfo(-1, "Show new live preview image every N sampling steps. Set to -1 to show after completion of batch.", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), "show_progress_type": OptionInfo("Full", "Image creation progress preview mode", gr.Radio, {"choices": ["Full", "Approx NN", "Approx cheap"]}), "live_preview_content": OptionInfo("Combined", "Live preview subject", gr.Radio, {"choices": ["Combined", "Prompt", "Negative prompt"]}), - "live_preview_refresh_period": OptionInfo(1000, "Progressbar/preview update period, in milliseconds") + "live_preview_refresh_period": OptionInfo(250, "Progressbar/preview update period, in milliseconds") })) options_templates.update(options_section(('sampler-params', "Sampler parameters"), { diff --git a/setup.py b/setup.py index c7a108de2..56ec39ef1 100644 --- a/setup.py +++ b/setup.py @@ -119,6 +119,8 @@ def git(arg: str, ignore: bool = False): global errors # pylint: disable=global-statement errors += 1 log.error(f'Error running git with args: {arg}') + if 'or stash them' in txt: + log.error('Local changes detected: check setup.log for details') log.debug(f'Git output: {txt}') return txt