feat: LyCORIS (also called "locon") model supported.

feat: Support model deletion function in "Extra Network" model viewer.
fix: "Download all files" now working as expect.
fix: Auto select newest version when load model info from url.
fix: Auto select default '/' folder when load model info from url.
feat: Press "`" key to toggle "Extra Network" model viewer under "img2img" and "txt2img" tab.
feat: Press "x" key to fast paste Civitai model url and load model info under "Civitai Helper" tab,then click "Download Model".
feat: Press "ctrl + x" keys trigger model generate.
refact: Move scripted css to style.css and other style adjusts.
pull/211/head
backflow 2023-07-08 03:32:49 +08:00
parent 920ca3267f
commit b85fe70488
11 changed files with 498 additions and 470 deletions

View File

@ -29,7 +29,7 @@ Civitai: [Civitai Url](https://civitai.com/models/16768/civitai-helper-sd-webui-
- 🖼️: Modified "replace preview" text into this icon
- 🌐: Open this model's Civitai url in a new tab
- 💡: Add this model's trigger words to prompt
- 🏷️: Use this model's preview image's prompt
- 🪞: Use this model's preview image's prompt
* Above buttons support thumbnail mode of Extra Network
* Option to always show additional buttons, to work with touchscreen.

View File

@ -1,68 +1,58 @@
"use strict";
function ch_convert_file_path_to_url(path){
function ch_convert_file_path_to_url(path) {
let prefix = "file=";
let path_to_url = path.replaceAll('\\', '/');
return prefix+path_to_url;
return prefix + path_to_url;
}
function ch_img_node_str(path){
function ch_img_node_str(path) {
return `<img src='${ch_convert_file_path_to_url(path)}' style="width:24px"/>`;
}
function ch_gradio_version(){
function ch_gradio_version() {
let foot = gradioApp().getElementById("footer");
if (!foot){return null;}
if (!foot) { return null; }
let versions = foot.querySelector(".versions");
if (!versions){return null;}
if (!versions) { return null; }
if (versions.innerHTML.indexOf("gradio: 3.16.2")>0) {
if (versions.innerHTML.indexOf("gradio: 3.16.2") > 0) {
return "3.16.2";
} else {
return "3.23.0";
}
}
// send msg to python side by filling a hidden text box
// 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")
function send_ch_py_msg(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
js_msg_txtbox.value = JSON.stringify(msg);
js_msg_txtbox.dispatchEvent(new Event("input"));
}
}
// 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(){
function 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)
console.log("find py_msg_txtbox, value:", py_msg_txtbox.value)
return py_msg_txtbox.value
} else {
return ""
}
}
// 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")
const get_new_ch_py_msg = (max_count = 7) => new Promise((resolve, reject) => {
let count = 0;
let new_msg = "";
let find_msg = false;
@ -71,13 +61,10 @@ const get_new_ch_py_msg = (max_count=3) => new Promise((resolve, reject) => {
count++;
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("find py_msg_txtbox, value: ", py_msg_txtbox.value)
new_msg = py_msg_txtbox.value
if (new_msg != "") {
find_msg=true
find_msg = true
}
}
@ -96,11 +83,9 @@ const get_new_ch_py_msg = (max_count=3) => new Promise((resolve, reject) => {
reject('');
clearInterval(interval);
}
}, 1000);
}, 400);
})
function getActiveTabType() {
const currentTab = get_uiCurrentTabContent();
switch (currentTab.id) {
@ -112,8 +97,6 @@ function getActiveTabType() {
return null;
}
function getActivePrompt() {
const currentTab = get_uiCurrentTabContent();
switch (currentTab.id) {
@ -136,9 +119,8 @@ function getActiveNegativePrompt() {
return null;
}
//button's click function
async function open_model_url(event, model_type, search_term){
// button's click function
async function open_model_url(event, model_type, search_term) {
console.log("start open_model_url");
//get hidden components of extension
@ -147,7 +129,6 @@ async function open_model_url(event, model_type, search_term){
return
}
//msg to python side
let msg = {
"action": "",
@ -157,7 +138,6 @@ async function open_model_url(event, model_type, search_term){
"neg_prompt": "",
}
msg["action"] = "open_url";
msg["model_type"] = model_type;
msg["search_term"] = search_term;
@ -176,8 +156,7 @@ async function open_model_url(event, model_type, search_term){
//check response msg from python
let new_py_msg = await get_new_ch_py_msg();
console.log("new_py_msg:");
console.log(new_py_msg);
// console.log("new_py_msg:", new_py_msg);
//check msg
if (new_py_msg) {
@ -187,19 +166,12 @@ async function open_model_url(event, model_type, search_term){
if (py_msg_json.content.url) {
window.open(py_msg_json.content.url, "_blank");
}
}
}
console.log("end open_model_url");
console.log("end open_model_url")
}
function add_trigger_words(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
@ -208,7 +180,6 @@ function add_trigger_words(event, model_type, search_term){
return
}
//msg to python side
let msg = {
"action": "",
@ -232,16 +203,13 @@ function add_trigger_words(event, model_type, search_term){
//click hidden button
js_add_trigger_words_btn.click();
console.log("end add_trigger_words");
event.stopPropagation()
event.preventDefault()
}
function use_preview_prompt(event, model_type, search_term){
function use_preview_prompt(event, model_type, search_term) {
console.log("start use_preview_prompt");
//get hidden components of extension
@ -281,13 +249,53 @@ function use_preview_prompt(event, model_type, search_term){
event.stopPropagation()
event.preventDefault()
}
async function delete_model(event, model_type, search_term) {
if (!confirm('Comfirm delete model: "' + search_term + '"?')) { return }
//get hidden components of extension
let js_delete_model_btn = gradioApp().getElementById("ch_js_delete_model_btn");
if (!js_delete_model_btn) {
return
}
//msg to python side
let msg = {
"action": "delete_model",
"model_type": model_type,
"search_term": search_term
}
// fill to msg box
send_ch_py_msg(msg)
//click hidden button
js_delete_model_btn.click();
// stop parent event
event.stopPropagation()
event.preventDefault()
//check response msg from python
let new_py_msg = await get_new_ch_py_msg();
console.log("new_py_msg:", new_py_msg);
//check msg
if (new_py_msg) {
let py_msg_json = JSON.parse(new_py_msg);
//check delete result
console.log(py_msg_json)
if (py_msg_json && py_msg_json.result) {
alert('Model delete successfully!!')
let card = event.target.closest('.card')
card.parentNode.removeChild(card)
}
}
}
// download model's new version into SD at python side
function ch_dl_model_new_version(event, model_path, version_id, download_url){
function ch_dl_model_new_version(event, model_path, version_id, download_url) {
console.log("start ch_dl_model_new_version");
// must confirm before downloading
@ -325,10 +333,237 @@ function ch_dl_model_new_version(event, model_path, version_id, download_url){
event.stopPropagation()
event.preventDefault()
}
function createAdditionalButton(btnProps) {
let el = document.createElement("a");
Object.assign(el, btnProps);
el.setAttribute('onclick', btnProps.onclick)
el.className = 'civitai-helper-action'
el.href = "#";
return el
}
function convert_to_py_model_type(js_model_type) {
//get model_type for python side
switch (js_model_type) {
case "textual_inversion": return "ti";
case "hypernetworks": return "hyper";
case "checkpoints": return "ckp";
case "lora": return "lora";
case "lycoris": return "lycoris";
}
}
// add just one model_type cards buttons
function update_card_for_one_tab(model_type, container) {
let ch_btn_txts = ['🌐', '💡', '🪞', '🗑️'];
let replace_preview_text = getTranslation("replace preview");
if (!replace_preview_text) {
replace_preview_text = "replace preview";
}
// get component
let ch_always_display_ckb = gradioApp().querySelector("#ch_always_display_ckb input");
let ch_show_btn_on_thumb_ckb = gradioApp().querySelector("#ch_show_btn_on_thumb_ckb input");
let ch_always_display = false;
let ch_show_btn_on_thumb = false;
if (ch_always_display_ckb) {
ch_always_display = ch_always_display_ckb.checked;
}
if (ch_show_btn_on_thumb_ckb) {
ch_show_btn_on_thumb = ch_show_btn_on_thumb_ckb.checked;
}
//change all "replace preview" into an icon
let extra_network_id = "";
let metadata_button = null;
let additional_node = null;
let replace_preview_btn = null;
let ul_node = null;
let search_term_node = null;
let search_term = "";
let is_thumb_mode = false;
// check if extr network is under thumbnail mode
is_thumb_mode = false;
if (container.className == "extra-network-thumbs") {
is_thumb_mode = true;
// if (!ch_show_btn_on_thumb) {continue;}
}
model_type = convert_to_py_model_type(model_type);
for (let card of container.children) {
//get ul node, which is the parent of all buttons
ul_node = card.querySelector(".actions .additional ul");
if (ul_node.childElementCount > 1) {
// buttons all ready added, just quit
console.log('buttons all ready added, just quit')
return
}
//metadata_buttoncard
metadata_button = card.querySelector(".metadata-button");
//additional node
additional_node = card.querySelector(".actions .additional");
// replace preview text button
replace_preview_btn = card.querySelector(".actions .additional a");
// check thumb mode
if (is_thumb_mode) {
additional_node.style.display = null;
if (ch_show_btn_on_thumb) {
ul_node.style.background = "rgba(0, 0, 0, .5)";
} 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
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;
}
}
//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
additional_node.style.display = ch_always_display ? "block" : null;
// 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) {
replace_preview_btn.className = "civitai-helper-action";
replace_preview_btn.innerHTML = "🖼️";
}
// search_term node
// search_term = subfolder path + model name + ext
search_term_node = card.querySelector(".actions .additional .search_term");
if (!search_term_node) {
console.log("can not find search_term node for cards in " + extra_network_id);
continue;
}
// get search_term
search_term = search_term_node.innerText;
if (!search_term) {
console.log("search_term is empty for cards in " + extra_network_id);
continue;
}
// remove webui added extra checkpoint's sha256 value,
// in file: stable-diffusion-webui/modules/ui_extra_networks_checkpoints.py
// line: 24: "search_term": self.search_terms_from_path(checkpoint.filename) + " " + (checkpoint.sha256 or ""),
search_term = search_term.split(" ")[0];
// if (is_thumb_mode) {
// ul_node.style.background = btn_thumb_background;
// }
// then we need to add 4 buttons to each ul node:
let open_url_node = createAdditionalButton({
innerHTML: "🌐",
title: "Open this model's civitai url",
onclick: "open_model_url(event, '" + model_type + "', '" + search_term + "')"
})
let add_trigger_words_node = createAdditionalButton({
innerHTML: "💡",
title: "Add trigger words to prompt",
onclick: "add_trigger_words(event, '" + model_type + "', '" + search_term + "')"
})
let use_preview_prompt_node = createAdditionalButton({
innerHTML: "🪞",
title: "Use prompt from preview image",
onclick: "use_preview_prompt(event, '" + model_type + "', '" + search_term + "')"
})
let delete_model_node = createAdditionalButton({
innerHTML: "🗑️",
title: "Delete model",
onclick: "delete_model(event, '" + model_type + "', '" + search_term + "')",
})
//add to card
ul_node.appendChild(open_url_node);
//add br if metadata_button exists
if (is_thumb_mode && metadata_button) {
ul_node.appendChild(document.createElement("br"));
}
ul_node.appendChild(add_trigger_words_node);
ul_node.appendChild(use_preview_prompt_node);
ul_node.appendChild(delete_model_node);
}
}
// fast pasete civitai model url and trigger model info loading
async function checkClipboard() {
let text = await navigator.clipboard.readText()
if (text.startsWith('https://civitai.com/models/')) {
let comp = document.querySelector('#model_download_url_txt')
let textarea = comp.querySelector('textarea')
textarea.value = text
textarea.dispatchEvent(new Event('input'))
comp.querySelector('button').click()
}
}
// shotcut key event listener
window.addEventListener('keydown', e => {
let el = e.target
switch (e.key) {
case '`':
if (el.isContentEditable || el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') { e.preventDefault() }
let type = getActiveTabType()
if (type.endsWith('2img')) {
document.querySelector('#' + type + '_extra_networks')?.click()
}
break
case 'x':
let txt = document.querySelector('#quicksettings + .tabs button.selected').innerText
if (txt == 'Civitai Helper')
checkClipboard()
else if (txt.endsWith('2img') && e.altKey)
document.querySelector(`button#${txt}_generate`).click()
break
}
})
onUiLoaded(() => {
@ -336,12 +571,19 @@ onUiLoaded(() => {
let gradio_ver = ch_gradio_version();
console.log("gradio_ver:" + gradio_ver);
// get all extra network tabs
let tab_prefix_list = ["txt2img", "img2img"];
let model_type_list = ["textual_inversion", "hypernetworks", "checkpoints", "lora"];
let cardid_suffix = "cards";
let model_type_list = ["textual_inversion", "hypernetworks", "checkpoints", "lora", "lycoris"];
//get init py msg
// add all model_type cards buttons by tab_prefix
function update_card_form_all_tab(tab_prefix) {
for (let model_type of model_type_list) {
let container_id = [tab_prefix, model_type, 'cards'].join('_')
let container = document.getElementById(container_id)
update_card_for_one_tab(model_type, container);
}
}
// get init py msg
// let init_py_msg_str = get_ch_py_msg();
// let extension_path = "";
// if (!init_py_msg_str) {
@ -354,375 +596,80 @@ onUiLoaded(() => {
// }
// }
// //icon image node as string
// // icon image node as string
// function icon(icon_name){
// let icon_path = extension_path+"/icon/"+icon_name;
// return ch_img_node_str(icon_path);
// }
// check cards number change, and re-craete buttons
function checkPeriodically(tab_prefix, model_type) {
let container_id = '#' + [tab_prefix, model_type, 'cards_html'].join('_')
let container = document.querySelector(container_id + ' ' + container_id)
// update extra network tab pages' cards
// * replace "replace preview" text button into an icon
// * add 3 button to each card:
// - open model url 🌐
// - add trigger words 💡
// - use preview image's prompt 🏷️
// notice: javascript can not get response from python side
// so, these buttons just sent request to python
// then, python side gonna open url and update prompt text box, without telling js side.
function update_card_for_civitai(){
// record current cards size
let len = container.querySelectorAll('.card').length
//css
let btn_margin = "0px 5px";
let btn_fontSize = "200%";
let btn_thumb_fontSize = "100%";
let btn_thumb_display = "inline";
let btn_thumb_pos = "static";
let btn_thumb_backgroundImage = "none";
let btn_thumb_background = "rgba(0, 0, 0, 0.8)";
// we only wait 5s, after that we assumed that DOM will never changed
let millis = 1000 * 5
let ch_btn_txts = ['🌐', '💡', '🏷️'];
let replace_preview_text = getTranslation("replace preview");
if (!replace_preview_text) {
replace_preview_text = "replace preview";
}
// get component
let ch_always_display_ckb = gradioApp().querySelector("#ch_always_display_ckb input");
let ch_show_btn_on_thumb_ckb = gradioApp().querySelector("#ch_show_btn_on_thumb_ckb input");
let ch_always_display = false;
let ch_show_btn_on_thumb = false;
if (ch_always_display_ckb) {
ch_always_display = ch_always_display_ckb.checked;
}
if (ch_show_btn_on_thumb_ckb) {
ch_show_btn_on_thumb = ch_show_btn_on_thumb_ckb.checked;
}
//change all "replace preview" into an icon
let extra_network_id = "";
let extra_network_node = null;
let metadata_button = null;
let additional_node = null;
let replace_preview_btn = null;
let ul_node = null;
let search_term_node = null;
let search_term = "";
let model_type = "";
let cards = null;
let need_to_add_buttons = false;
let is_thumb_mode = false;
//get current tab
let active_tab_type = getActiveTabType();
if (!active_tab_type){active_tab_type = "txt2img";}
for (const tab_prefix of tab_prefix_list) {
if (tab_prefix != active_tab_type) {continue;}
//find out current selected model type tab
let active_extra_tab_type = "";
let extra_tabs = gradioApp().getElementById(tab_prefix+"_extra_tabs");
if (!extra_tabs) {console.log("can not find extra_tabs: " + tab_prefix+"_extra_tabs");}
//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]
console.log("found active tab: " + active_extra_tab);
switch (active_extra_tab) {
case "textual_inversion":
active_extra_tab_type = "ti";
break;
case "hypernetworks":
active_extra_tab_type = "hyper";
break;
case "checkpoints":
active_extra_tab_type = "ckp";
break;
case "lora":
active_extra_tab_type = "lora";
break;
// wait for server response and DOM updates, if cards size changed,
// then our buttons is losted, put them back again
let timer = setInterval(() => {
let new_len = container.querySelectorAll('.card').length
if (len != new_len) {
update_card_for_one_tab(model_type, container.lastElementChild)
clearInterval(timer)
}
for (const js_model_type of model_type_list) {
//get model_type for python side
switch (js_model_type) {
case "textual_inversion":
model_type = "ti";
break;
case "hypernetworks":
model_type = "hyper";
break;
case "checkpoints":
model_type = "ckp";
break;
case "lora":
model_type = "lora";
break;
}
if (!model_type) {
console.log("can not get model_type from: " + js_model_type);
continue;
}
//only handle current sub-tab
if (model_type != active_extra_tab_type) {
continue;
}
console.log("handle active extra tab");
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
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");
is_thumb_mode = true;
// if (!ch_show_btn_on_thumb) {continue;}
}
} else {
console.log("can not find extra_network_node: " + extra_network_id);
continue;
}
// console.log("find extra_network_node: " + extra_network_id);
// get all card nodes
cards = extra_network_node.querySelectorAll(".card");
for (let card of cards) {
//metadata_buttoncard
metadata_button = card.querySelector(".metadata-button");
//additional node
additional_node = card.querySelector(".actions .additional");
//get ul node, which is the parent of all buttons
ul_node = card.querySelector(".actions .additional ul");
// replace preview text button
replace_preview_btn = card.querySelector(".actions .additional a");
// check thumb mode
if (is_thumb_mode) {
additional_node.style.display = null;
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
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;
}
}
//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) {
additional_node.style.display = "block";
} else {
additional_node.style.display = null;
}
// 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 !== "🖼️") {
need_to_add_buttons = true;
replace_preview_btn.innerHTML = "🖼️";
if (!is_thumb_mode) {
replace_preview_btn.style.fontSize = btn_fontSize;
replace_preview_btn.style.margin = btn_margin;
} else {
replace_preview_btn.style.display = btn_thumb_display;
replace_preview_btn.style.fontSize = btn_thumb_fontSize;
replace_preview_btn.style.position = btn_thumb_pos;
replace_preview_btn.style.backgroundImage = btn_thumb_backgroundImage;
}
}
}
if (!need_to_add_buttons) {
continue;
}
// search_term node
// search_term = subfolder path + model name + ext
search_term_node = card.querySelector(".actions .additional .search_term");
if (!search_term_node){
console.log("can not find search_term node for cards in " + extra_network_id);
continue;
}
// get search_term
search_term = search_term_node.innerHTML;
if (!search_term) {
console.log("search_term is empty for cards in " + extra_network_id);
continue;
}
// if (is_thumb_mode) {
// ul_node.style.background = btn_thumb_background;
// }
// 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 = "🌐";
if (!is_thumb_mode) {
open_url_node.style.fontSize = btn_fontSize;
open_url_node.style.margin = btn_margin;
} else {
open_url_node.style.display = btn_thumb_display;
open_url_node.style.fontSize = btn_thumb_fontSize;
open_url_node.style.position = btn_thumb_pos;
open_url_node.style.backgroundImage = btn_thumb_backgroundImage;
}
open_url_node.title = "Open this model's civitai url";
open_url_node.setAttribute("onclick","open_model_url(event, '"+model_type+"', '"+search_term+"')");
let add_trigger_words_node = document.createElement("a");
add_trigger_words_node.href = "#";
add_trigger_words_node.innerHTML = "💡";
if (!is_thumb_mode) {
add_trigger_words_node.style.fontSize = btn_fontSize;
add_trigger_words_node.style.margin = btn_margin;
} else {
add_trigger_words_node.style.display = btn_thumb_display;
add_trigger_words_node.style.fontSize = btn_thumb_fontSize;
add_trigger_words_node.style.position = btn_thumb_pos;
add_trigger_words_node.style.backgroundImage = btn_thumb_backgroundImage;
}
add_trigger_words_node.title = "Add trigger words to prompt";
add_trigger_words_node.setAttribute("onclick","add_trigger_words(event, '"+model_type+"', '"+search_term+"')");
let use_preview_prompt_node = document.createElement("a");
use_preview_prompt_node.href = "#";
use_preview_prompt_node.innerHTML = "🏷️";
if (!is_thumb_mode) {
use_preview_prompt_node.style.fontSize = btn_fontSize;
use_preview_prompt_node.style.margin = btn_margin;
} else {
use_preview_prompt_node.style.display = btn_thumb_display;
use_preview_prompt_node.style.fontSize = btn_thumb_fontSize;
use_preview_prompt_node.style.position = btn_thumb_pos;
use_preview_prompt_node.style.backgroundImage = btn_thumb_backgroundImage;
}
use_preview_prompt_node.title = "Use prompt from preview image";
use_preview_prompt_node.setAttribute("onclick","use_preview_prompt(event, '"+model_type+"', '"+search_term+"')");
//add to card
ul_node.appendChild(open_url_node);
//add br if metadata_button exists
if (is_thumb_mode && metadata_button) {
ul_node.appendChild(document.createElement("br"));
}
ul_node.appendChild(add_trigger_words_node);
ul_node.appendChild(use_preview_prompt_node);
}
millis -= 500
if (millis <= 0) {
clearInterval(timer)
}
}
}, 500)
}
let tab_id = ""
let extra_tab = null;
let extra_toolbar = null;
let extra_network_refresh_btn = null;
//add refresh button to extra network's toolbar
for (let prefix of tab_prefix_list) {
tab_id = prefix + "_extra_tabs";
// add refresh button to extra network's toolbar
for (let tab_prefix of tab_prefix_list) {
tab_id = tab_prefix + "_extra_tabs";
extra_tab = gradioApp().getElementById(tab_id);
//get toolbar
//get Refresh button
extra_network_refresh_btn = gradioApp().getElementById(prefix+"_extra_refresh");
extra_network_refresh_btn = gradioApp().getElementById(tab_prefix + "_extra_refresh");
if (!extra_network_refresh_btn){
if (!extra_network_refresh_btn) {
console.log("can not get extra network refresh button for " + tab_id);
continue;
}
// add refresh button to toolbar
let ch_refresh = document.createElement("button");
ch_refresh.innerHTML = "🔁";
ch_refresh.innerHTML = "🔄️";
ch_refresh.title = "Refresh Civitai Helper's additional buttons";
ch_refresh.className = "lg secondary gradio-button";
ch_refresh.style.fontSize = "200%";
ch_refresh.onclick = update_card_for_civitai;
ch_refresh.style.fontSize = "2em";
ch_refresh.onclick = () => update_card_form_all_tab(tab_prefix)
extra_network_refresh_btn.parentNode.appendChild(ch_refresh);
// listen to refresh buttons' click event
// check and re-add buttons back on
document.getElementById(tab_prefix + '_extra_refresh').addEventListener('click', e => {
let model_type = e.target.closest('.tab-nav').querySelector('button.selected').innerText.replaceAll(' ', '_').toLowerCase()
checkPeriodically(tab_prefix, model_type)
})
// listen to "Extra Networks" toggle button's click event,
// then initialiy add all buttons, only trigger once,
// after that all updates are trigger by refresh button click.
document.getElementById(tab_prefix + '_extra_networks').addEventListener('click', () => {
// wait UI updates
setTimeout(() => update_card_form_all_tab(tab_prefix), 1500);
}, { once: true })
}
//run it once
update_card_for_civitai();
});

View File

@ -23,7 +23,8 @@ model_type_dict = {
"TextualInversion": "ti",
"Hypernetwork": "hyper",
"LORA": "lora",
"LoCon": "lora",
"LoCon": "lycoris",
"LyCORIS": "lycoris",
}
@ -277,6 +278,7 @@ def get_model_names_by_type_and_filter(model_type:str, filter:dict) -> list:
return model_names
def get_model_names_by_input(model_type, empty_info_only):
return get_model_names_by_type_and_filter(model_type, {"empty_info_only":empty_info_only})
@ -365,9 +367,7 @@ def get_preview_image_by_model_path(model_path:str, max_size_preview, skip_nsfw_
# search local model by version id in 1 folder, no subfolder
# return - model_info
def search_local_model_info_by_version_id(folder:str, version_id:int) -> dict:
util.printD("Searching local model by version id")
util.printD("folder: " + folder)
util.printD("version_id: " + str(version_id))
util.printD("Searching local model by version id: " + str(version_id) + "in folder: " + folder)
if not folder:
util.printD("folder is none")
@ -414,9 +414,6 @@ def search_local_model_info_by_version_id(folder:str, version_id:int) -> dict:
return
# check new version for a model by model path
# return (model_path, model_id, model_name, new_verion_id, new_version_name, description, download_url, img_url)
def check_model_new_version_by_path(model_path:str, delay:float=1) -> tuple:
@ -603,10 +600,46 @@ def check_models_new_version_by_model_types(model_types:list, delay:float=1) ->
# add to list
new_versions.append(r)
return new_versions
# delete model file by model type and search_term
# parameter: model_type, search_term
# return: delete result
def delete_model_by_search_term(model_type:str, search_term:str):
if model_type not in model.folders.keys():
msg = f"unknow model type: {model_type}"
util.printD(msg)
return
model_type_key = {k for k in model_type_dict if model_type_dict[k] == model_type}
util.printD(f"Find {model_type_key} model file: '{search_term}'")
# search_term = subfolderpath + model name + ext. And it always start with a / even there is no sub folder
base, ext = os.path.splitext(search_term)
model_info_base = base
if base[:1] == "/":
model_info_base = base[1:]
# find 3 kinds of files: .model.info, .safetensor and .priview.jpeg
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)
model_image_filepath = os.path.join(model_folder, model_info_base + ".preview.png")
model_filename = model_folder + search_term
if os.path.isfile(model_info_filepath):
os.remove(model_info_filepath)
if os.path.isfile(model_image_filepath):
os.remove(model_image_filepath)
result = False
if os.path.isfile(model_filename):
os.remove(model_filename)
result = True
else:
print("Error: Model file: %s not found" % model_filename)
return result

View File

@ -10,6 +10,13 @@ dl_ext = ".downloading"
# disable ssl warning info
requests.packages.urllib3.disable_warnings()
def human_readable_size(size, decimal_places=2):
for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']:
if size < 1024.0 or unit == 'PiB':
break
size /= 1024.0
return f"{size:.{decimal_places}f} {unit}"
# output is downloaded file path
def dl(url, folder, filename, filepath):
util.printD("Start downloading from: " + url)
@ -35,7 +42,7 @@ def dl(url, folder, filename, filepath):
# get file size
total_size = 0
total_size = int(rh.headers['Content-Length'])
util.printD(f"File size: {total_size}")
util.printD(f"File size: {human_readable_size(total_size)}")
# if file_path is empty, need to get file name from download url's header
if not file_path:
@ -65,7 +72,7 @@ def dl(url, folder, filename, filepath):
count = 2
new_base = base
while os.path.isfile(file_path):
util.printD("Target file already exist.")
util.printD("Target file'" + file_path + "'already exist.")
# re-name
new_base = base + "_" + str(count)
file_path = new_base + ext
@ -81,8 +88,8 @@ def dl(url, folder, filename, filepath):
downloaded_size = 0
if os.path.exists(dl_file_path):
downloaded_size = os.path.getsize(dl_file_path)
util.printD(f"Downloaded size: {downloaded_size}")
if downloaded_size > 0:
util.printD(f"Downloaded size: {downloaded_size}")
# create header range
headers = {'Range': 'bytes=%d-' % downloaded_size}
@ -101,15 +108,15 @@ def dl(url, folder, filename, filepath):
f.flush()
# progress
progress = int(50 * downloaded_size / total_size)
progress = int(100 * downloaded_size / total_size)
sys.stdout.reconfigure(encoding='utf-8')
sys.stdout.write("\r[%s%s] %d%%" % ('-' * progress, ' ' * (50 - progress), 100 * downloaded_size / total_size))
sys.stdout.write("\r[%s%s] %d%%" % ('=' * progress, ' ' * (100 - progress), 100 * downloaded_size / total_size))
sys.stdout.flush()
print()
# rename file
os.rename(dl_file_path, file_path)
util.printD(f"File Downloaded to: {file_path}")
util.printD(f"File save to: {file_path}")
return file_path

View File

@ -175,6 +175,18 @@ def use_preview_image_prompt(msg):
return [preview_prompt, preview_neg_prompt, preview_prompt, preview_neg_prompt]
def delete_model(msg):
util.printD("Start delete_model")
result = msg_handler.parse_js_msg(msg)
if not result:
util.printD("Parsing js ms failed")
return
model_type = result["model_type"]
search_term = result["search_term"]
result = civitai.delete_model_by_search_term(model_type, search_term)
return json.dumps({"result": result})
# download model's new verson by model path, version id and download url
# output is a md log
@ -251,6 +263,6 @@ def dl_model_new_version(msg, max_size_preview, skip_nsfw_preview):
# then, get preview image
civitai.get_preview_image_by_model_path(new_model_path, max_size_preview, skip_nsfw_preview)
output = "Done. Model downloaded to: " + new_model_path
output = "Done, model save to: " + new_model_path
util.printD(output)
return output

View File

@ -17,6 +17,7 @@ folders = {
"hyper": os.path.join(root_path, "models", "hypernetworks"),
"ckp": os.path.join(root_path, "models", "Stable-diffusion"),
"lora": os.path.join(root_path, "models", "Lora"),
"lycoris": os.path.join(root_path, "models", "LyCORIS"),
}
exts = (".bin", ".pt", ".safetensors", ".ckpt")
@ -48,7 +49,7 @@ def get_custom_model_folder():
# write model info to file
def write_model_info(path, model_info):
util.printD("Write model info to file: " + path)
util.printD("Write model info to: " + path)
with open(os.path.realpath(path), 'w') as f:
f.write(json.dumps(model_info, indent=4))

View File

@ -264,7 +264,7 @@ def get_model_info_by_url(model_url_or_id:str) -> tuple:
subfolders = []
# add default root folder
subfolders.append("/")
subfolders.append(os.sep)
util.printD("Get following info for downloading:")
util.printD(f"model_name:{model_name}")
@ -436,41 +436,29 @@ def dl_model_by_input(model_info:dict, model_type:str, subfolder_str:str, versio
util.printD(output)
return output
version_id = ver_info["id"]
modelVersions = model_info['modelVersions']
if dl_all_bool:
# get all download url from files info
# some model versions have multiple files
download_urls = []
if "files" in ver_info.keys():
for file_info in ver_info["files"]:
if "downloadUrl" in file_info.keys():
download_urls.append(file_info["downloadUrl"])
if not len(download_urls):
if "downloadUrl" in ver_info.keys():
download_urls.append(ver_info["downloadUrl"])
# check if this model is already existed
r = civitai.search_local_model_info_by_version_id(model_folder, version_id)
if r:
output = "This model version is already existed"
util.printD(output)
return output
# download
filepath = ""
for url in download_urls:
model_filepath = downloader.dl(url, model_folder, None, None)
if not model_filepath:
idx = 0
for modelVer in modelVersions:
# check if this model is already existed
r = civitai.search_local_model_info_by_version_id(model_folder, modelVer["id"])
if r:
util.printD("Model file: '" + r["files"][0]["name"] + "' already existed, skiping it.")
continue
filepath = downloader.dl(modelVer["downloadUrl"], model_folder, None, None)
if not filepath:
output = "Downloading failed, check console log for detail"
util.printD(output)
return output
continue
if url == ver_info["downloadUrl"]:
filepath = model_filepath
idx = idx + 1
save_info_and_preview_image(filepath, modelVer, max_size_preview, skip_nsfw_preview)
util.printD(str(idx) + " of " + str(len(modelVersions)) + " files downloaded, save to: " + filepath)
output = "Done, " + str(idx) + "/" + str(len(modelVersions)) + " files downloaded"
else:
# only download one file
# get download url
@ -487,17 +475,18 @@ def dl_model_by_input(model_info:dict, model_type:str, subfolder_str:str, versio
util.printD(output)
return output
# get version info
version_info = [mv for mv in modelVersions if mv["id"] == ver_info["id"]][0]
if not version_info:
output = "Model downloaded, but failed to get version info, check console log for detail. Model saved to: " + filepath
util.printD(output)
return output
if not filepath:
filepath = model_filepath
output = save_info_and_preview_image(filepath, version_info, max_size_preview, skip_nsfw_preview)
# get version info
version_info = civitai.get_version_info_by_version_id(version_id)
if not version_info:
output = "Model downloaded, but failed to get version info, check console log for detail. Model saved to: " + filepath
util.printD(output)
return output
return output
def save_info_and_preview_image(filepath:str, version_info:dict, max_size_preview:bool, skip_nsfw_preview:bool):
# write version info to file
base, ext = os.path.splitext(filepath)
info_file = base + civitai.suffix + model.info_ext
@ -506,6 +495,7 @@ def dl_model_by_input(model_info:dict, model_type:str, subfolder_str:str, versio
# then, get preview image
civitai.get_preview_image_by_model_path(filepath, max_size_preview, skip_nsfw_preview)
output = "Done. Model downloaded to: " + filepath
output = "Done, model save to: " + filepath
util.printD(output)
return output

View File

@ -4,7 +4,7 @@ import json
from . import util
# action list
js_actions = ("open_url", "add_trigger_words", "use_preview_prompt", "dl_model_new_version")
js_actions = ("open_url", "add_trigger_words", "use_preview_prompt", "delete_model", "dl_model_new_version")
py_actions = ("open_url")

View File

@ -60,7 +60,7 @@ def download_file(url, path):
r.raw.decode_content = True
shutil.copyfileobj(r.raw, f)
printD("File downloaded to: " + path)
printD("File save to: " + path)
# get subfolder list
def get_subfolders(folder:str) -> list:

View File

@ -81,7 +81,9 @@ def on_ui_tabs():
if r:
model_info, model_name, model_type, subfolders, version_strs = r
return [model_info, model_name, model_type, dl_subfolder_drop.update(choices=subfolders), dl_version_drop.update(choices=version_strs)]
value = os.sep if len(subfolders) == 1 else ""
return [model_info, model_name, model_type, dl_subfolder_drop.update(choices=subfolders, value=value), dl_version_drop.update(choices=version_strs, value=version_strs[0])]
# ====UI====
with gr.Blocks(analytics_enabled=False) as civitai_helper:
@ -133,7 +135,7 @@ def on_ui_tabs():
with gr.Box(elem_classes="ch_box"):
with gr.Column():
gr.Markdown("### Download Model")
with gr.Row():
with gr.Row(elem_id="model_download_url_txt"):
dl_model_url_or_id_txtbox = gr.Textbox(label="Civitai URL", lines=1, value="")
dl_model_info_btn = gr.Button(value="1. Get Model Info by Civitai Url", variant="primary")
@ -181,6 +183,7 @@ def on_ui_tabs():
js_open_url_btn = gr.Button(value="Open Model Url", visible=False, elem_id="ch_js_open_url_btn")
js_add_trigger_words_btn = gr.Button(value="Add Trigger Words", visible=False, elem_id="ch_js_add_trigger_words_btn")
js_use_preview_prompt_btn = gr.Button(value="Use Prompt from Preview Image", visible=False, elem_id="ch_js_use_preview_prompt_btn")
js_use_delete_model_btn = gr.Button(value="Delete Model", visible=False, elem_id="ch_js_delete_model_btn")
js_dl_model_new_version_btn = gr.Button(value="Download Model's new version", visible=False, elem_id="ch_js_dl_model_new_version_btn")
# ====events====
@ -207,6 +210,7 @@ def on_ui_tabs():
js_open_url_btn.click(js_action_civitai.open_model_url, inputs=[js_msg_txtbox, open_url_with_js_ckb], outputs=py_msg_txtbox)
js_add_trigger_words_btn.click(js_action_civitai.add_trigger_words, inputs=[js_msg_txtbox], outputs=[txt2img_prompt, img2img_prompt])
js_use_preview_prompt_btn.click(js_action_civitai.use_preview_image_prompt, inputs=[js_msg_txtbox], outputs=[txt2img_prompt, txt2img_neg_prompt, img2img_prompt, img2img_neg_prompt])
js_use_delete_model_btn.click(js_action_civitai.delete_model, inputs=[js_msg_txtbox], outputs=[py_msg_txtbox])
js_dl_model_new_version_btn.click(js_action_civitai.dl_model_new_version, inputs=[js_msg_txtbox, max_size_preview_ckb, skip_nsfw_preview_ckb], outputs=dl_log_md)
# the third parameter is the element id on html, with a "tab_" as prefix

View File

@ -2,17 +2,51 @@ blockquote ul {
list-style:disc;
margin:4px 40px;
}
blockquote ol {
list-style:decimal;
margin:4px 40px;
}
.block.padded.ch_box {
padding: 10px !important;
}
.block.padded.ch_vpadding {
padding: 10px 0 !important;
}
.civitai-helper-action {
font-size: 1.8em;
line-height: 1;
vertical-align: middle;
opacity: .7;
}
.civitai-helper-action:hover {
filter: drop-shadow(2px 4px 6px black);
opacity: 1;
}
.extra-network-cards .civitai-helper-action:nth-child(2) {
margin: 0 .1em 0 .15em;
font-size: 3em;
}
.extra-network-thumbs .card {
height: 8rem !important;
width: 8rem !important;
}
.extra-network-thumbs .civitai-helper-action {
background-image: none !important;
display: inline-block !important;
position: static !important;
font-size: 1.25em !important;
line-height: 1.25em;
}
.extra-network-thumbs .civitai-helper-action:nth-child(2) {
font-size: 2em !important;
height: 1.35em !important;
margin-left: 0.25em;
}
.extra-network-thumbs .card ul {
position: absolute;
width: 100%;
height: 2em;
display: flex;
justify-content: space-between;
align-items: center;
}