From 6f95dd201acd76b98da9d096691ef5b41fb438bf Mon Sep 17 00:00:00 2001 From: Zixaphir Date: Mon, 24 Jul 2023 13:06:10 -0700 Subject: [PATCH 1/4] Update for basic compatibility with sd-webui-v1.5 --- javascript/civitai_helper.js | 123 ++++++++++++++++++++--------------- scripts/ch_lib/model.py | 7 +- 2 files changed, 74 insertions(+), 56 deletions(-) diff --git a/javascript/civitai_helper.js b/javascript/civitai_helper.js index 11ba250..d24ddbb 100644 --- a/javascript/civitai_helper.js +++ b/javascript/civitai_helper.js @@ -19,12 +19,12 @@ function ch_gradio_version(){ let versions = foot.querySelector(".versions"); if (!versions){return null;} - if (versions.innerHTML.indexOf("gradio: 3.16.2")>0) { + if (versions.textContent.indexOf("gradio: 3.16.2")>0) { return "3.16.2"; } else { return "3.23.0"; } - + } @@ -141,7 +141,7 @@ function getActiveNegativePrompt() { async function open_model_url(event, model_type, search_term){ console.log("start open_model_url"); - //get hidden components of extension + //get hidden components of extension let js_open_url_btn = gradioApp().getElementById("ch_js_open_url_btn"); if (!js_open_url_btn) { return @@ -193,7 +193,7 @@ async function open_model_url(event, model_type, search_term){ } - + console.log("end open_model_url"); @@ -202,7 +202,7 @@ async function open_model_url(event, model_type, search_term){ function add_trigger_words(event, model_type, search_term){ console.log("start add_trigger_words"); - //get hidden components of extension + //get hidden components of extension let js_add_trigger_words_btn = gradioApp().getElementById("ch_js_add_trigger_words_btn"); if (!js_add_trigger_words_btn) { return @@ -238,13 +238,13 @@ function add_trigger_words(event, model_type, search_term){ event.stopPropagation() event.preventDefault() - + } function use_preview_prompt(event, model_type, search_term){ console.log("start use_preview_prompt"); - //get hidden components of extension + //get hidden components of extension let js_use_preview_prompt_btn = gradioApp().getElementById("ch_js_use_preview_prompt_btn"); if (!js_use_preview_prompt_btn) { return @@ -296,7 +296,7 @@ function ch_dl_model_new_version(event, model_path, version_id, download_url){ return } - //get hidden components of extension + //get hidden components of extension let js_dl_model_new_version_btn = gradioApp().getElementById("ch_js_dl_model_new_version_btn"); if (!js_dl_model_new_version_btn) { return @@ -386,7 +386,7 @@ onUiLoaded(() => { if (!replace_preview_text) { replace_preview_text = "replace preview"; } - + // get component @@ -434,7 +434,7 @@ onUiLoaded(() => { .find(el => el.closest('.tabitem').style.display === 'block') ?.id.match(/^(txt2img|img2img)_(.+)_cards$/)[2] - + console.log("found active tab: " + active_extra_tab); switch (active_extra_tab) { @@ -493,7 +493,9 @@ onUiLoaded(() => { extra_network_id = tab_prefix+"_"+js_model_type+"_"+cardid_suffix; // console.log("searching extra_network_node: " + extra_network_id); extra_network_node = gradioApp().getElementById(extra_network_id); + // check if extr network is under thumbnail mode + // XXX thumbnail mode removed in sd-webui v1.5.0 is_thumb_mode = false if (extra_network_node) { if (extra_network_node.className == "extra-network-thumbs") { @@ -519,50 +521,57 @@ onUiLoaded(() => { // replace preview text button replace_preview_btn = card.querySelector(".actions .additional a"); + if (replace_preview_btn==null) { + replace_preview_btn = document.createElement("a"); + } + // check thumb mode if (is_thumb_mode) { additional_node.style.display = null; + if (!ul_node) { + // nothing to do. + continue; + } + if (ch_show_btn_on_thumb) { ul_node.style.background = btn_thumb_background; } else { - //reset - ul_node.style.background = null; // console.log("remove existed buttons"); // remove existed buttons - if (ul_node) { - // find all .a child nodes - let atags = ul_node.querySelectorAll("a"); - - for (let atag of atags) { - //reset display + //reset + ul_node.style.background = null; + // find all .a child nodes + let atags = ul_node.querySelectorAll("a"); + + for (let atag of atags) { + //reset display + atag.style.display = null; + //remove extension's button + if (ch_btn_txts.indexOf(atag.textContent)>=0) { + //need to remove + ul_node.removeChild(atag); + } else { + //do not remove, just reset + atag.textContent = replace_preview_text; atag.style.display = null; - //remove extension's button - if (ch_btn_txts.indexOf(atag.innerHTML)>=0) { - //need to remove - ul_node.removeChild(atag); - } else { - //do not remove, just reset - atag.innerHTML = replace_preview_text; - atag.style.display = null; - atag.style.fontSize = null; - atag.style.position = null; - atag.style.backgroundImage = null; - } + atag.style.fontSize = null; + atag.style.position = null; + atag.style.backgroundImage = null; } - - //also remove br tag in ul - let brtag = ul_node.querySelector("br"); - if (brtag) { - ul_node.removeChild(brtag); - } - } - //just reset and remove nodes, do nothing else - continue; + + //also remove br tag in ul + let brtag = ul_node.querySelector("br"); + if (brtag) { + ul_node.removeChild(brtag); + } } + //just reset and remove nodes, do nothing else + continue; + } else { // full preview mode if (ch_always_display) { @@ -571,19 +580,25 @@ onUiLoaded(() => { additional_node.style.display = null; } - // remove br tag - let brtag = ul_node.querySelector("br"); - if (brtag) { - ul_node.removeChild(brtag); + + if (!ul_node) { + ul_node = document.createElement("ul"); + } else { + // remove br tag + let brtag = ul_node.querySelector("br"); + if (brtag) { + ul_node.removeChild(brtag); + } } } // change replace preview text button into icon if (replace_preview_btn) { - if (replace_preview_btn.innerHTML !== "🖼️") { + if (replace_preview_btn.textContent !== "🖼️") { + ul_node.appendChild(replace_preview_btn); need_to_add_buttons = true; - replace_preview_btn.innerHTML = "🖼️"; + replace_preview_btn.textContent = "🖼️"; if (!is_thumb_mode) { replace_preview_btn.style.fontSize = btn_fontSize; replace_preview_btn.style.margin = btn_margin; @@ -601,7 +616,6 @@ onUiLoaded(() => { continue; } - // search_term node // search_term = subfolder path + model name + ext search_term_node = card.querySelector(".actions .additional .search_term"); @@ -611,7 +625,7 @@ onUiLoaded(() => { } // get search_term - search_term = search_term_node.innerHTML; + search_term = search_term_node.textContent; if (!search_term) { console.log("search_term is empty for cards in " + extra_network_id); continue; @@ -626,7 +640,7 @@ onUiLoaded(() => { // then we need to add 3 buttons to each ul node: let open_url_node = document.createElement("a"); open_url_node.href = "#"; - open_url_node.innerHTML = "🌐"; + open_url_node.textContent = "🌐"; if (!is_thumb_mode) { open_url_node.style.fontSize = btn_fontSize; open_url_node.style.margin = btn_margin; @@ -641,7 +655,7 @@ onUiLoaded(() => { let add_trigger_words_node = document.createElement("a"); add_trigger_words_node.href = "#"; - add_trigger_words_node.innerHTML = "💡"; + add_trigger_words_node.textContent = "💡"; if (!is_thumb_mode) { add_trigger_words_node.style.fontSize = btn_fontSize; add_trigger_words_node.style.margin = btn_margin; @@ -657,7 +671,7 @@ onUiLoaded(() => { let use_preview_prompt_node = document.createElement("a"); use_preview_prompt_node.href = "#"; - use_preview_prompt_node.innerHTML = "🏷️"; + use_preview_prompt_node.textContent = "🏷️"; if (!is_thumb_mode) { use_preview_prompt_node.style.fontSize = btn_fontSize; use_preview_prompt_node.style.margin = btn_margin; @@ -679,12 +693,13 @@ onUiLoaded(() => { ul_node.appendChild(add_trigger_words_node); ul_node.appendChild(use_preview_prompt_node); - - + if (!ul_node.parentElement) { + additional_node.appendChild(ul_node); + } } - + } } @@ -713,7 +728,7 @@ onUiLoaded(() => { // add refresh button to toolbar let ch_refresh = document.createElement("button"); - ch_refresh.innerHTML = "🔁"; + ch_refresh.textContent = "🔁"; ch_refresh.title = "Refresh Civitai Helper's additional buttons"; ch_refresh.className = "lg secondary gradio-button"; ch_refresh.style.fontSize = "200%"; diff --git a/scripts/ch_lib/model.py b/scripts/ch_lib/model.py index 2d65f78..6945978 100644 --- a/scripts/ch_lib/model.py +++ b/scripts/ch_lib/model.py @@ -43,8 +43,11 @@ def get_custom_model_folder(): if shared.cmd_opts.lora_dir and os.path.isdir(shared.cmd_opts.lora_dir): folders["lora"] = shared.cmd_opts.lora_dir - if shared.cmd_opts.lyco_dir and os.path.isdir(shared.cmd_opts.lyco_dir): - folders["lycoris"] = shared.cmd_opts.lyco_dir + try: + if shared.cmd_opts.lyco_dir and os.path.isdir(shared.cmd_opts.lyco_dir): + folders["lycoris"] = shared.cmd_opts.lyco_dir + except: + pass # XXX sd-webui v1.5.0 handles the lyco directory differently. # write model info to file From 06c7dbef02502a6ae33738ae0fdfd44283fe348d Mon Sep 17 00:00:00 2001 From: zixaphir Date: Fri, 28 Jul 2023 14:06:32 -0700 Subject: [PATCH 2/4] replace_preview_button is non-functional. Need to find a solution to use sdwebui's saveCardPreview JS without having access to filename paths. --- javascript/civitai_helper.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/javascript/civitai_helper.js b/javascript/civitai_helper.js index d24ddbb..d4ba778 100644 --- a/javascript/civitai_helper.js +++ b/javascript/civitai_helper.js @@ -413,7 +413,6 @@ onUiLoaded(() => { let search_term = ""; let model_type = ""; let cards = null; - let need_to_add_buttons = false; let is_thumb_mode = false; //get current tab @@ -521,10 +520,6 @@ onUiLoaded(() => { // replace preview text button replace_preview_btn = card.querySelector(".actions .additional a"); - if (replace_preview_btn==null) { - replace_preview_btn = document.createElement("a"); - } - // check thumb mode if (is_thumb_mode) { additional_node.style.display = null; @@ -593,11 +588,10 @@ onUiLoaded(() => { } - // change replace preview text button into icon if (replace_preview_btn) { + // change replace preview text button into icon if (replace_preview_btn.textContent !== "🖼️") { ul_node.appendChild(replace_preview_btn); - need_to_add_buttons = true; replace_preview_btn.textContent = "🖼️"; if (!is_thumb_mode) { replace_preview_btn.style.fontSize = btn_fontSize; @@ -612,7 +606,7 @@ onUiLoaded(() => { } } - if (!need_to_add_buttons) { + if (ul_node.querySelector('.openurl')) { continue; } @@ -631,8 +625,6 @@ onUiLoaded(() => { continue; } - - // if (is_thumb_mode) { // ul_node.style.background = btn_thumb_background; // } @@ -641,6 +633,7 @@ onUiLoaded(() => { let open_url_node = document.createElement("a"); open_url_node.href = "#"; open_url_node.textContent = "🌐"; + open_url_node.className = "openurl"; if (!is_thumb_mode) { open_url_node.style.fontSize = btn_fontSize; open_url_node.style.margin = btn_margin; @@ -656,6 +649,7 @@ onUiLoaded(() => { let add_trigger_words_node = document.createElement("a"); add_trigger_words_node.href = "#"; add_trigger_words_node.textContent = "💡"; + add_trigger_words_node.className = "addtriggerwords"; if (!is_thumb_mode) { add_trigger_words_node.style.fontSize = btn_fontSize; add_trigger_words_node.style.margin = btn_margin; @@ -672,6 +666,7 @@ onUiLoaded(() => { let use_preview_prompt_node = document.createElement("a"); use_preview_prompt_node.href = "#"; use_preview_prompt_node.textContent = "🏷️"; + use_preview_prompt_node.className = "usepreviewprompt"; if (!is_thumb_mode) { use_preview_prompt_node.style.fontSize = btn_fontSize; use_preview_prompt_node.style.margin = btn_margin; From ccb16925b320e695387666ffbbe058fcb15fbcf0 Mon Sep 17 00:00:00 2001 From: zixaphir Date: Fri, 28 Jul 2023 15:28:02 -0700 Subject: [PATCH 3/4] Hack around Lora and Lycoris being treated as the same by webui --- scripts/ch_lib/civitai.py | 76 +++++++++++++++++++++++---------------- scripts/ch_lib/model.py | 57 ++++++++++++++++++----------- 2 files changed, 82 insertions(+), 51 deletions(-) diff --git a/scripts/ch_lib/civitai.py b/scripts/ch_lib/civitai.py index 9002dcd..0ca3150 100644 --- a/scripts/ch_lib/civitai.py +++ b/scripts/ch_lib/civitai.py @@ -207,11 +207,22 @@ def load_model_info_by_search_term(model_type, search_term): if base[:1] == "/": model_info_base = base[1:] - model_folder = model.folders[model_type] - model_info_filename = model_info_base + suffix + model.info_ext - model_info_filepath = os.path.join(model_folder, model_info_filename) + if model_type == "lora" and model.folders['lycoris']: + model_folders = [model.folders[model_type], model.folders['lycoris']] + else: + model_folders = [model.folders[model_type]] - if not os.path.isfile(model_info_filepath): + #model_folder = model.folders[model_type] + for model_folder in model_folders: + model_info_filename = model_info_base + suffix + model.info_ext + model_info_filepath = os.path.join(model_folder, model_info_filename) + + found = os.path.isfile(model_info_filepath) + + if found: + break; + + if not found: util.printD("Can not find model info file: " + model_info_filepath) return @@ -227,7 +238,10 @@ def load_model_info_by_search_term(model_type, search_term): # return: model name list def get_model_names_by_type_and_filter(model_type:str, filter:dict) -> list: - model_folder = model.folders[model_type] + if model_type == "lora" and model.folders['lycoris']: + model_folders = [model.folders[model_type], model.folders['lycoris']] + else: + model_folders = [model.folders[model_type]] # set filter # only get models don't have a civitai info file @@ -245,35 +259,35 @@ def get_model_names_by_type_and_filter(model_type:str, filter:dict) -> list: # get information from filter # only get those model names don't have a civitai model info file model_names = [] - for root, dirs, files in os.walk(model_folder, followlinks=True): - for filename in files: - item = os.path.join(root, filename) - # check extension - base, ext = os.path.splitext(item) - if ext in model.exts: - # find a model + for model_folder in model_folders: + for root, dirs, files in os.walk(model_folder, followlinks=True): + for filename in files: + item = os.path.join(root, filename) + # check extension + base, ext = os.path.splitext(item) + if ext in model.exts: + # find a model - # check filter - if no_info_only: - # check model info file - info_file = base + suffix + model.info_ext - if os.path.isfile(info_file): - continue + # check filter + if no_info_only: + # check model info file + info_file = base + suffix + model.info_ext + if os.path.isfile(info_file): + continue - if empty_info_only: - # check model info file - info_file = base + suffix + model.info_ext - if os.path.isfile(info_file): - # load model info - model_info = model.load_model_info(info_file) - # check content - if model_info: - if "id" in model_info.keys(): - # find a non-empty model info file - continue - - model_names.append(filename) + if empty_info_only: + # check model info file + info_file = base + suffix + model.info_ext + if os.path.isfile(info_file): + # load model info + model_info = model.load_model_info(info_file) + # check content + if model_info: + if "id" in model_info.keys(): + # find a non-empty model info file + continue + model_names.append(filename) return model_names diff --git a/scripts/ch_lib/model.py b/scripts/ch_lib/model.py index 6945978..22bd0ac 100644 --- a/scripts/ch_lib/model.py +++ b/scripts/ch_lib/model.py @@ -44,10 +44,18 @@ def get_custom_model_folder(): folders["lora"] = shared.cmd_opts.lora_dir try: + # pre-1.5.0 if shared.cmd_opts.lyco_dir and os.path.isdir(shared.cmd_opts.lyco_dir): folders["lycoris"] = shared.cmd_opts.lyco_dir + except: - pass # XXX sd-webui v1.5.0 handles the lyco directory differently. + try: + # sd-webui v1.5.1 added a backcompat option for lyco. + if shared.cmd_opts.lyco_dir_backcompat and os.path.isdir(shared.cmd_opts.lyco_dir_backcompat): + folders["lycoris"] = shared.cmd_opts.lyco_dir_backcompat + except: + # XXX v1.5.0 has no options for the Lyco dir: it is hardcoded as 'os.path.join(paths.models_path, "LyCORIS")' + pass # write model info to file @@ -75,20 +83,24 @@ def load_model_info(path): # parameter: model_type - string # return: model name list def get_model_names_by_type(model_type:str) -> list: - - model_folder = folders[model_type] + + if model_type == "lora" and folders['lycoris']: + model_folders = [folders[model_type], folders['lycoris']] + else: + model_folders = [folders[model_type]] # get information from filter # only get those model names don't have a civitai model info file model_names = [] - for root, dirs, files in os.walk(model_folder, followlinks=True): - for filename in files: - item = os.path.join(root, filename) - # check extension - base, ext = os.path.splitext(item) - if ext in exts: - # find a model - model_names.append(filename) + for model_folder in model_folders: + for root, dirs, files in os.walk(model_folder, followlinks=True): + for filename in files: + item = os.path.join(root, filename) + # check extension + base, ext = os.path.splitext(item) + if ext in exts: + # find a model + model_names.append(filename) return model_names @@ -104,19 +116,24 @@ def get_model_path_by_type_and_name(model_type:str, model_name:str) -> str: if not model_name: util.printD("model name can not be empty") return - - folder = folders[model_type] + + if model_type == "lora" and folders['lycoris']: + model_folders = [folders[model_type], folders['lycoris']] + else: + model_folders = [folders[model_type]] + # model could be in subfolder, need to walk. model_root = "" model_path = "" - for root, dirs, files in os.walk(folder, followlinks=True): - for filename in files: - if filename == model_name: - # find model - model_root = root - model_path = os.path.join(root, filename) - return (model_root, model_path) + for folder in model_folders: + for root, dirs, files in os.walk(folder, followlinks=True): + for filename in files: + if filename == model_name: + # find model + model_root = root + model_path = os.path.join(root, filename) + return (model_root, model_path) return From 2d95b6fcf9acb56c8f9243ec25d14767017481bb Mon Sep 17 00:00:00 2001 From: zixaphir Date: Fri, 28 Jul 2023 19:33:37 -0700 Subject: [PATCH 4/4] Hack back in lost Replace Preview functionality. --- javascript/civitai_helper.js | 144 ++++++++++++++++++++++++++--------- 1 file changed, 109 insertions(+), 35 deletions(-) diff --git a/javascript/civitai_helper.js b/javascript/civitai_helper.js index d4ba778..a96ae8d 100644 --- a/javascript/civitai_helper.js +++ b/javascript/civitai_helper.js @@ -32,7 +32,7 @@ function ch_gradio_version(){ // then will click a button to trigger an action // msg is an object, not a string, will be stringify in this function function send_ch_py_msg(msg){ - console.log("run send_ch_py_msg") + console.log("run send_ch_py_msg"); let js_msg_txtbox = gradioApp().querySelector("#ch_js_msg_txtbox textarea"); if (js_msg_txtbox && msg) { // fill to msg box @@ -45,15 +45,15 @@ function send_ch_py_msg(msg){ // get msg from python side from a hidden textbox // normally this is an old msg, need to wait for a new msg function get_ch_py_msg(){ - console.log("run get_ch_py_msg") + console.log("run get_ch_py_msg"); const py_msg_txtbox = gradioApp().querySelector("#ch_py_msg_txtbox textarea"); if (py_msg_txtbox && py_msg_txtbox.value) { console.log("find py_msg_txtbox"); console.log("py_msg_txtbox value: "); - console.log(py_msg_txtbox.value) - return py_msg_txtbox.value + console.log(py_msg_txtbox.value); + return py_msg_txtbox.value; } else { - return "" + return ""; } } @@ -61,7 +61,7 @@ function get_ch_py_msg(){ // get msg from python side from a hidden textbox // it will try once in every sencond, until it reach the max try times const get_new_ch_py_msg = (max_count=3) => new Promise((resolve, reject) => { - console.log("run get_new_ch_py_msg") + console.log("run get_new_ch_py_msg"); let count = 0; let new_msg = ""; @@ -73,11 +73,11 @@ const get_new_ch_py_msg = (max_count=3) => new Promise((resolve, reject) => { if (py_msg_txtbox && py_msg_txtbox.value) { console.log("find py_msg_txtbox"); console.log("py_msg_txtbox value: "); - console.log(py_msg_txtbox.value) + console.log(py_msg_txtbox.value); - new_msg = py_msg_txtbox.value + new_msg = py_msg_txtbox.value; if (new_msg != "") { - find_msg=true + find_msg = true; } } @@ -98,7 +98,7 @@ const get_new_ch_py_msg = (max_count=3) => new Promise((resolve, reject) => { } }, 1000); -}) +}); function getActiveTabType() { @@ -144,7 +144,7 @@ async function open_model_url(event, model_type, search_term){ //get hidden components of extension let js_open_url_btn = gradioApp().getElementById("ch_js_open_url_btn"); if (!js_open_url_btn) { - return + return; } @@ -155,7 +155,7 @@ async function open_model_url(event, model_type, search_term){ "search_term": "", "prompt": "", "neg_prompt": "", - } + }; msg["action"] = "open_url"; @@ -165,14 +165,14 @@ async function open_model_url(event, model_type, search_term){ msg["neg_prompt"] = ""; // fill to msg box - send_ch_py_msg(msg) + send_ch_py_msg(msg); //click hidden button js_open_url_btn.click(); // stop parent event - event.stopPropagation() - event.preventDefault() + event.stopPropagation(); + event.preventDefault(); //check response msg from python let new_py_msg = await get_new_ch_py_msg(); @@ -205,7 +205,7 @@ function add_trigger_words(event, model_type, search_term){ //get hidden components of extension let js_add_trigger_words_btn = gradioApp().getElementById("ch_js_add_trigger_words_btn"); if (!js_add_trigger_words_btn) { - return + return; } @@ -216,7 +216,7 @@ function add_trigger_words(event, model_type, search_term){ "search_term": "", "prompt": "", "neg_prompt": "", - } + }; msg["action"] = "add_trigger_words"; msg["model_type"] = model_type; @@ -228,15 +228,15 @@ function add_trigger_words(event, model_type, search_term){ msg["prompt"] = act_prompt.value; // fill to msg box - send_ch_py_msg(msg) + send_ch_py_msg(msg); //click hidden button js_add_trigger_words_btn.click(); console.log("end add_trigger_words"); - event.stopPropagation() - event.preventDefault() + event.stopPropagation(); + event.preventDefault(); } @@ -247,7 +247,7 @@ function use_preview_prompt(event, model_type, search_term){ //get hidden components of extension let js_use_preview_prompt_btn = gradioApp().getElementById("ch_js_use_preview_prompt_btn"); if (!js_use_preview_prompt_btn) { - return + return; } //msg to python side @@ -257,7 +257,7 @@ function use_preview_prompt(event, model_type, search_term){ "search_term": "", "prompt": "", "neg_prompt": "", - } + }; msg["action"] = "use_preview_prompt"; msg["model_type"] = model_type; @@ -272,15 +272,15 @@ function use_preview_prompt(event, model_type, search_term){ msg["neg_prompt"] = neg_prompt.value; // fill to msg box - send_ch_py_msg(msg) + send_ch_py_msg(msg); //click hidden button js_use_preview_prompt_btn.click(); console.log("end use_preview_prompt"); - event.stopPropagation() - event.preventDefault() + event.stopPropagation(); + event.preventDefault(); } @@ -293,13 +293,13 @@ function ch_dl_model_new_version(event, model_path, version_id, download_url){ // must confirm before downloading let dl_confirm = "\nConfirm to download.\n\nCheck Download Model Section's log and console log for detail."; if (!confirm(dl_confirm)) { - return + return; } //get hidden components of extension let js_dl_model_new_version_btn = gradioApp().getElementById("ch_js_dl_model_new_version_btn"); if (!js_dl_model_new_version_btn) { - return + return; } //msg to python side @@ -308,7 +308,7 @@ function ch_dl_model_new_version(event, model_path, version_id, download_url){ "model_path": "", "version_id": "", "download_url": "", - } + }; msg["action"] = "dl_model_new_version"; msg["model_path"] = model_path; @@ -316,19 +316,61 @@ function ch_dl_model_new_version(event, model_path, version_id, download_url){ msg["download_url"] = download_url; // fill to msg box - send_ch_py_msg(msg) + send_ch_py_msg(msg); //click hidden button js_dl_model_new_version_btn.click(); console.log("end dl_model_new_version"); - event.stopPropagation() - event.preventDefault() + event.stopPropagation(); + event.preventDefault(); } +function waitForEditor(page, type, name) { + let id = page + '_' + type + '_edit_user_metadata'; + + return new Promise(resolve => { + let name_field; + let editor = document.getElementById(id); + + let popup = document.querySelector(".global-popup"); + if (popup != null) { + // hide the editor window so it doesn't get in the user's + // way while we wait for the replace preview functionality + // to become available. + popup.style.display = "none"; + } + + // not only do we need to wait for the editor, + // but also for it to populate with the model metadata. + if (editor != null) { + name_field = editor.querySelector('.extra-network-name'); + if (name_field.textContent.trim() == name) { + return resolve(editor); + } + } + + const observer = new MutationObserver(() => { + let editor = document.getElementById(id); + let name_field; + if (editor != null) { + name_field = editor.querySelector('.extra-network-name'); + if (name_field.textContent.trim() == name) { + resolve(editor); + observer.disconnect(); + } + } + }); + + observer.observe(document.body, { + subtree: true, + childList: true, + }); + }); +} onUiLoaded(() => { @@ -417,7 +459,7 @@ onUiLoaded(() => { //get current tab let active_tab_type = getActiveTabType(); - if (!active_tab_type){active_tab_type = "txt2img";} + if (!active_tab_type) {active_tab_type = "txt2img";} for (const tab_prefix of tab_prefix_list) { if (tab_prefix != active_tab_type) {continue;} @@ -431,7 +473,7 @@ onUiLoaded(() => { //get active extratab const active_extra_tab = Array.from(get_uiCurrentTabContent().querySelectorAll('.extra-network-cards,.extra-network-thumbs')) .find(el => el.closest('.tabitem').style.display === 'block') - ?.id.match(/^(txt2img|img2img)_(.+)_cards$/)[2] + ?.id.match(/^(txt2img|img2img)_(.+)_cards$/)[2]; console.log("found active tab: " + active_extra_tab); @@ -495,7 +537,7 @@ onUiLoaded(() => { // check if extr network is under thumbnail mode // XXX thumbnail mode removed in sd-webui v1.5.0 - is_thumb_mode = false + is_thumb_mode = false; if (extra_network_node) { if (extra_network_node.className == "extra-network-thumbs") { console.log(extra_network_id + " is in thumbnail mode"); @@ -520,6 +562,38 @@ onUiLoaded(() => { // replace preview text button replace_preview_btn = card.querySelector(".actions .additional a"); + if (replace_preview_btn == null) { + /* + * in sdwebui 1.5, the replace preview button has been + * moved to a hard to reach location, so we have to do + * quite a lot to get to its functionality. + */ + + // waste memory by keeping all of this in scope, per card. + let page = active_tab_type; + let type = js_model_type; + let name = card.dataset.name; + + // create the replace_preview_btn, as it no longer exists + replace_preview_btn = document.createElement("a"); + + // create an event handler to redirect a click to the real replace_preview_button + replace_preview_btn.addEventListener("click", function(e) { + // we have to create a whole hidden editor window to access preview replace functionality + extraNetworksEditUserMetadata(e, page, type, name); + + // the editor window takes quite some time to populate. What a waste. + waitForEditor(page, type, name).then(editor => { + // Gather the buttons we need to both replace the preview and close the editor + let cancel_button = editor.querySelector('.edit-user-metadata-buttons button:first-of-type'); + let replace_preview_button = editor.querySelector('.edit-user-metadata-buttons button:nth-of-type(2)'); + + replace_preview_button.click(); + cancel_button.click(); + }); + }); + } + // check thumb mode if (is_thumb_mode) { additional_node.style.display = null; @@ -702,7 +776,7 @@ onUiLoaded(() => { } - let tab_id = "" + let tab_id = ""; let extra_tab = null; let extra_toolbar = null; let extra_network_refresh_btn = null;