reconnect ui on browser restart

This commit is contained in:
Vladimir Mandic
2023-04-18 12:10:30 -04:00
parent e8d8dae4c7
commit ed32259bf7
8 changed files with 91 additions and 204 deletions
+5 -1
View File
@@ -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
+1 -1
View File
@@ -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);
+3 -18
View File
@@ -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()
}
+42 -88
View File
@@ -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)
}
+36 -92
View File
@@ -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<args.length;i++){
res.push(args[i])
}
for(var i=0;i<args.length;i++) res.push(args[i])
return res
}
function switch_to_txt2img(){
gradioApp().querySelector('#tabs').querySelectorAll('button')[0].click();
return args_to_array(arguments);
}
@@ -75,6 +56,7 @@ function switch_to_img2img_tab(no){
gradioApp().querySelector('#tabs').querySelectorAll('button')[1].click();
gradioApp().getElementById('mode_img2img').querySelectorAll('button')[no].click();
}
function switch_to_img2img(){
switch_to_img2img_tab(0);
return args_to_array(arguments);
@@ -98,35 +80,26 @@ function switch_to_inpaint_sketch(){
function switch_to_inpaint(){
gradioApp().querySelector('#tabs').querySelectorAll('button')[1].click();
gradioApp().getElementById('mode_img2img').querySelectorAll('button')[2].click();
return args_to_array(arguments);
}
function switch_to_extras(){
gradioApp().querySelector('#tabs').querySelectorAll('button')[2].click();
return args_to_array(arguments);
}
function get_tab_index(tabId){
var res = 0
gradioApp().getElementById(tabId).querySelector('div').querySelectorAll('button').forEach(function(button, i){
if(button.className.indexOf('selected') != -1)
res = i
if(button.className.indexOf('selected') != -1) res = i
})
return res
}
function create_tab_index_args(tabId, args){
var res = []
for(var i=0; i<args.length; i++){
res.push(args[i])
}
for(var i=0; i<args.length; i++) res.push(args[i])
res[0] = get_tab_index(tabId)
return res
}
@@ -139,18 +112,12 @@ function get_img2img_tab_index() {
function create_submit_args(args){
res = []
for(var i=0;i<args.length;i++){
res.push(args[i])
}
for(var i=0;i<args.length;i++) res.push(args[i])
// As it is currently, txt2img and img2img send back the previous output args (txt2img_gallery, generation_info, html_info) whenever you generate a new image.
// This can lead to uploading a huge gallery of previously generated images, which leads to an unnecessary delay between submitting and beginning to generate.
// I don't know why gradio is sending outputs along with inputs, but we can prevent sending the image gallery here, which seems to be an issue for some.
// If gradio at some point stops sending outputs, this may break something
if(Array.isArray(res[res.length - 3])){
res[res.length - 3] = null
}
if(Array.isArray(res[res.length - 3])) res[res.length - 3] = null
return res
}
@@ -161,50 +128,38 @@ function showSubmitButtons(tabname, show){
// gradioApp().getElementById(tabname+'_skip').style.display = "block"
}
function submit(){
function submit(id, once = false){
rememberGallerySelection('txt2img_gallery')
showSubmitButtons('txt2img', false)
var id = randomId()
requestProgress(id, gradioApp().getElementById('txt2img_gallery_container'), gradioApp().getElementById('txt2img_gallery'), function(){
showSubmitButtons('txt2img', true)
})
if (!id) id = randomId()
const atEnd = () => 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='<h1 style="font-family:monospace;margin-top:20%;color:lightgray;text-align:center;">Reloading...</h1>';
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)
+1 -3
View File
@@ -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({
+1 -1
View File
@@ -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"), {
+2
View File
@@ -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