aboutsummaryrefslogtreecommitdiff
path: root/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'javascript')
-rw-r--r--javascript/aspectRatioOverlay.js9
-rw-r--r--javascript/dragdrop.js10
-rw-r--r--javascript/edit-attention.js127
-rw-r--r--javascript/extensions.js2
-rw-r--r--javascript/extraNetworks.js69
-rw-r--r--javascript/hints.js30
-rw-r--r--javascript/hires_fix.js22
-rw-r--r--javascript/imageviewer.js13
-rw-r--r--javascript/localization.js6
-rw-r--r--javascript/progressbar.js274
-rw-r--r--javascript/textualInversion.js13
-rw-r--r--javascript/ui.js157
12 files changed, 533 insertions, 199 deletions
diff --git a/javascript/aspectRatioOverlay.js b/javascript/aspectRatioOverlay.js
index 66f26a22..0f164b82 100644
--- a/javascript/aspectRatioOverlay.js
+++ b/javascript/aspectRatioOverlay.js
@@ -21,11 +21,16 @@ function dimensionChange(e, is_width, is_height){
var targetElement = null;
var tabIndex = get_tab_index('mode_img2img')
- if(tabIndex == 0){
+ if(tabIndex == 0){ // img2img
targetElement = gradioApp().querySelector('div[data-testid=image] img');
- } else if(tabIndex == 1){
+ } else if(tabIndex == 1){ //Sketch
+ targetElement = gradioApp().querySelector('#img2img_sketch div[data-testid=image] img');
+ } else if(tabIndex == 2){ // Inpaint
targetElement = gradioApp().querySelector('#img2maskimg div[data-testid=image] img');
+ } else if(tabIndex == 3){ // Inpaint sketch
+ targetElement = gradioApp().querySelector('#inpaint_sketch div[data-testid=image] img');
}
+
if(targetElement){
diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js
index 3ed1cb3c..fe008924 100644
--- a/javascript/dragdrop.js
+++ b/javascript/dragdrop.js
@@ -9,11 +9,19 @@ function dropReplaceImage( imgWrap, files ) {
return;
}
+ const tmpFile = files[0];
+
imgWrap.querySelector('.modify-upload button + button, .touch-none + div button + button')?.click();
const callback = () => {
const fileInput = imgWrap.querySelector('input[type="file"]');
if ( fileInput ) {
- fileInput.files = files;
+ if ( files.length === 0 ) {
+ files = new DataTransfer();
+ files.items.add(tmpFile);
+ fileInput.files = files.files;
+ } else {
+ fileInput.files = files;
+ }
fileInput.dispatchEvent(new Event('change'));
}
};
diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js
index b947cbec..619bb1fa 100644
--- a/javascript/edit-attention.js
+++ b/javascript/edit-attention.js
@@ -1,75 +1,96 @@
-addEventListener('keydown', (event) => {
+function keyupEditAttention(event){
let target = event.originalTarget || event.composedPath()[0];
- if (!target.matches("#toprow textarea.gr-text-input[placeholder]")) return;
+ if (!target.matches("[id*='_toprow'] textarea.gr-text-input[placeholder]")) return;
if (! (event.metaKey || event.ctrlKey)) return;
-
- let plus = "ArrowUp"
- let minus = "ArrowDown"
- if (event.key != plus && event.key != minus) return;
+ let isPlus = event.key == "ArrowUp"
+ let isMinus = event.key == "ArrowDown"
+ if (!isPlus && !isMinus) return;
let selectionStart = target.selectionStart;
let selectionEnd = target.selectionEnd;
- // If the user hasn't selected anything, let's select their current parenthesis block
- if (selectionStart === selectionEnd) {
+ let text = target.value;
+
+ function selectCurrentParenthesisBlock(OPEN, CLOSE){
+ if (selectionStart !== selectionEnd) return false;
+
// Find opening parenthesis around current cursor
- const before = target.value.substring(0, selectionStart);
- let beforeParen = before.lastIndexOf("(");
- if (beforeParen == -1) return;
- let beforeParenClose = before.lastIndexOf(")");
+ const before = text.substring(0, selectionStart);
+ let beforeParen = before.lastIndexOf(OPEN);
+ if (beforeParen == -1) return false;
+ let beforeParenClose = before.lastIndexOf(CLOSE);
while (beforeParenClose !== -1 && beforeParenClose > beforeParen) {
- beforeParen = before.lastIndexOf("(", beforeParen - 1);
- beforeParenClose = before.lastIndexOf(")", beforeParenClose - 1);
+ beforeParen = before.lastIndexOf(OPEN, beforeParen - 1);
+ beforeParenClose = before.lastIndexOf(CLOSE, beforeParenClose - 1);
}
// Find closing parenthesis around current cursor
- const after = target.value.substring(selectionStart);
- let afterParen = after.indexOf(")");
- if (afterParen == -1) return;
- let afterParenOpen = after.indexOf("(");
+ const after = text.substring(selectionStart);
+ let afterParen = after.indexOf(CLOSE);
+ if (afterParen == -1) return false;
+ let afterParenOpen = after.indexOf(OPEN);
while (afterParenOpen !== -1 && afterParen > afterParenOpen) {
- afterParen = after.indexOf(")", afterParen + 1);
- afterParenOpen = after.indexOf("(", afterParenOpen + 1);
+ afterParen = after.indexOf(CLOSE, afterParen + 1);
+ afterParenOpen = after.indexOf(OPEN, afterParenOpen + 1);
}
- if (beforeParen === -1 || afterParen === -1) return;
+ if (beforeParen === -1 || afterParen === -1) return false;
// Set the selection to the text between the parenthesis
- const parenContent = target.value.substring(beforeParen + 1, selectionStart + afterParen);
+ const parenContent = text.substring(beforeParen + 1, selectionStart + afterParen);
const lastColon = parenContent.lastIndexOf(":");
selectionStart = beforeParen + 1;
selectionEnd = selectionStart + lastColon;
target.setSelectionRange(selectionStart, selectionEnd);
- }
+ return true;
+ }
+
+ // If the user hasn't selected anything, let's select their current parenthesis block
+ if(! selectCurrentParenthesisBlock('<', '>')){
+ selectCurrentParenthesisBlock('(', ')')
+ }
event.preventDefault();
- if (selectionStart == 0 || target.value[selectionStart - 1] != "(") {
- target.value = target.value.slice(0, selectionStart) +
- "(" + target.value.slice(selectionStart, selectionEnd) + ":1.0)" +
- target.value.slice(selectionEnd);
-
- target.focus();
- target.selectionStart = selectionStart + 1;
- target.selectionEnd = selectionEnd + 1;
-
- } else {
- end = target.value.slice(selectionEnd + 1).indexOf(")") + 1;
- weight = parseFloat(target.value.slice(selectionEnd + 1, selectionEnd + 1 + end));
- if (isNaN(weight)) return;
- if (event.key == minus) weight -= 0.1;
- if (event.key == plus) weight += 0.1;
-
- weight = parseFloat(weight.toPrecision(12));
-
- target.value = target.value.slice(0, selectionEnd + 1) +
- weight +
- target.value.slice(selectionEnd + 1 + end - 1);
-
- target.focus();
- target.selectionStart = selectionStart;
- target.selectionEnd = selectionEnd;
- }
- // Since we've modified a Gradio Textbox component manually, we need to simulate an `input` DOM event to ensure its
- // internal Svelte data binding remains in sync.
- target.dispatchEvent(new Event("input", { bubbles: true }));
-});
+ closeCharacter = ')'
+ delta = opts.keyedit_precision_attention
+
+ if (selectionStart > 0 && text[selectionStart - 1] == '<'){
+ closeCharacter = '>'
+ delta = opts.keyedit_precision_extra
+ } else if (selectionStart == 0 || text[selectionStart - 1] != "(") {
+
+ // do not include spaces at the end
+ while(selectionEnd > selectionStart && text[selectionEnd-1] == ' '){
+ selectionEnd -= 1;
+ }
+ if(selectionStart == selectionEnd){
+ return
+ }
+
+ text = text.slice(0, selectionStart) + "(" + text.slice(selectionStart, selectionEnd) + ":1.0)" + text.slice(selectionEnd);
+
+ selectionStart += 1;
+ selectionEnd += 1;
+ }
+
+ end = text.slice(selectionEnd + 1).indexOf(closeCharacter) + 1;
+ weight = parseFloat(text.slice(selectionEnd + 1, selectionEnd + 1 + end));
+ if (isNaN(weight)) return;
+
+ weight += isPlus ? delta : -delta;
+ weight = parseFloat(weight.toPrecision(12));
+ if(String(weight).length == 1) weight += ".0"
+
+ text = text.slice(0, selectionEnd + 1) + weight + text.slice(selectionEnd + 1 + end - 1);
+
+ target.focus();
+ target.value = text;
+ target.selectionStart = selectionStart;
+ target.selectionEnd = selectionEnd;
+
+ updateInput(target)
+}
+
+addEventListener('keydown', (event) => {
+ keyupEditAttention(event);
+}); \ No newline at end of file
diff --git a/javascript/extensions.js b/javascript/extensions.js
index 59179ca6..ac6e35b9 100644
--- a/javascript/extensions.js
+++ b/javascript/extensions.js
@@ -29,7 +29,7 @@ function install_extension_from_index(button, url){
textarea = gradioApp().querySelector('#extension_to_install textarea')
textarea.value = url
- textarea.dispatchEvent(new Event("input", { bubbles: true }))
+ updateInput(textarea)
gradioApp().querySelector('#install_extension_button').click()
}
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js
new file mode 100644
index 00000000..c5a9adb3
--- /dev/null
+++ b/javascript/extraNetworks.js
@@ -0,0 +1,69 @@
+
+function setupExtraNetworksForTab(tabname){
+ gradioApp().querySelector('#'+tabname+'_extra_tabs').classList.add('extra-networks')
+
+ var tabs = gradioApp().querySelector('#'+tabname+'_extra_tabs > div')
+ var search = gradioApp().querySelector('#'+tabname+'_extra_search textarea')
+ var refresh = gradioApp().getElementById(tabname+'_extra_refresh')
+ var close = gradioApp().getElementById(tabname+'_extra_close')
+
+ search.classList.add('search')
+ tabs.appendChild(search)
+ tabs.appendChild(refresh)
+ tabs.appendChild(close)
+
+ search.addEventListener("input", function(evt){
+ searchTerm = search.value.toLowerCase()
+
+ gradioApp().querySelectorAll('#'+tabname+'_extra_tabs div.card').forEach(function(elem){
+ text = elem.querySelector('.name').textContent.toLowerCase()
+ elem.style.display = text.indexOf(searchTerm) == -1 ? "none" : ""
+ })
+ });
+}
+
+var activePromptTextarea = {};
+
+function setupExtraNetworks(){
+ setupExtraNetworksForTab('txt2img')
+ setupExtraNetworksForTab('img2img')
+
+ function registerPrompt(tabname, id){
+ var textarea = gradioApp().querySelector("#" + id + " > label > textarea");
+
+ if (! activePromptTextarea[tabname]){
+ activePromptTextarea[tabname] = textarea
+ }
+
+ textarea.addEventListener("focus", function(){
+ activePromptTextarea[tabname] = textarea;
+ });
+ }
+
+ registerPrompt('txt2img', 'txt2img_prompt')
+ registerPrompt('txt2img', 'txt2img_neg_prompt')
+ registerPrompt('img2img', 'img2img_prompt')
+ registerPrompt('img2img', 'img2img_neg_prompt')
+}
+
+onUiLoaded(setupExtraNetworks)
+
+function cardClicked(tabname, textToAdd, allowNegativePrompt){
+ var textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector("#" + tabname + "_prompt > label > textarea")
+
+ textarea.value = textarea.value + " " + textToAdd
+ updateInput(textarea)
+}
+
+function saveCardPreview(event, tabname, filename){
+ var textarea = gradioApp().querySelector("#" + tabname + '_preview_filename > label > textarea')
+ var button = gradioApp().getElementById(tabname + '_save_preview')
+
+ textarea.value = filename
+ updateInput(textarea)
+
+ button.click()
+
+ event.stopPropagation()
+ event.preventDefault()
+}
diff --git a/javascript/hints.js b/javascript/hints.js
index 63e17e05..7b60b25e 100644
--- a/javascript/hints.js
+++ b/javascript/hints.js
@@ -4,7 +4,7 @@ titles = {
"Sampling steps": "How many times to improve the generated image iteratively; higher values take longer; very low values can produce bad results",
"Sampling method": "Which algorithm to use to produce the image",
"GFPGAN": "Restore low quality faces using GFPGAN neural network",
- "Euler a": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps to higher than 30-40 does not help",
+ "Euler a": "Euler Ancestral - very creative, each can get a completely different picture depending on step count, setting steps higher than 30-40 does not help",
"DDIM": "Denoising Diffusion Implicit Models - best at inpainting",
"DPM adaptive": "Ignores step count - uses a number of steps determined by the CFG and resolution",
@@ -14,12 +14,14 @@ titles = {
"Seed": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result",
"\u{1f3b2}\ufe0f": "Set seed to -1, which will cause a new random number to be used every time",
"\u267b\ufe0f": "Reuse seed from last generation, mostly useful if it was randomed",
- "\u{1f3a8}": "Add a random artist to the prompt.",
"\u2199\ufe0f": "Read generation parameters from prompt or last generation if prompt is empty into user interface.",
"\u{1f4c2}": "Open images output directory",
"\u{1f4be}": "Save style",
"\U0001F5D1": "Clear prompt",
"\u{1f4cb}": "Apply selected styles to current prompt",
+ "\u{1f4d2}": "Paste available values into the field",
+ "\u{1f3b4}": "Show extra networks",
+
"Inpaint a part of image": "Draw a mask over an image, and the script will regenerate the masked area with content according to prompt",
"SD upscale": "Upscale image normally, split result into tiles, improve each tile using img2img, merge whole image back",
@@ -48,7 +50,7 @@ titles = {
"None": "Do not do anything special",
"Prompt matrix": "Separate prompts into parts using vertical pipe character (|) and the script will create a picture for every combination of them (except for the first part, which will be present in all combinations)",
- "X/Y plot": "Create a grid where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows",
+ "X/Y/Z plot": "Create grid(s) where images will have different parameters. Use inputs below to specify which parameters will be shared by columns and rows",
"Custom code": "Run Python code. Advanced user only. Must run program with --allow-code for this to work",
"Prompt S/R": "Separate a list of words with commas, and the first word will be used as a keyword: script will search for this word in the prompt, and replace it with others",
@@ -74,16 +76,13 @@ titles = {
"Style 1": "Style to apply; styles have components for both positive and negative prompts and apply to both",
"Style 2": "Style to apply; styles have components for both positive and negative prompts and apply to both",
"Apply style": "Insert selected styles into prompt fields",
- "Create style": "Save current prompts as a style. If you add the token {prompt} to the text, the style use that as placeholder for your prompt when you use the style in the future.",
+ "Create style": "Save current prompts as a style. If you add the token {prompt} to the text, the style uses that as a placeholder for your prompt when you use the style in the future.",
"Checkpoint name": "Loads weights from checkpoint before making images. You can either use hash or a part of filename (as seen in settings) for checkpoint name. Recommended to use with Y axis for less switching.",
"Inpainting conditioning mask strength": "Only applies to inpainting models. Determines how strongly to mask off the original image for inpainting and img2img. 1.0 means fully masked, which is the default behaviour. 0.0 means a fully unmasked conditioning. Lower values will help preserve the overall composition of the image, but will struggle with large changes.",
"vram": "Torch active: Peak amount of VRAM used by Torch during generation, excluding cached data.\nTorch reserved: Peak amount of VRAM allocated by Torch, including all active and cached data.\nSys VRAM: Peak amount of VRAM allocation across all applications / total GPU VRAM (peak utilization%).",
- "Highres. fix": "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition",
- "Scale latent": "Uscale the image in latent space. Alternative is to produce the full image from latent representation, upscale that, and then move it back to latent space.",
-
"Eta noise seed delta": "If this values is non-zero, it will be added to seed and used to initialize RNG for noises when using samplers with Eta. You can use this to produce even more variation of images, or you can use this to match images of other software if you know what you are doing.",
"Do not add watermark to images": "If this option is enabled, watermark will not be added to created images. Warning: if you do not add watermark, you may be behaving in an unethical manner.",
@@ -94,13 +93,24 @@ titles = {
"Weighted sum": "Result = A * (1 - M) + B * M",
"Add difference": "Result = A + (B - C) * M",
+ "No interpolation": "Result = A",
- "Learning rate": "how fast should the training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.",
+ "Initialization text": "If the number of tokens is more than the number of vectors, some may be skipped.\nLeave the textbox empty to start with zeroed out vectors",
+ "Learning rate": "How fast should training go. Low values will take longer to train, high values may fail to converge (not generate accurate results) and/or may break the embedding (This has happened if you see Loss: nan in the training info textbox. If this happens, you need to manually restore your embedding from an older not-broken backup).\n\nYou can set a single numeric value, or multiple learning rates using the syntax:\n\n rate_1:max_steps_1, rate_2:max_steps_2, ...\n\nEG: 0.005:100, 1e-3:1000, 1e-5\n\nWill train with rate of 0.005 for first 100 steps, then 1e-3 until 1000 steps, then 1e-5 for all remaining steps.",
"Clip skip": "Early stopping parameter for CLIP model; 1 is stop at last layer as usual, 2 is stop at penultimate layer, etc.",
- "Approx NN": "Cheap neural network approximation. Very fast compared to VAE, but produces pictures with 4 times smaller horizontal/vertical resoluton and lower quality.",
- "Approx cheap": "Very cheap approximation. Very fast compared to VAE, but produces pictures with 8 times smaller horizontal/vertical resoluton and extremely low quality."
+ "Approx NN": "Cheap neural network approximation. Very fast compared to VAE, but produces pictures with 4 times smaller horizontal/vertical resolution and lower quality.",
+ "Approx cheap": "Very cheap approximation. Very fast compared to VAE, but produces pictures with 8 times smaller horizontal/vertical resolution and extremely low quality.",
+
+ "Hires. fix": "Use a two step process to partially create an image at smaller resolution, upscale, and then improve details in it without changing composition",
+ "Hires steps": "Number of sampling steps for upscaled picture. If 0, uses same as for original.",
+ "Upscale by": "Adjusts the size of the image by multiplying the original width and height by the selected value. Ignored if either Resize width to or Resize height to are non-zero.",
+ "Resize width to": "Resizes image to this width. If 0, width is inferred from either of two nearby sliders.",
+ "Resize height to": "Resizes image to this height. If 0, height is inferred from either of two nearby sliders.",
+ "Multiplier for extra networks": "When adding extra network such as Hypernetwork or Lora to prompt, use this multiplier for it.",
+ "Discard weights with matching name": "Regular expression; if weights's name matches it, the weights is not written to the resulting checkpoint. Use ^model_ema to discard EMA weights.",
+ "Extra networks tab order": "Comma-separated list of tab names; tabs listed here will appear in the extra networks UI first and in order lsited."
}
diff --git a/javascript/hires_fix.js b/javascript/hires_fix.js
new file mode 100644
index 00000000..0629475f
--- /dev/null
+++ b/javascript/hires_fix.js
@@ -0,0 +1,22 @@
+
+function setInactive(elem, inactive){
+ if(inactive){
+ elem.classList.add('inactive')
+ } else{
+ elem.classList.remove('inactive')
+ }
+}
+
+function onCalcResolutionHires(enable, width, height, hr_scale, hr_resize_x, hr_resize_y){
+ hrUpscaleBy = gradioApp().getElementById('txt2img_hr_scale')
+ hrResizeX = gradioApp().getElementById('txt2img_hr_resize_x')
+ hrResizeY = gradioApp().getElementById('txt2img_hr_resize_y')
+
+ gradioApp().getElementById('txt2img_hires_fix_row2').style.display = opts.use_old_hires_fix_width_height ? "none" : ""
+
+ setInactive(hrUpscaleBy, opts.use_old_hires_fix_width_height || hr_resize_x > 0 || hr_resize_y > 0)
+ setInactive(hrResizeX, opts.use_old_hires_fix_width_height || hr_resize_x == 0)
+ setInactive(hrResizeY, opts.use_old_hires_fix_width_height || hr_resize_y == 0)
+
+ return [enable, width, height, hr_scale, hr_resize_x, hr_resize_y]
+}
diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js
index 0488bfb4..3f7b1289 100644
--- a/javascript/imageviewer.js
+++ b/javascript/imageviewer.js
@@ -148,9 +148,18 @@ function showGalleryImage() {
if(e && e.parentElement.tagName == 'DIV'){
e.style.cursor='pointer'
e.style.userSelect='none'
- e.addEventListener('click', function (evt) {
- if(!opts.js_modal_lightbox) return;
+
+ var isFirefox = isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
+
+ // For Firefox, listening on click first switched to next image then shows the lightbox.
+ // If you know how to fix this without switching to mousedown event, please.
+ // For other browsers the event is click to make it possiblr to drag picture.
+ var event = isFirefox ? 'mousedown' : 'click'
+
+ e.addEventListener(event, function (evt) {
+ if(!opts.js_modal_lightbox || evt.button != 0) return;
modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed)
+ evt.preventDefault()
showModal(evt)
}, true);
}
diff --git a/javascript/localization.js b/javascript/localization.js
index f92d2d24..1a5a1dbb 100644
--- a/javascript/localization.js
+++ b/javascript/localization.js
@@ -10,10 +10,8 @@ ignore_ids_for_localization={
modelmerger_tertiary_model_name: 'OPTION',
train_embedding: 'OPTION',
train_hypernetwork: 'OPTION',
- txt2img_style_index: 'OPTION',
- txt2img_style2_index: 'OPTION',
- img2img_style_index: 'OPTION',
- img2img_style2_index: 'OPTION',
+ txt2img_styles: 'OPTION',
+ img2img_styles: 'OPTION',
setting_random_artist_categories: 'SPAN',
setting_face_restoration_model: 'SPAN',
setting_realesrgan_enabled_models: 'SPAN',
diff --git a/javascript/progressbar.js b/javascript/progressbar.js
index d6323ed9..ff6d757b 100644
--- a/javascript/progressbar.js
+++ b/javascript/progressbar.js
@@ -1,82 +1,25 @@
// code related to showing and updating progressbar shown as the image is being made
-global_progressbars = {}
-galleries = {}
-galleryObservers = {}
-
-// this tracks launches of window.setTimeout for progressbar to prevent starting a new timeout when the previous is still running
-timeoutIds = {}
-
-function check_progressbar(id_part, id_progressbar, id_progressbar_span, id_skip, id_interrupt, id_preview, id_gallery){
- // gradio 3.8's enlightened approach allows them to create two nested div elements inside each other with same id
- // every time you use gr.HTML(elem_id='xxx'), so we handle this here
- var progressbar = gradioApp().querySelector("#"+id_progressbar+" #"+id_progressbar)
- var progressbarParent
- if(progressbar){
- progressbarParent = gradioApp().querySelector("#"+id_progressbar)
- } else{
- progressbar = gradioApp().getElementById(id_progressbar)
- progressbarParent = null
- }
-
- var skip = id_skip ? gradioApp().getElementById(id_skip) : null
- var interrupt = gradioApp().getElementById(id_interrupt)
-
- if(opts.show_progress_in_title && progressbar && progressbar.offsetParent){
- if(progressbar.innerText){
- let newtitle = '[' + progressbar.innerText.trim() + '] Stable Diffusion';
- if(document.title != newtitle){
- document.title = newtitle;
- }
- }else{
- let newtitle = 'Stable Diffusion'
- if(document.title != newtitle){
- document.title = newtitle;
- }
- }
- }
-
- if(progressbar!= null && progressbar != global_progressbars[id_progressbar]){
- global_progressbars[id_progressbar] = progressbar
- var mutationObserver = new MutationObserver(function(m){
- if(timeoutIds[id_part]) return;
- preview = gradioApp().getElementById(id_preview)
- gallery = gradioApp().getElementById(id_gallery)
+galleries = {}
+storedGallerySelections = {}
+galleryObservers = {}
- if(preview != null && gallery != null){
- preview.style.width = gallery.clientWidth + "px"
- preview.style.height = gallery.clientHeight + "px"
- if(progressbarParent) progressbar.style.width = progressbarParent.clientWidth + "px"
+function rememberGallerySelection(id_gallery){
+ storedGallerySelections[id_gallery] = getGallerySelectedIndex(id_gallery)
+}
- //only watch gallery if there is a generation process going on
- check_gallery(id_gallery);
+function getGallerySelectedIndex(id_gallery){
+ let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item')
+ let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2')
- var progressDiv = gradioApp().querySelectorAll('#' + id_progressbar_span).length > 0;
- if(progressDiv){
- timeoutIds[id_part] = window.setTimeout(function() {
- timeoutIds[id_part] = null
- requestMoreProgress(id_part, id_progressbar_span, id_skip, id_interrupt)
- }, 500)
- } else{
- if (skip) {
- skip.style.display = "none"
- }
- interrupt.style.display = "none"
+ let currentlySelectedIndex = -1
+ galleryButtons.forEach(function(v, i){ if(v==galleryBtnSelected) { currentlySelectedIndex = i } })
- //disconnect observer once generation finished, so user can close selected image if they want
- if (galleryObservers[id_gallery]) {
- galleryObservers[id_gallery].disconnect();
- galleries[id_gallery] = null;
- }
- }
- }
-
- });
- mutationObserver.observe( progressbar, { childList:true, subtree:true })
- }
+ return currentlySelectedIndex
}
+// this is a workaround for https://github.com/gradio-app/gradio/issues/2984
function check_gallery(id_gallery){
let gallery = gradioApp().getElementById(id_gallery)
// if gallery has no change, no need to setting up observer again.
@@ -85,10 +28,16 @@ function check_gallery(id_gallery){
if(galleryObservers[id_gallery]){
galleryObservers[id_gallery].disconnect();
}
- let prevSelectedIndex = selected_gallery_index();
+
+ storedGallerySelections[id_gallery] = -1
+
galleryObservers[id_gallery] = new MutationObserver(function (){
let galleryButtons = gradioApp().querySelectorAll('#'+id_gallery+' .gallery-item')
let galleryBtnSelected = gradioApp().querySelector('#'+id_gallery+' .gallery-item.\\!ring-2')
+ let currentlySelectedIndex = getGallerySelectedIndex(id_gallery)
+ prevSelectedIndex = storedGallerySelections[id_gallery]
+ storedGallerySelections[id_gallery] = -1
+
if (prevSelectedIndex !== -1 && galleryButtons.length>prevSelectedIndex && !galleryBtnSelected) {
// automatically re-open previously selected index (if exists)
activeElement = gradioApp().activeElement;
@@ -120,30 +69,175 @@ function check_gallery(id_gallery){
}
onUiUpdate(function(){
- check_progressbar('txt2img', 'txt2img_progressbar', 'txt2img_progress_span', 'txt2img_skip', 'txt2img_interrupt', 'txt2img_preview', 'txt2img_gallery')
- check_progressbar('img2img', 'img2img_progressbar', 'img2img_progress_span', 'img2img_skip', 'img2img_interrupt', 'img2img_preview', 'img2img_gallery')
- check_progressbar('ti', 'ti_progressbar', 'ti_progress_span', '', 'ti_interrupt', 'ti_preview', 'ti_gallery')
+ check_gallery('txt2img_gallery')
+ check_gallery('img2img_gallery')
})
-function requestMoreProgress(id_part, id_progressbar_span, id_skip, id_interrupt){
- btn = gradioApp().getElementById(id_part+"_check_progress");
- if(btn==null) return;
-
- btn.click();
- var progressDiv = gradioApp().querySelectorAll('#' + id_progressbar_span).length > 0;
- var skip = id_skip ? gradioApp().getElementById(id_skip) : null
- var interrupt = gradioApp().getElementById(id_interrupt)
- if(progressDiv && interrupt){
- if (skip) {
- skip.style.display = "block"
+function request(url, data, handler, errorHandler){
+ var xhr = new XMLHttpRequest();
+ var url = url;
+ xhr.open("POST", url, true);
+ xhr.setRequestHeader("Content-Type", "application/json");
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200) {
+ try {
+ var js = JSON.parse(xhr.responseText);
+ handler(js)
+ } catch (error) {
+ console.error(error);
+ errorHandler()
+ }
+ } else{
+ errorHandler()
+ }
}
- interrupt.style.display = "block"
+ };
+ var js = JSON.stringify(data);
+ xhr.send(js);
+}
+
+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 setTitle(progress){
+ var title = 'Stable Diffusion'
+
+ if(opts.show_progress_in_title && progress){
+ title = '[' + progress.trim() + '] ' + title;
}
+
+ if(document.title != title){
+ document.title = title;
+ }
+}
+
+
+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)+")"
}
-function requestProgress(id_part){
- btn = gradioApp().getElementById(id_part+"_check_progress_initial");
- if(btn==null) return;
+// 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){
+ var dateStart = new Date()
+ var wasEverActive = false
+ var parentProgressbar = progressbarContainer.parentNode
+ var parentGallery = gallery ? gallery.parentNode : null
+
+ var divProgress = document.createElement('div')
+ divProgress.className='progressDiv'
+ divProgress.style.display = opts.show_progressbar ? "" : "none"
+ var divInner = document.createElement('div')
+ divInner.className='progress'
+
+ divProgress.appendChild(divInner)
+ parentProgressbar.insertBefore(divProgress, progressbarContainer)
+
+ if(parentGallery){
+ var livePreview = document.createElement('div')
+ livePreview.className='livePreview'
+ parentGallery.insertBefore(livePreview, gallery)
+ }
+
+ var removeProgressBar = function(){
+ setTitle("")
+ 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){
+ removeProgressBar()
+ return
+ }
+
+ var rect = progressbarContainer.getBoundingClientRect()
+
+ 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)
+ }
+
+
+ setTitle(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){
+ removeProgressBar()
+ return
+ }
+
+ if(elapsedFromStart > 5 && !res.queued && !res.active){
+ removeProgressBar()
+ return
+ }
+
+
+ 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)
+ }
+ }
+ img.src = res.live_preview;
+ }
+
+
+ if(onProgress){
+ onProgress(res)
+ }
+
+ setTimeout(() => {
+ fun(id_task, res.id_live_preview);
+ }, opts.live_preview_refresh_period || 500)
+ }, function(){
+ removeProgressBar()
+ })
+ }
- btn.click();
+ fun(id_task, 0)
}
diff --git a/javascript/textualInversion.js b/javascript/textualInversion.js
index 8061be08..0354b860 100644
--- a/javascript/textualInversion.js
+++ b/javascript/textualInversion.js
@@ -1,8 +1,17 @@
+
function start_training_textual_inversion(){
- requestProgress('ti')
gradioApp().querySelector('#ti_error').innerHTML=''
- return args_to_array(arguments)
+ var id = randomId()
+ requestProgress(id, gradioApp().getElementById('ti_output'), gradioApp().getElementById('ti_gallery'), function(){}, function(progress){
+ gradioApp().getElementById('ti_progress').innerHTML = progress.textinfo
+ })
+
+ var res = args_to_array(arguments)
+
+ res[0] = id
+
+ return res
}
diff --git a/javascript/ui.js b/javascript/ui.js
index 587dd782..ba72623c 100644
--- a/javascript/ui.js
+++ b/javascript/ui.js
@@ -1,4 +1,4 @@
-// various functions for interation with ui.py not large enough to warrant putting them in separate files
+// various functions for interaction with ui.py not large enough to warrant putting them in separate files
function set_theme(theme){
gradioURL = window.location.href
@@ -19,7 +19,7 @@ function selected_gallery_index(){
function extract_image_from_gallery(gallery){
if(gallery.length == 1){
- return gallery[0]
+ return [gallery[0]]
}
index = selected_gallery_index()
@@ -28,7 +28,7 @@ function extract_image_from_gallery(gallery){
return [null]
}
- return gallery[index];
+ return [gallery[index]];
}
function args_to_array(args){
@@ -45,16 +45,33 @@ function switch_to_txt2img(){
return args_to_array(arguments);
}
-function switch_to_img2img(){
+function switch_to_img2img_tab(no){
gradioApp().querySelector('#tabs').querySelectorAll('button')[1].click();
- gradioApp().getElementById('mode_img2img').querySelectorAll('button')[0].click();
+ gradioApp().getElementById('mode_img2img').querySelectorAll('button')[no].click();
+}
+function switch_to_img2img(){
+ switch_to_img2img_tab(0);
+ return args_to_array(arguments);
+}
+
+function switch_to_sketch(){
+ switch_to_img2img_tab(1);
+ return args_to_array(arguments);
+}
+function switch_to_inpaint(){
+ switch_to_img2img_tab(2);
+ return args_to_array(arguments);
+}
+
+function switch_to_inpaint_sketch(){
+ switch_to_img2img_tab(3);
return args_to_array(arguments);
}
function switch_to_inpaint(){
gradioApp().querySelector('#tabs').querySelectorAll('button')[1].click();
- gradioApp().getElementById('mode_img2img').querySelectorAll('button')[1].click();
+ gradioApp().getElementById('mode_img2img').querySelectorAll('button')[2].click();
return args_to_array(arguments);
}
@@ -87,9 +104,11 @@ function create_tab_index_args(tabId, args){
return res
}
-function get_extras_tab_index(){
- const [,,...args] = [...arguments]
- return [get_tab_index('mode_extras'), get_tab_index('extras_resize_mode'), ...args]
+function get_img2img_tab_index() {
+ let res = args_to_array(arguments)
+ res.splice(-2)
+ res[0] = get_tab_index('mode_img2img')
+ return res
}
function create_submit_args(args){
@@ -109,22 +128,54 @@ function create_submit_args(args){
return res
}
+function showSubmitButtons(tabname, show){
+ gradioApp().getElementById(tabname+'_interrupt').style.display = show ? "none" : "block"
+ gradioApp().getElementById(tabname+'_skip').style.display = show ? "none" : "block"
+}
+
function submit(){
- requestProgress('txt2img')
+ rememberGallerySelection('txt2img_gallery')
+ showSubmitButtons('txt2img', false)
+
+ var id = randomId()
+ requestProgress(id, gradioApp().getElementById('txt2img_gallery_container'), gradioApp().getElementById('txt2img_gallery'), function(){
+ showSubmitButtons('txt2img', true)
+
+ })
+
+ var res = create_submit_args(arguments)
+
+ res[0] = id
- return create_submit_args(arguments)
+ return res
}
function submit_img2img(){
- requestProgress('img2img')
+ rememberGallerySelection('img2img_gallery')
+ showSubmitButtons('img2img', false)
- res = create_submit_args(arguments)
+ var id = randomId()
+ requestProgress(id, gradioApp().getElementById('img2img_gallery_container'), gradioApp().getElementById('img2img_gallery'), function(){
+ showSubmitButtons('img2img', true)
+ })
- res[0] = get_tab_index('mode_img2img')
+ var res = create_submit_args(arguments)
+
+ res[0] = id
+ res[1] = get_tab_index('mode_img2img')
return res
}
+function modelmerger(){
+ var 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:')
@@ -140,27 +191,17 @@ function confirm_clear_prompt(prompt, negative_prompt) {
return [prompt, negative_prompt]
}
-
-
opts = {}
-function apply_settings(jsdata){
- console.log(jsdata)
-
- opts = JSON.parse(jsdata)
-
- return jsdata
-}
-
onUiUpdate(function(){
if(Object.keys(opts).length != 0) return;
json_elem = gradioApp().getElementById('settings_json')
if(json_elem == null) return;
- textarea = json_elem.querySelector('textarea')
- jsdata = textarea.value
+ var textarea = json_elem.querySelector('textarea')
+ var jsdata = textarea.value
opts = JSON.parse(jsdata)
-
+ executeCallbacks(optionsChangedCallbacks);
Object.defineProperty(textarea, 'value', {
set: function(newValue) {
@@ -171,6 +212,8 @@ onUiUpdate(function(){
if (oldValue != newValue) {
opts = JSON.parse(textarea.value)
}
+
+ executeCallbacks(optionsChangedCallbacks);
},
get: function() {
var valueProp = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
@@ -180,13 +223,51 @@ onUiUpdate(function(){
json_elem.parentElement.style.display="none"
- if (!txt2img_textarea) {
- txt2img_textarea = gradioApp().querySelector("#txt2img_prompt > label > textarea");
- txt2img_textarea?.addEventListener("input", () => update_token_counter("txt2img_token_button"));
- }
- if (!img2img_textarea) {
- img2img_textarea = gradioApp().querySelector("#img2img_prompt > label > textarea");
- img2img_textarea?.addEventListener("input", () => update_token_counter("img2img_token_button"));
+ 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
+ }
+
+
+ prompt.parentElement.insertBefore(counter, prompt)
+ counter.classList.add("token-counter")
+ prompt.parentElement.style.position = "relative"
+
+ textarea.addEventListener("input", function(){
+ update_token_counter(id_button);
+ });
+ }
+
+ 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){
+ settings_tabs.appendChild(show_all_pages)
+ show_all_pages.onclick = function(){
+ gradioApp().querySelectorAll('#settings > div').forEach(function(elem){
+ elem.style.display = "block";
+ })
+ }
+ }
+})
+
+onOptionsChanged(function(){
+ elem = gradioApp().getElementById('sd_checkpoint_hash')
+ sd_checkpoint_hash = opts.sd_checkpoint_hash || ""
+ shorthash = sd_checkpoint_hash.substr(0,10)
+
+ if(elem && elem.textContent != shorthash){
+ elem.textContent = shorthash
+ elem.title = sd_checkpoint_hash
+ elem.href = "https://google.com/search?q=" + sd_checkpoint_hash
}
})
@@ -220,3 +301,11 @@ function restart_reload(){
return []
}
+
+// Simulate an `input` DOM event for Gradio Textbox component. Needed after you edit its contents in javascript, otherwise your edits
+// will only visible on web page and not sent to python.
+function updateInput(target){
+ let e = new Event("input", { bubbles: true })
+ Object.defineProperty(e, "target", {value: target})
+ target.dispatchEvent(e);
+}