diff --git a/.gitattributes b/.gitattributes index a599a901f4c9b1f561b97b72318356286e1406ca..a314bc9c5d811100bc2f8b2966ffb2d39ddccc69 100644 --- a/.gitattributes +++ b/.gitattributes @@ -36,3 +36,4 @@ Auto-Photoshop-StableDiffusion-Plugin/docs/heal_brush.gif filter=lfs diff=lfs me Auto-Photoshop-StableDiffusion-Plugin/docs/inpainting.gif filter=lfs diff=lfs merge=lfs -text Auto-Photoshop-StableDiffusion-Plugin/docs/outpainting.gif filter=lfs diff=lfs merge=lfs -text Auto-Photoshop-StableDiffusion-Plugin/docs/prompt_shortcut.gif filter=lfs diff=lfs merge=lfs -text +Stable-Diffusion-Webui-Civitai-Helper/img/all_in_one.png filter=lfs diff=lfs merge=lfs -text diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/all_in_one.png b/Stable-Diffusion-Webui-Civitai-Helper/img/all_in_one.png new file mode 100644 index 0000000000000000000000000000000000000000..7db1d22b4c770224d6619813cdf975fce52eb21e --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/img/all_in_one.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bedc58968db1a83c84e57d94327c4e27f5cd6fbd93c45a59627a5257b06c8a47 +size 1104345 diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/check_model_new_version.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/check_model_new_version.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1d05e4490b2ff7a3a82edee277a7130aee149d55 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/check_model_new_version.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/check_model_new_version_output.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/check_model_new_version_output.jpg new file mode 100644 index 0000000000000000000000000000000000000000..47761f788c50549355c1d2f58cffc3032cda3ebd Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/check_model_new_version_output.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/download_model.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/download_model.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f6c052ccfca850008f7a87e7dfd8b45d2ec17209 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/download_model.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/extension_tab.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/extension_tab.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a9e0dbe1d1164117cd11064ebbf220377a495c2e Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/extension_tab.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/extra_network.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/extra_network.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25b2acbdb4afefb348f058a9d45fd8a090eda8ca Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/extra_network.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/get_one_model_info.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/get_one_model_info.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9de82e031c5320d45fef054026ada12f3026e776 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/get_one_model_info.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/model_card.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/model_card.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6ceb33f68f91f3bf3eef625bc18ee427e1002456 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/model_card.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/model_info_file.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/model_info_file.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d9e36c8e69a3feb90034a2c205a4d4c27aa43bd7 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/model_info_file.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/other_setting.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/other_setting.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9b9a342410e65bcf616978530c79a636ab173f4e Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/other_setting.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/refresh_ch.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/refresh_ch.jpg new file mode 100644 index 0000000000000000000000000000000000000000..93b4ab70f94ff1718cebc3880f34c4dce45ddfc8 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/refresh_ch.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/thumb_mode.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/thumb_mode.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1044e68cf89c9f0faa1e1f74a8a7cbb5d97809d6 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/thumb_mode.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/blame_sdweui_update_to_this_ext.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/blame_sdweui_update_to_this_ext.jpg new file mode 100644 index 0000000000000000000000000000000000000000..59ccb37188850686ef48248d2f565098403a0d99 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/blame_sdweui_update_to_this_ext.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part1.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..76acd20220c8f3c22619e5d5f4541ce2aeefbeb4 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part1.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part2.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0f231f6f8b9fb981fd9b52ff04aa44f26abf4ddb Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part2.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part3.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fc032c496932463b958444048200dd43068984ca Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part3.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part4.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6ffd6197fe4889e946dd2c24d2192b8940c1bddc Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/changed_model_folder_name_then_forget_part4.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part1.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..26567730c1a3253244d9a8230b90456add779b69 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part1.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part2.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..54b9a2cd28a2f57e43a3f2f0b92c68dbd1c52123 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part2.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part3.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8c0d4ce269af46779bdf4981a7bebf40a4768559 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part3.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part4.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9ddad8e72b21e5148b14a1e4170a6a774a8d2c4c Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/css_issue_part4.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/did_not_relaunch_sdwebui.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/did_not_relaunch_sdwebui.jpg new file mode 100644 index 0000000000000000000000000000000000000000..984228d4cffa6367e5191bb756350f7611b42091 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/did_not_relaunch_sdwebui.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/do_not_even_use_this_ext.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/do_not_even_use_this_ext.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e4be133af1c95e21fce1e6b9b4a26f312722ccb5 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/do_not_even_use_this_ext.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/have_not_scan_model.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/have_not_scan_model.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fc20a79fd6c096d1a2df8d612d06f89a323cdbbd Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/have_not_scan_model.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/have_not_update_sdwebui.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/have_not_update_sdwebui.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3fb704c1f4ce3ef9ea423d2bab3eeff9eab7fe4f Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/have_not_update_sdwebui.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/request_a_feature_it_already_has.jpg b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/request_a_feature_it_already_has.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e1eab1df869661eab9475344d28eed4c59129e0 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/img/user_claim_wall/request_a_feature_it_already_has.jpg differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/javascript/civitai_helper.js b/Stable-Diffusion-Webui-Civitai-Helper/javascript/civitai_helper.js new file mode 100644 index 0000000000000000000000000000000000000000..4454d0d4d4b73ec0436beb570e65f943aebfe97f --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/javascript/civitai_helper.js @@ -0,0 +1,728 @@ +"use strict"; + + +function ch_convert_file_path_to_url(path){ + let prefix = "file="; + let path_to_url = path.replaceAll('\\', '/'); + return prefix+path_to_url; +} + +function ch_img_node_str(path){ + return ``; +} + + +function ch_gradio_version(){ + let foot = gradioApp().getElementById("footer"); + if (!foot){return null;} + + let versions = foot.querySelector(".versions"); + if (!versions){return null;} + + 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") + 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(){ + 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 + } 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") + + let count = 0; + let new_msg = ""; + let find_msg = false; + const interval = setInterval(() => { + const py_msg_txtbox = gradioApp().querySelector("#ch_py_msg_txtbox textarea"); + 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) + + new_msg = py_msg_txtbox.value + if (new_msg != "") { + find_msg=true + } + } + + if (find_msg) { + //clear msg in both sides + py_msg_txtbox.value = ""; + py_msg_txtbox.dispatchEvent(new Event("input")); + + resolve(new_msg); + clearInterval(interval); + } else if (count > max_count) { + //clear msg in both sides + py_msg_txtbox.value = ""; + py_msg_txtbox.dispatchEvent(new Event("input")); + + reject(''); + clearInterval(interval); + } + + }, 1000); +}) + + +function getActiveTabType() { + const currentTab = get_uiCurrentTabContent(); + switch (currentTab.id) { + case "tab_txt2img": + return "txt2img"; + case "tab_img2img": + return "img2img"; + } + return null; +} + + + +function getActivePrompt() { + const currentTab = get_uiCurrentTabContent(); + switch (currentTab.id) { + case "tab_txt2img": + return currentTab.querySelector("#txt2img_prompt textarea"); + case "tab_img2img": + return currentTab.querySelector("#img2img_prompt textarea"); + } + return null; +} + +function getActiveNegativePrompt() { + const currentTab = get_uiCurrentTabContent(); + switch (currentTab.id) { + case "tab_txt2img": + return currentTab.querySelector("#txt2img_neg_prompt textarea"); + case "tab_img2img": + return currentTab.querySelector("#img2img_neg_prompt textarea"); + } + return null; +} + + +//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 + let js_open_url_btn = gradioApp().getElementById("ch_js_open_url_btn"); + if (!js_open_url_btn) { + return + } + + + //msg to python side + let msg = { + "action": "", + "model_type": "", + "search_term": "", + "prompt": "", + "neg_prompt": "", + } + + + msg["action"] = "open_url"; + msg["model_type"] = model_type; + msg["search_term"] = search_term; + msg["prompt"] = ""; + msg["neg_prompt"] = ""; + + // fill to msg box + send_ch_py_msg(msg) + + //click hidden button + js_open_url_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:"); + console.log(new_py_msg); + + //check msg + if (new_py_msg) { + let py_msg_json = JSON.parse(new_py_msg); + //check for url + if (py_msg_json && py_msg_json.content) { + if (py_msg_json.content.url) { + window.open(py_msg_json.content.url, "_blank"); + } + + } + + + } + + + console.log("end open_model_url"); + + +} + +function add_trigger_words(event, model_type, search_term){ + console.log("start add_trigger_words"); + + //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 + } + + + //msg to python side + let msg = { + "action": "", + "model_type": "", + "search_term": "", + "prompt": "", + "neg_prompt": "", + } + + msg["action"] = "add_trigger_words"; + msg["model_type"] = model_type; + msg["search_term"] = search_term; + msg["neg_prompt"] = ""; + + // get active prompt + let act_prompt = getActivePrompt(); + msg["prompt"] = act_prompt.value; + + // fill to msg box + send_ch_py_msg(msg) + + //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){ + console.log("start use_preview_prompt"); + + //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 + } + + //msg to python side + let msg = { + "action": "", + "model_type": "", + "search_term": "", + "prompt": "", + "neg_prompt": "", + } + + msg["action"] = "use_preview_prompt"; + msg["model_type"] = model_type; + msg["search_term"] = search_term; + + // get active prompt + let act_prompt = getActivePrompt(); + msg["prompt"] = act_prompt.value; + + // get active neg prompt + let neg_prompt = getActiveNegativePrompt(); + msg["neg_prompt"] = neg_prompt.value; + + // fill to msg box + send_ch_py_msg(msg) + + //click hidden button + js_use_preview_prompt_btn.click(); + + console.log("end use_preview_prompt"); + + event.stopPropagation() + event.preventDefault() + +} + + + +// download model's new version into SD at python side +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 + let dl_confirm = "\nConfirm to download.\n\nCheck Download Model Section's log and console log for detail."; + if (!confirm(dl_confirm)) { + 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 + } + + //msg to python side + let msg = { + "action": "", + "model_path": "", + "version_id": "", + "download_url": "", + } + + msg["action"] = "dl_model_new_version"; + msg["model_path"] = model_path; + msg["version_id"] = version_id; + msg["download_url"] = download_url; + + // fill to msg box + 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() + + +} + + +onUiLoaded(() => { + + //get gradio version + 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"; + + //get init py msg + // let init_py_msg_str = get_ch_py_msg(); + // let extension_path = ""; + // if (!init_py_msg_str) { + // console.log("Can not get init_py_msg"); + // } else { + // init_py_msg = JSON.parse(init_py_msg_str); + // if (init_py_msg) { + // extension_path = init_py_msg.extension_path; + // console.log("get extension path: " + extension_path); + // } + // } + + // //icon image node as string + // function icon(icon_name){ + // let icon_path = extension_path+"/icon/"+icon_name; + // return ch_img_node_str(icon_path); + // } + + + // 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(){ + + //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)"; + + 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; + } + + + 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); + + + + + } + + + } + } + + + } + + + 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"; + extra_tab = gradioApp().getElementById(tab_id); + + //get toolbar + //get Refresh button + extra_network_refresh_btn = gradioApp().getElementById(prefix+"_extra_refresh"); + + + 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.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; + + extra_network_refresh_btn.parentNode.appendChild(ch_refresh); + + } + + + //run it once + update_card_for_civitai(); + + +}); + + + diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/__pycache__/civitai_helper.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/__pycache__/civitai_helper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b8e7d60ca2ca6e4ad63551b283ac6bee4d287cd Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/__pycache__/civitai_helper.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__init__.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/__init__.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cbebd2239960418da2f273fd0d37b51f6deabb6 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/__init__.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/civitai.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/civitai.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a608bf672f340c81bdf172b29eb2bfbafa68c398 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/civitai.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/downloader.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/downloader.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..953146531930bda033b23b935aec5c5ed27a1f03 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/downloader.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/js_action_civitai.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/js_action_civitai.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5eb5eb6c17cba5041e477c7151f2d78ff9bd5b07 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/js_action_civitai.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8ec10c4c27327c3ba33c29fed1ec391d8bfc704 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model_action_civitai.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model_action_civitai.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60ffad49461a9ad7c499d77bd0586087363d3482 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model_action_civitai.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/msg_handler.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/msg_handler.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eaf80646e4ea16c98a5acd90b33303dcf009966e Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/msg_handler.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/setting.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/setting.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96a07b10fcca5049da041f6da08516eef0754960 Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/setting.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/util.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/util.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ca40074c6ee1392499159fbf2cd3816d1f91dae Binary files /dev/null and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/util.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/civitai.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/civitai.py new file mode 100644 index 0000000000000000000000000000000000000000..2a15ae745d6d98b75a177d8a3deb1c897fa2a2c3 --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/civitai.py @@ -0,0 +1,612 @@ +# -*- coding: UTF-8 -*- +# handle msg between js and python side +import os +import time +import json +import re +import requests +from . import util +from . import model +from . import setting + +suffix = ".civitai" + +url_dict = { + "modelPage":"https://civitai.com/models/", + "modelId": "https://civitai.com/api/v1/models/", + "modelVersionId": "https://civitai.com/api/v1/model-versions/", + "hash": "https://civitai.com/api/v1/model-versions/by-hash/" +} + +model_type_dict = { + "Checkpoint": "ckp", + "TextualInversion": "ti", + "Hypernetwork": "hyper", + "LORA": "lora", + "LoCon": "lora", +} + + + +# get image with full size +# width is in number, not string +# return: url str +def get_full_size_image_url(image_url, width): + return re.sub('/width=\d+/', '/width=' + str(width) + '/', image_url) + + +# use this sha256 to get model info from civitai +# return: model info dict +def get_model_info_by_hash(hash:str): + util.printD("Request model info from civitai") + + if not hash: + util.printD("hash is empty") + return + + r = requests.get(url_dict["hash"]+hash, headers=util.def_headers, proxies=util.proxies) + if not r.ok: + if r.status_code == 404: + # this is not a civitai model + util.printD("Civitai does not have this model") + return {} + else: + util.printD("Get error code: " + str(r.status_code)) + util.printD(r.text) + return + + # try to get content + content = None + try: + content = r.json() + except Exception as e: + util.printD("Parse response json failed") + util.printD(str(e)) + util.printD("response:") + util.printD(r.text) + return + + if not content: + util.printD("error, content from civitai is None") + return + + return content + + + +def get_model_info_by_id(id:str) -> dict: + util.printD("Request model info from civitai") + + if not id: + util.printD("id is empty") + return + + r = requests.get(url_dict["modelId"]+str(id), headers=util.def_headers, proxies=util.proxies) + if not r.ok: + if r.status_code == 404: + # this is not a civitai model + util.printD("Civitai does not have this model") + return {} + else: + util.printD("Get error code: " + str(r.status_code)) + util.printD(r.text) + return + + # try to get content + content = None + try: + content = r.json() + except Exception as e: + util.printD("Parse response json failed") + util.printD(str(e)) + util.printD("response:") + util.printD(r.text) + return + + if not content: + util.printD("error, content from civitai is None") + return + + return content + + +def get_version_info_by_version_id(id:str) -> dict: + util.printD("Request version info from civitai") + + if not id: + util.printD("id is empty") + return + + r = requests.get(url_dict["modelVersionId"]+str(id), headers=util.def_headers, proxies=util.proxies) + if not r.ok: + if r.status_code == 404: + # this is not a civitai model + util.printD("Civitai does not have this model version") + return {} + else: + util.printD("Get error code: " + str(r.status_code)) + util.printD(r.text) + return + + # try to get content + content = None + try: + content = r.json() + except Exception as e: + util.printD("Parse response json failed") + util.printD(str(e)) + util.printD("response:") + util.printD(r.text) + return + + if not content: + util.printD("error, content from civitai is None") + return + + return content + + +def get_version_info_by_model_id(id:str) -> dict: + + model_info = get_model_info_by_id(id) + if not model_info: + util.printD(f"Failed to get model info by id: {id}") + return + + # check content to get version id + if "modelVersions" not in model_info.keys(): + util.printD("There is no modelVersions in this model_info") + return + + if not model_info["modelVersions"]: + util.printD("modelVersions is None") + return + + if len(model_info["modelVersions"])==0: + util.printD("modelVersions is Empty") + return + + def_version = model_info["modelVersions"][0] + if not def_version: + util.printD("default version is None") + return + + if "id" not in def_version.keys(): + util.printD("default version has no id") + return + + version_id = def_version["id"] + + if not version_id: + util.printD("default version's id is None") + return + + # get version info + version_info = get_version_info_by_version_id(str(version_id)) + if not version_info: + util.printD(f"Failed to get version info by version_id: {version_id}") + return + + return version_info + + + + +# get model info file's content by model type and search_term +# parameter: model_type, search_term +# return: model_info +def load_model_info_by_search_term(model_type, search_term): + util.printD(f"Load model info of {search_term} in {model_type}") + if model_type not in model.folders.keys(): + util.printD("unknow model type: " + model_type) + return + + # 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:] + + 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 not os.path.isfile(model_info_filepath): + util.printD("Can not find model info file: " + model_info_filepath) + return + + return model.load_model_info(model_info_filepath) + + + + + +# get model file names by model type +# parameter: model_type - string +# parameter: filter - dict, which kind of model you need +# return: model name list +def get_model_names_by_type_and_filter(model_type:str, filter:dict) -> list: + + model_folder = model.folders[model_type] + + # set filter + # only get models don't have a civitai info file + no_info_only = False + empty_info_only = False + + if filter: + if "no_info_only" in filter.keys(): + no_info_only = filter["no_info_only"] + if "empty_info_only" in filter.keys(): + empty_info_only = filter["empty_info_only"] + + + + # 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 + + # 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) + + + 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}) + + +# get id from url +def get_model_id_from_url(url:str) -> str: + util.printD("Run get_model_id_from_url") + id = "" + + if not url: + util.printD("url or model id can not be empty") + return "" + + if url.isnumeric(): + # is already an id + id = str(url) + return id + + s = re.sub("\\?.+$", "", url).split("/") + if len(s) < 2: + util.printD("url is not valid") + return "" + + if s[-2].isnumeric(): + id = s[-2] + elif s[-1].isnumeric(): + id = s[-1] + else: + util.printD("There is no model id in this url") + return "" + + return id + + +# get preview image by model path +# image will be saved to file, so no return +def get_preview_image_by_model_path(model_path:str, max_size_preview, skip_nsfw_preview): + if not model_path: + util.printD("model_path is empty") + return + + if not os.path.isfile(model_path): + util.printD("model_path is not a file: "+model_path) + return + + base, ext = os.path.splitext(model_path) + first_preview = base+".png" + sec_preview = base+".preview.png" + info_file = base + suffix + model.info_ext + + # check preview image + if not os.path.isfile(sec_preview): + # need to download preview image + util.printD("Checking preview image for model: " + model_path) + # load model_info file + if os.path.isfile(info_file): + model_info = model.load_model_info(info_file) + if not model_info: + util.printD("Model Info is empty") + return + + if "images" in model_info.keys(): + if model_info["images"]: + for img_dict in model_info["images"]: + if "nsfw" in img_dict.keys(): + if img_dict["nsfw"]: + util.printD("This image is NSFW") + if skip_nsfw_preview: + util.printD("Skip NSFW image") + continue + + if "url" in img_dict.keys(): + img_url = img_dict["url"] + if max_size_preview: + # use max width + if "width" in img_dict.keys(): + if img_dict["width"]: + img_url = get_full_size_image_url(img_url, img_dict["width"]) + + util.download_file(img_url, sec_preview) + # we only need 1 preview image + break + + + +# 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)) + + if not folder: + util.printD("folder is none") + return + + if not os.path.isdir(folder): + util.printD("folder is not a dir") + return + + if not version_id: + util.printD("version_id is none") + return + + # search civitai model info file + for filename in os.listdir(folder): + # check ext + base, ext = os.path.splitext(filename) + if ext == model.info_ext: + # find info file + if len(base) < 9: + # not a civitai info file + continue + + if base[-8:] == suffix: + # find a civitai info file + path = os.path.join(folder, filename) + model_info = model.load_model_info(path) + if not model_info: + continue + + if "id" not in model_info.keys(): + continue + + id = model_info["id"] + if not id: + continue + + # util.printD(f"Compare version id, src: {id}, target:{version_id}") + if str(id) == str(version_id): + # find the one + return model_info + + + 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: + if not model_path: + util.printD("model_path is empty") + return + + if not os.path.isfile(model_path): + util.printD("model_path is not a file: "+model_path) + return + + # get model info file name + base, ext = os.path.splitext(model_path) + info_file = base + suffix + model.info_ext + + if not os.path.isfile(info_file): + return + + # get model info + model_info_file = model.load_model_info(info_file) + if not model_info_file: + return + + if "id" not in model_info_file.keys(): + return + + local_version_id = model_info_file["id"] + if not local_version_id: + return + + if "modelId" not in model_info_file.keys(): + return + + model_id = model_info_file["modelId"] + if not model_id: + return + + # get model info by id from civitai + model_info = get_model_info_by_id(model_id) + # delay before next request, to prevent to be treat as DDoS + util.printD(f"delay:{delay} second") + time.sleep(delay) + + if not model_info: + return + + if "modelVersions" not in model_info.keys(): + return + + modelVersions = model_info["modelVersions"] + if not modelVersions: + return + + if not len(modelVersions): + return + + current_version = modelVersions[0] + if not current_version: + return + + if "id" not in current_version.keys(): + return + + current_version_id = current_version["id"] + if not current_version_id: + return + + util.printD(f"Compare version id, local: {local_version_id}, remote: {current_version_id} ") + if current_version_id == local_version_id: + return + + model_name = "" + if "name" in model_info.keys(): + model_name = model_info["name"] + + if not model_name: + model_name = "" + + + new_version_name = "" + if "name" in current_version.keys(): + new_version_name = current_version["name"] + + if not new_version_name: + new_version_name = "" + + description = "" + if "description" in current_version.keys(): + description = current_version["description"] + + if not description: + description = "" + + downloadUrl = "" + if "downloadUrl" in current_version.keys(): + downloadUrl = current_version["downloadUrl"] + + if not downloadUrl: + downloadUrl = "" + + # get 1 preview image + img_url = "" + if "images" in current_version.keys(): + if current_version["images"]: + if current_version["images"][0]: + if "url" in current_version["images"][0].keys(): + img_url = current_version["images"][0]["url"] + if not img_url: + img_url = "" + + + + return (model_path, model_id, model_name, current_version_id, new_version_name, description, downloadUrl, img_url) + + + + +# check model's new version +# parameter: delay - float, how many seconds to delay between each request to civitai +# return: new_versions - a list for all new versions, each one is (model_path, model_id, model_name, new_verion_id, new_version_name, description, download_url, img_url) +def check_models_new_version_by_model_types(model_types:list, delay:float=1) -> list: + util.printD("Checking models' new version") + + if not model_types: + return [] + + # check model types, which cloud be a string as 1 type + mts = [] + if type(model_types) == str: + mts.append(model_types) + elif type(model_types) == list: + mts = model_types + else: + util.printD("Unknow model types:") + util.printD(model_types) + return [] + + # output is a markdown document string to show a list of new versions on UI + output = "" + # new version list + new_versions = [] + + # walk all models + for model_type, model_folder in model.folders.items(): + if model_type not in mts: + continue + + util.printD("Scanning path: " + model_folder) + for root, dirs, files in os.walk(model_folder, followlinks=True): + for filename in files: + # check ext + item = os.path.join(root, filename) + base, ext = os.path.splitext(item) + if ext in model.exts: + # find a model + r = check_model_new_version_by_path(item, delay) + + if not r: + continue + + model_path, model_id, model_name, current_version_id, new_version_name, description, downloadUrl, img_url = r + # check exist + if not current_version_id: + continue + + # check this version id in list + is_already_in_list = False + for new_version in new_versions: + if current_version_id == new_version[3]: + # already in list + is_already_in_list = True + break + + if is_already_in_list: + util.printD("New version is already in list") + continue + + # search this new version id to check if this model is already downloaded + target_model_info = search_local_model_info_by_version_id(root, current_version_id) + if target_model_info: + util.printD("New version is already existed") + continue + + # add to list + new_versions.append(r) + + + + + return new_versions + + + diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/downloader.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/downloader.py new file mode 100644 index 0000000000000000000000000000000000000000..2b14ebc2cde6650337b4b3e1520337a66d6ea61a --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/downloader.py @@ -0,0 +1,115 @@ +# -*- coding: UTF-8 -*- +import sys +import requests +import os +from . import util + + +dl_ext = ".downloading" + +# disable ssl warning info +requests.packages.urllib3.disable_warnings() + +# output is downloaded file path +def dl(url, folder, filename, filepath): + util.printD("Start downloading from: " + url) + # get file_path + file_path = "" + if filepath: + file_path = filepath + else: + # if file_path is not in parameter, then folder must be in parameter + if not folder: + util.printD("folder is none") + return + + if not os.path.isdir(folder): + util.printD("folder does not exist: "+folder) + return + + if filename: + file_path = os.path.join(folder, filename) + + # first request for header + rh = requests.get(url, stream=True, verify=False, headers=util.def_headers, proxies=util.proxies) + # get file size + total_size = 0 + total_size = int(rh.headers['Content-Length']) + util.printD(f"File size: {total_size}") + + # if file_path is empty, need to get file name from download url's header + if not file_path: + filename = "" + if "Content-Disposition" in rh.headers.keys(): + cd = rh.headers["Content-Disposition"] + # Extract the filename from the header + # content of a CD: "attachment;filename=FileName.txt" + # in case "" is in CD filename's start and end, need to strip them out + filename = cd.split("=")[1].strip('"') + if not filename: + util.printD("Fail to get file name from Content-Disposition: " + cd) + return + + if not filename: + util.printD("Can not get file name from download url's header") + return + + # with folder and filename, now we have the full file path + file_path = os.path.join(folder, filename) + + + util.printD("Target file path: " + file_path) + base, ext = os.path.splitext(file_path) + + # check if file is already exist + count = 2 + new_base = base + while os.path.isfile(file_path): + util.printD("Target file already exist.") + # re-name + new_base = base + "_" + str(count) + file_path = new_base + ext + count += 1 + + # use a temp file for downloading + dl_file_path = new_base+dl_ext + + + util.printD(f"Downloading to temp file: {dl_file_path}") + + # check if downloading file is exsited + 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}") + + # create header range + headers = {'Range': 'bytes=%d-' % downloaded_size} + headers['User-Agent'] = util.def_headers['User-Agent'] + + # download with header + r = requests.get(url, stream=True, verify=False, headers=headers, proxies=util.proxies) + + # write to file + with open(dl_file_path, "ab") as f: + for chunk in r.iter_content(chunk_size=1024): + if chunk: + downloaded_size += len(chunk) + f.write(chunk) + # force to write to disk + f.flush() + + # progress + progress = int(50 * 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.flush() + + print() + + # rename file + os.rename(dl_file_path, file_path) + util.printD(f"File Downloaded to: {file_path}") + return file_path + diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/js_action_civitai.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/js_action_civitai.py new file mode 100644 index 0000000000000000000000000000000000000000..0c818539e421329ed35a903ab08294a3553804c1 --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/js_action_civitai.py @@ -0,0 +1,256 @@ +# -*- coding: UTF-8 -*- +# handle msg between js and python side +import os +import json +import requests +import webbrowser +from . import util +from . import model +from . import civitai +from . import msg_handler +from . import downloader + + + +# get civitai's model url and open it in browser +# parameter: model_type, search_term +# output: python msg - will be sent to hidden textbox then picked by js side +def open_model_url(msg, open_url_with_js): + util.printD("Start open_model_url") + + output = "" + 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"] + + model_info = civitai.load_model_info_by_search_term(model_type, search_term) + if not model_info: + util.printD(f"Failed to get model info for {model_type} {search_term}") + return "" + + if "modelId" not in model_info.keys(): + util.printD(f"Failed to get model id from info file for {model_type} {search_term}") + return "" + + model_id = model_info["modelId"] + if not model_id: + util.printD(f"model id from info file of {model_type} {search_term} is None") + return "" + + url = civitai.url_dict["modelPage"]+str(model_id) + + + # msg content for js + content = { + "url":"" + } + + if not open_url_with_js: + util.printD("Open Url: " + url) + # open url + webbrowser.open_new_tab(url) + else: + util.printD("Send Url to js") + content["url"] = url + output = msg_handler.build_py_msg("open_url", content) + + util.printD("End open_model_url") + return output + + + +# add trigger words to prompt +# parameter: model_type, search_term, prompt +# return: [new_prompt, new_prompt] - new prompt with trigger words, return twice for txt2img and img2img +def add_trigger_words(msg): + util.printD("Start add_trigger_words") + + 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"] + prompt = result["prompt"] + + + model_info = civitai.load_model_info_by_search_term(model_type, search_term) + if not model_info: + util.printD(f"Failed to get model info for {model_type} {search_term}") + return [prompt, prompt] + + if "trainedWords" not in model_info.keys(): + util.printD(f"Failed to get trainedWords from info file for {model_type} {search_term}") + return [prompt, prompt] + + trainedWords = model_info["trainedWords"] + if not trainedWords: + util.printD(f"No trainedWords from info file for {model_type} {search_term}") + return [prompt, prompt] + + if len(trainedWords) == 0: + util.printD(f"trainedWords from info file for {model_type} {search_term} is empty") + return [prompt, prompt] + + # get ful trigger words + trigger_words = "" + for word in trainedWords: + trigger_words = trigger_words + word + ", " + + new_prompt = prompt + " " + trigger_words + util.printD("trigger_words: " + trigger_words) + util.printD("prompt: " + prompt) + util.printD("new_prompt: " + new_prompt) + + util.printD("End add_trigger_words") + + # add to prompt + return [new_prompt, new_prompt] + + + +# use preview image's prompt as prompt +# parameter: model_type, model_name, prompt, neg_prompt +# return: [new_prompt, new_neg_prompt, new_prompt, new_neg_prompt,] - return twice for txt2img and img2img +def use_preview_image_prompt(msg): + util.printD("Start use_preview_image_prompt") + + 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"] + prompt = result["prompt"] + neg_prompt = result["neg_prompt"] + + + model_info = civitai.load_model_info_by_search_term(model_type, search_term) + if not model_info: + util.printD(f"Failed to get model info for {model_type} {search_term}") + return [prompt, neg_prompt, prompt, neg_prompt] + + if "images" not in model_info.keys(): + util.printD(f"Failed to get images from info file for {model_type} {search_term}") + return [prompt, neg_prompt, prompt, neg_prompt] + + images = model_info["images"] + if not images: + util.printD(f"No images from info file for {model_type} {search_term}") + return [prompt, neg_prompt, prompt, neg_prompt] + + if len(images) == 0: + util.printD(f"images from info file for {model_type} {search_term} is empty") + return [prompt, neg_prompt, prompt, neg_prompt] + + # get prompt from preview images' meta data + preview_prompt = "" + preview_neg_prompt = "" + for img in images: + if "meta" in img.keys(): + if img["meta"]: + if "prompt" in img["meta"].keys(): + if img["meta"]["prompt"]: + preview_prompt = img["meta"]["prompt"] + + if "negativePrompt" in img["meta"].keys(): + if img["meta"]["negativePrompt"]: + preview_neg_prompt = img["meta"]["negativePrompt"] + + # we only need 1 prompt + if preview_prompt: + break + + if not preview_prompt: + util.printD(f"There is no prompt of {model_type} {search_term} in its preview image") + return [prompt, neg_prompt, prompt, neg_prompt] + + util.printD("End use_preview_image_prompt") + + return [preview_prompt, preview_neg_prompt, preview_prompt, preview_neg_prompt] + + +# download model's new verson by model path, version id and download url +# output is a md log +def dl_model_new_version(msg, max_size_preview, skip_nsfw_preview): + util.printD("Start dl_model_new_version") + + output = "" + + result = msg_handler.parse_js_msg(msg) + if not result: + output = "Parsing js ms failed" + util.printD(output) + return output + + model_path = result["model_path"] + version_id = result["version_id"] + download_url = result["download_url"] + + util.printD("model_path: " + model_path) + util.printD("version_id: " + str(version_id)) + util.printD("download_url: " + download_url) + + # check data + if not model_path: + output = "model_path is empty" + util.printD(output) + return output + + if not version_id: + output = "version_id is empty" + util.printD(output) + return output + + if not download_url: + output = "download_url is empty" + util.printD(output) + return output + + if not os.path.isfile(model_path): + output = "model_path is not a file: "+ model_path + util.printD(output) + return output + + # get model folder from model path + model_folder = os.path.dirname(model_path) + + # no need to check when downloading new version, since checking new version is already checked + # 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 file + new_model_path = downloader.dl(download_url, model_folder, None, None) + if not new_model_path: + output = "Download failed, check console log for detail. Download url: " + download_url + util.printD(output) + return output + + # 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: " + new_model_path + util.printD(output) + return output + + # now write version info to file + base, ext = os.path.splitext(new_model_path) + info_file = base + civitai.suffix + model.info_ext + model.write_model_info(info_file, version_info) + + # 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 + util.printD(output) + return output diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model.py new file mode 100644 index 0000000000000000000000000000000000000000..729012d742738cab559a8836727fc8289c21f18e --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model.py @@ -0,0 +1,120 @@ +# -*- coding: UTF-8 -*- +# handle msg between js and python side +import os +import json +from . import util +from modules import shared + + +# this is the default root path +root_path = os.getcwd() + +# if command line arguement is used to change model folder, +# then model folder is in absolute path, not based on this root path anymore. +# so to make extension work with those absolute model folder paths, model folder also need to be in absolute path +folders = { + "ti": os.path.join(root_path, "embeddings"), + "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"), +} + +exts = (".bin", ".pt", ".safetensors", ".ckpt") +info_ext = ".info" +vae_suffix = ".vae" + + +# get cusomter model path +def get_custom_model_folder(): + util.printD("Get Custom Model Folder") + + global folders + + if shared.cmd_opts.embeddings_dir and os.path.isdir(shared.cmd_opts.embeddings_dir): + folders["ti"] = shared.cmd_opts.embeddings_dir + + if shared.cmd_opts.hypernetwork_dir and os.path.isdir(shared.cmd_opts.hypernetwork_dir): + folders["hyper"] = shared.cmd_opts.hypernetwork_dir + + if shared.cmd_opts.ckpt_dir and os.path.isdir(shared.cmd_opts.ckpt_dir): + folders["ckp"] = shared.cmd_opts.ckpt_dir + + if shared.cmd_opts.lora_dir and os.path.isdir(shared.cmd_opts.lora_dir): + folders["lora"] = shared.cmd_opts.lora_dir + + + + + +# write model info to file +def write_model_info(path, model_info): + util.printD("Write model info to file: " + path) + with open(os.path.realpath(path), 'w') as f: + f.write(json.dumps(model_info, indent=4)) + + +def load_model_info(path): + # util.printD("Load model info from file: " + path) + model_info = None + with open(os.path.realpath(path), 'r') as f: + try: + model_info = json.load(f) + except Exception as e: + util.printD("Selected file is not json: " + path) + util.printD(e) + return + + return model_info + + +# get model file names by model type +# parameter: model_type - string +# return: model name list +def get_model_names_by_type(model_type:str) -> list: + + model_folder = 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) + + + return model_names + + +# return 2 values: (model_root, model_path) +def get_model_path_by_type_and_name(model_type:str, model_name:str) -> str: + util.printD("Run get_model_path_by_type_and_name") + if model_type not in folders.keys(): + util.printD("unknown model_type: " + model_type) + return + + if not model_name: + util.printD("model name can not be empty") + return + + folder = 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) + + return + + + diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model_action_civitai.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model_action_civitai.py new file mode 100644 index 0000000000000000000000000000000000000000..f09f0752a406e772dcaa95b1ccec20ba9de39506 --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/model_action_civitai.py @@ -0,0 +1,511 @@ +# -*- coding: UTF-8 -*- +# handle msg between js and python side +import os +import time +from . import util +from . import model +from . import civitai +from . import downloader + + +# scan model to generate SHA256, then use this SHA256 to get model info from civitai +# return output msg +def scan_model(scan_model_types, max_size_preview, skip_nsfw_preview): + util.printD("Start scan_model") + output = "" + + # check model types + if not scan_model_types: + output = "Model Types is None, can not scan." + util.printD(output) + return output + + model_types = [] + # check type if it is a string + if type(scan_model_types) == str: + model_types.append(scan_model_types) + else: + model_types = scan_model_types + + model_count = 0 + image_count = 0 + # scan_log = "" + for model_type, model_folder in model.folders.items(): + if model_type not in model_types: + continue + + util.printD("Scanning path: " + model_folder) + for root, dirs, files in os.walk(model_folder, followlinks=True): + for filename in files: + # check ext + item = os.path.join(root, filename) + base, ext = os.path.splitext(item) + if ext in model.exts: + # ignore vae file + if len(base) > 4: + if base[-4:] == model.vae_suffix: + # find .vae + util.printD("This is a vae file: " + filename) + continue + + # find a model + # get info file + info_file = base + civitai.suffix + model.info_ext + # check info file + if not os.path.isfile(info_file): + util.printD("Creating model info for: " + filename) + # get model's sha256 + hash = util.gen_file_sha256(item) + + if not hash: + output = "failed generating SHA256 for model:" + filename + util.printD(output) + return output + + # use this sha256 to get model info from civitai + model_info = civitai.get_model_info_by_hash(hash) + # delay 1 second for ti + if model_type == "ti": + util.printD("Delay 1 second for TI") + time.sleep(1) + + if model_info is None: + output = "Connect to Civitai API service failed. Wait a while and try again" + util.printD(output) + return output+", check console log for detail" + + # write model info to file + model.write_model_info(info_file, model_info) + + # set model_count + model_count = model_count+1 + + # check preview image + civitai.get_preview_image_by_model_path(item, max_size_preview, skip_nsfw_preview) + image_count = image_count+1 + + + # scan_log = "Done" + + output = f"Done. Scanned {model_count} models, checked {image_count} images" + + util.printD(output) + + return output + + + +# Get model info by model type, name and url +# output is log info to display on markdown component +def get_model_info_by_input(model_type, model_name, model_url_or_id, max_size_preview, skip_nsfw_preview): + output = "" + # parse model id + model_id = civitai.get_model_id_from_url(model_url_or_id) + if not model_id: + output = "failed to parse model id from url: " + model_url_or_id + util.printD(output) + return output + + # get model file path + # model could be in subfolder + result = model.get_model_path_by_type_and_name(model_type, model_name) + if not result: + output = "failed to get model file path" + util.printD(output) + return output + + model_root, model_path = result + if not model_path: + output = "model path is empty" + util.printD(output) + return output + + # get info file path + base, ext = os.path.splitext(model_path) + info_file = base + civitai.suffix + model.info_ext + + # get model info + #we call it model_info, but in civitai, it is actually version info + model_info = civitai.get_version_info_by_model_id(model_id) + + if not model_info: + output = "failed to get model info from url: " + model_url_or_id + util.printD(output) + return output + + # write model info to file + model.write_model_info(info_file, model_info) + + util.printD("Saved model info to: "+ info_file) + + # check preview image + civitai.get_preview_image_by_model_path(model_path, max_size_preview, skip_nsfw_preview) + + output = "Model Info saved to: " + info_file + return output + + + +# check models' new version and output to UI as markdown doc +def check_models_new_version_to_md(model_types:list) -> str: + new_versions = civitai.check_models_new_version_by_model_types(model_types, 1) + + count = 0 + output = "" + if not new_versions: + output = "No model has new version" + else: + output = "Found new version for following models:
" + for new_version in new_versions: + count = count+1 + model_path, model_id, model_name, new_verion_id, new_version_name, description, download_url, img_url = new_version + # in md, each part is something like this: + # [model_name](model_url) + # [version_name](download_url) + # version description + url = civitai.url_dict["modelPage"]+str(model_id) + + part = f'
Model: {model_name}
' + part = part + f'
File: {model_path}
' + if download_url: + # replace "\" to "/" in model_path for windows + model_path = model_path.replace('\\', '\\\\') + part = part + f'
New Version: {new_version_name}' + # add js function to download new version into SD webui by python + part = part + " " + # in embed HTML, onclick= will also follow a ", never a ', so have to write it as following + part = part + f"[Download into SD]" + + else: + part = part + f'
New Version: {new_version_name}' + part = part + '
' + + # description + if description: + part = part + '
'+ description + '

' + + # preview image + if img_url: + part = part + f"
" + + + output = output + part + + util.printD(f"Done. Find {count} models have new version. Check UI for detail.") + + return output + + +# get model info by url +def get_model_info_by_url(model_url_or_id:str) -> tuple: + util.printD("Getting model info by: " + model_url_or_id) + + # parse model id + model_id = civitai.get_model_id_from_url(model_url_or_id) + if not model_id: + util.printD("failed to parse model id from url or id") + return + + model_info = civitai.get_model_info_by_id(model_id) + if model_info is None: + util.printD("Connect to Civitai API service failed. Wait a while and try again") + return + + if not model_info: + util.printD("failed to get model info from url or id") + return + + # parse model type, model name, subfolder, version from this model info + # get model type + if "type" not in model_info.keys(): + util.printD("model type is not in model_info") + return + + civitai_model_type = model_info["type"] + if civitai_model_type not in civitai.model_type_dict.keys(): + util.printD("This model type is not supported:"+civitai_model_type) + return + + model_type = civitai.model_type_dict[civitai_model_type] + + # get model type + if "name" not in model_info.keys(): + util.printD("model name is not in model_info") + return + + model_name = model_info["name"] + if not model_name: + util.printD("model name is Empty") + model_name = "" + + # get version list + if "modelVersions" not in model_info.keys(): + util.printD("modelVersions is not in model_info") + return + + modelVersions = model_info["modelVersions"] + if not modelVersions: + util.printD("modelVersions is Empty") + return + + version_strs = [] + for version in modelVersions: + # version name can not be used as id + # version id is not readable + # so , we use name_id as version string + version_str = version["name"]+"_"+str(version["id"]) + version_strs.append(version_str) + + # get folder by model type + folder = model.folders[model_type] + # get subfolders + subfolders = util.get_subfolders(folder) + if not subfolders: + subfolders = [] + + # add default root folder + subfolders.append("/") + + util.printD("Get following info for downloading:") + util.printD(f"model_name:{model_name}") + util.printD(f"model_type:{model_type}") + util.printD(f"subfolders:{subfolders}") + util.printD(f"version_strs:{version_strs}") + + return (model_info, model_name, model_type, subfolders, version_strs) + +# get version info by version string +def get_ver_info_by_ver_str(version_str:str, model_info:dict) -> dict: + if not version_str: + util.printD("version_str is empty") + return + + if not model_info: + util.printD("model_info is None") + return + + # get version list + if "modelVersions" not in model_info.keys(): + util.printD("modelVersions is not in model_info") + return + + modelVersions = model_info["modelVersions"] + if not modelVersions: + util.printD("modelVersions is Empty") + return + + # find version by version_str + version = None + for ver in modelVersions: + # version name can not be used as id + # version id is not readable + # so , we use name_id as version string + ver_str = ver["name"]+"_"+str(ver["id"]) + if ver_str == version_str: + # find version + version = ver + + if not version: + util.printD("can not find version by version string: " + version_str) + return + + # get version id + if "id" not in version.keys(): + util.printD("this version has no id") + return + + return version + + +# get download url from model info by version string +# return - (version_id, download_url) +def get_id_and_dl_url_by_version_str(version_str:str, model_info:dict) -> tuple: + if not version_str: + util.printD("version_str is empty") + return + + if not model_info: + util.printD("model_info is None") + return + + # get version list + if "modelVersions" not in model_info.keys(): + util.printD("modelVersions is not in model_info") + return + + modelVersions = model_info["modelVersions"] + if not modelVersions: + util.printD("modelVersions is Empty") + return + + # find version by version_str + version = None + for ver in modelVersions: + # version name can not be used as id + # version id is not readable + # so , we use name_id as version string + ver_str = ver["name"]+"_"+str(ver["id"]) + if ver_str == version_str: + # find version + version = ver + + if not version: + util.printD("can not find version by version string: " + version_str) + return + + # get version id + if "id" not in version.keys(): + util.printD("this version has no id") + return + + version_id = version["id"] + if not version_id: + util.printD("version id is Empty") + return + + # get download url + if "downloadUrl" not in version.keys(): + util.printD("downloadUrl is not in this version") + return + + downloadUrl = version["downloadUrl"] + if not downloadUrl: + util.printD("downloadUrl is Empty") + return + + util.printD("Get Download Url: " + downloadUrl) + + return (version_id, downloadUrl) + + +# download model from civitai by input +# output to markdown log +def dl_model_by_input(model_info:dict, model_type:str, subfolder_str:str, version_str:str, dl_all_bool:bool, max_size_preview:bool, skip_nsfw_preview:bool) -> str: + + output = "" + + if not model_info: + output = "model_info is None" + util.printD(output) + return output + + if not model_type: + output = "model_type is None" + util.printD(output) + return output + + if not subfolder_str: + output = "subfolder string is None" + util.printD(output) + return output + + if not version_str: + output = "version_str is None" + util.printD(output) + return output + + # get model root folder + if model_type not in model.folders.keys(): + output = "Unknow model type: "+model_type + util.printD(output) + return output + + model_root_folder = model.folders[model_type] + + + # get subfolder + subfolder = "" + if subfolder_str == "/" or subfolder_str == "\\": + subfolder = "" + elif subfolder_str[:1] == "/" or subfolder_str[:1] == "\\": + subfolder = subfolder_str[1:] + else: + subfolder = subfolder_str + + # get model folder for downloading + model_folder = os.path.join(model_root_folder, subfolder) + if not os.path.isdir(model_folder): + output = "Model folder is not a dir: "+ model_folder + util.printD(output) + return output + + # get version info + ver_info = get_ver_info_by_ver_str(version_str, model_info) + if not ver_info: + output = "Fail to get version info, check console log for detail" + util.printD(output) + return output + + version_id = ver_info["id"] + + + 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: + output = "Downloading failed, check console log for detail" + util.printD(output) + return output + + if url == ver_info["downloadUrl"]: + filepath = model_filepath + else: + # only download one file + # get download url + url = ver_info["downloadUrl"] + if not url: + output = "Fail to get download url, check console log for detail" + util.printD(output) + return output + + # download + filepath = downloader.dl(url, model_folder, None, None) + if not filepath: + output = "Downloading failed, check console log for detail" + util.printD(output) + return output + + + if not filepath: + filepath = model_filepath + + # 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 + + # write version info to file + base, ext = os.path.splitext(filepath) + info_file = base + civitai.suffix + model.info_ext + model.write_model_info(info_file, version_info) + + # then, get preview image + civitai.get_preview_image_by_model_path(filepath, max_size_preview, skip_nsfw_preview) + + output = "Done. Model downloaded to: " + filepath + util.printD(output) + return output diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/msg_handler.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/msg_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..1132d775f0f03547f0291138dbe53d970f2fe869 --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/msg_handler.py @@ -0,0 +1,64 @@ +# -*- coding: UTF-8 -*- +# handle msg between js and python side +import json +from . import util + +# action list +js_actions = ("open_url", "add_trigger_words", "use_preview_prompt", "dl_model_new_version") +py_actions = ("open_url") + + +# handle request from javascript +# parameter: msg - msg from js as string in a hidden textbox +# return: dict for result +def parse_js_msg(msg): + util.printD("Start parse js msg") + msg_dict = json.loads(msg) + + # in case client side run JSON.stringify twice + if (type(msg_dict) == str): + msg_dict = json.loads(msg_dict) + + if "action" not in msg_dict.keys(): + util.printD("Can not find action from js request") + return + + action = msg_dict["action"] + if not action: + util.printD("Action from js request is None") + return + + if action not in js_actions: + util.printD("Unknow action: " + action) + return + + util.printD("End parse js msg") + + return msg_dict + + +# build python side msg for sending to js +# parameter: content dict +# return: msg as string, to fill into a hidden textbox +def build_py_msg(action:str, content:dict): + util.printD("Start build_msg") + if not content: + util.printD("Content is None") + return + + if not action: + util.printD("Action is None") + return + + if action not in py_actions: + util.printD("Unknow action: " + action) + return + + msg = { + "action" : action, + "content": content + } + + + util.printD("End build_msg") + return json.dumps(msg) \ No newline at end of file diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/setting.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/setting.py new file mode 100644 index 0000000000000000000000000000000000000000..aabff852d5fb989840f2aefc05867bce8d673994 --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/setting.py @@ -0,0 +1,113 @@ +# -*- coding: UTF-8 -*- +# collecting settings to here +import json +import os +import modules.scripts as scripts +from . import util + + +name = "setting.json" +path = os.path.join(scripts.basedir(), name) + +data = { + "model":{ + "max_size_preview": True, + "skip_nsfw_preview": False + }, + "general":{ + "open_url_with_js": True, + "always_display": False, + "show_btn_on_thumb": True, + "proxy": "", + }, + "tool":{ + } +} + + + +# save setting +# return output msg for log +def save(): + print("Saving setting to: " + path) + + json_data = json.dumps(data, indent=4) + + output = "" + + #write to file + try: + with open(path, 'w') as f: + f.write(json_data) + except Exception as e: + util.printD("Error when writing file:"+path) + output = str(e) + util.printD(str(e)) + return output + + output = "Setting saved to: " + path + util.printD(output) + + return output + + +# load setting to global data +def load(): + # load data into globel data + global data + + util.printD("Load setting from: " + path) + + if not os.path.isfile(path): + util.printD("No setting file, use default") + return + + json_data = None + with open(path, 'r') as f: + json_data = json.load(f) + + # check error + if not json_data: + util.printD("load setting file failed") + return + + data = json_data + + # check for new key + if "always_display" not in data["general"].keys(): + data["general"]["always_display"] = False + + if "show_btn_on_thumb" not in data["general"].keys(): + data["general"]["show_btn_on_thumb"] = True + + if "proxy" not in data["general"].keys(): + data["general"]["proxy"] = "" + + + return + +# save setting from parameter +def save_from_input(max_size_preview, skip_nsfw_preview, open_url_with_js, always_display, show_btn_on_thumb, proxy): + global data + data = { + "model":{ + "max_size_preview": max_size_preview, + "skip_nsfw_preview": skip_nsfw_preview + }, + "general":{ + "open_url_with_js": open_url_with_js, + "always_display": always_display, + "show_btn_on_thumb": show_btn_on_thumb, + "proxy": proxy, + }, + "tool":{ + } + } + + output = save() + + if not output: + output = "" + + return output + diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/util.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/util.py new file mode 100644 index 0000000000000000000000000000000000000000..3e43c98d01c4c62a64fbdcbeaafdce051ddd4d36 --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/util.py @@ -0,0 +1,105 @@ +# -*- coding: UTF-8 -*- +import os +import io +import hashlib +import requests +import shutil + + +version = "1.6.4" + +def_headers = {'User-Agent': 'Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148'} + + +proxies = None + + +# print for debugging +def printD(msg): + print(f"Civitai Helper: {msg}") + + +def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE): + """Yield pieces of data from a file-like object until EOF.""" + while True: + chunk = file.read(size) + if not chunk: + break + yield chunk + +# Now, hashing use the same way as pip's source code. +def gen_file_sha256(filname): + printD("Use Memory Optimized SHA256") + blocksize=1 << 20 + h = hashlib.sha256() + length = 0 + with open(os.path.realpath(filname), 'rb') as f: + for block in read_chunks(f, size=blocksize): + length += len(block) + h.update(block) + + hash_value = h.hexdigest() + printD("sha256: " + hash_value) + printD("length: " + str(length)) + return hash_value + + + +# get preview image +def download_file(url, path): + printD("Downloading file from: " + url) + # get file + r = requests.get(url, stream=True, headers=def_headers, proxies=proxies) + if not r.ok: + printD("Get error code: " + str(r.status_code)) + printD(r.text) + return + + # write to file + with open(os.path.realpath(path), 'wb') as f: + r.raw.decode_content = True + shutil.copyfileobj(r.raw, f) + + printD("File downloaded to: " + path) + +# get subfolder list +def get_subfolders(folder:str) -> list: + printD("Get subfolder for: " + folder) + if not folder: + printD("folder can not be None") + return + + if not os.path.isdir(folder): + printD("path is not a folder") + return + + prefix_len = len(folder) + subfolders = [] + for root, dirs, files in os.walk(folder, followlinks=True): + for dir in dirs: + full_dir_path = os.path.join(root, dir) + # get subfolder path from it + subfolder = full_dir_path[prefix_len:] + subfolders.append(subfolder) + + return subfolders + + +# get relative path +def get_relative_path(item_path:str, parent_path:str) -> str: + # printD("item_path:"+item_path) + # printD("parent_path:"+parent_path) + # item path must start with parent_path + if not item_path: + return "" + if not parent_path: + return "" + if not item_path.startswith(parent_path): + return item_path + + relative = item_path[len(parent_path):] + if relative[:1] == "/" or relative[:1] == "\\": + relative = relative[1:] + + # printD("relative:"+relative) + return relative \ No newline at end of file diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/civitai_helper.py b/Stable-Diffusion-Webui-Civitai-Helper/scripts/civitai_helper.py new file mode 100644 index 0000000000000000000000000000000000000000..ad9e70a1cff66b38085691765753c5dd5dc0677d --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/scripts/civitai_helper.py @@ -0,0 +1,218 @@ +# -*- coding: UTF-8 -*- +# This extension can help you manage your models from civitai. It can download preview, add trigger words, open model page and use the prompt from preview image +# repo: https://github.com/butaixianran/ + + + +import modules.scripts as scripts +import gradio as gr +import os +import webbrowser +import requests +import random +import hashlib +import json +import shutil +import re +import modules +from modules import script_callbacks +from modules import shared +from scripts.ch_lib import model +from scripts.ch_lib import js_action_civitai +from scripts.ch_lib import model_action_civitai +from scripts.ch_lib import setting +from scripts.ch_lib import civitai +from scripts.ch_lib import util + + +# init + +# root path +root_path = os.getcwd() + +# extension path +extension_path = scripts.basedir() + +model.get_custom_model_folder() +setting.load() + +# set proxy +if setting.data["general"]["proxy"]: + util.printD("Set Proxy: "+setting.data["general"]["proxy"]) + util.proxies = { + "http": setting.data["general"]["proxy"], + "https": setting.data["general"]["proxy"], + } + + + + +def on_ui_tabs(): + # init + # init_py_msg = { + # # relative extension path + # "extension_path": util.get_relative_path(extension_path, root_path), + # } + # init_py_msg_str = json.dumps(init_py_msg) + + + # get prompt textarea + # check modules/ui.py, search for txt2img_paste_fields + # Negative prompt is the second element + txt2img_prompt = modules.ui.txt2img_paste_fields[0][0] + txt2img_neg_prompt = modules.ui.txt2img_paste_fields[1][0] + img2img_prompt = modules.ui.img2img_paste_fields[0][0] + img2img_neg_prompt = modules.ui.img2img_paste_fields[1][0] + + # ====Event's function==== + def get_model_names_by_input(model_type, empty_info_only): + names = civitai.get_model_names_by_input(model_type, empty_info_only) + return model_name_drop.update(choices=names) + + + def get_model_info_by_url(url): + r = model_action_civitai.get_model_info_by_url(url) + + model_info = {} + model_name = "" + model_type = "" + subfolders = [] + version_strs = [] + 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)] + + # ====UI==== + with gr.Blocks(analytics_enabled=False) as civitai_helper: + # with gr.Blocks(css=".block.padded {padding: 10px !important}") as civitai_helper: + + # init + max_size_preview = setting.data["model"]["max_size_preview"] + skip_nsfw_preview = setting.data["model"]["skip_nsfw_preview"] + open_url_with_js = setting.data["general"]["open_url_with_js"] + always_display = setting.data["general"]["always_display"] + show_btn_on_thumb = setting.data["general"]["show_btn_on_thumb"] + proxy = setting.data["general"]["proxy"] + + model_types = list(model.folders.keys()) + no_info_model_names = civitai.get_model_names_by_input("ckp", False) + + # session data + dl_model_info = gr.State({}) + + + + with gr.Box(elem_classes="ch_box"): + with gr.Column(): + gr.Markdown("### Scan Models for Civitai") + with gr.Row(): + max_size_preview_ckb = gr.Checkbox(label="Download Max Size Preview", value=max_size_preview, elem_id="ch_max_size_preview_ckb") + skip_nsfw_preview_ckb = gr.Checkbox(label="Skip NSFW Preview Images", value=skip_nsfw_preview, elem_id="ch_skip_nsfw_preview_ckb") + scan_model_types_ckbg = gr.CheckboxGroup(choices=model_types, label="Model Types", value=model_types) + + # with gr.Row(): + scan_model_civitai_btn = gr.Button(value="Scan", variant="primary", elem_id="ch_scan_model_civitai_btn") + # with gr.Row(): + scan_model_log_md = gr.Markdown(value="Scanning takes time, just wait. Check console log for detail", elem_id="ch_scan_model_log_md") + + + with gr.Box(elem_classes="ch_box"): + with gr.Column(): + gr.Markdown("### Get Model Info from Civitai by URL") + gr.Markdown("Use this when scanning can not find a local model on civitai") + with gr.Row(): + model_type_drop = gr.Dropdown(choices=model_types, label="Model Type", value="ckp", multiselect=False) + empty_info_only_ckb = gr.Checkbox(label="Only Show Models have no Info", value=False, elem_id="ch_empty_info_only_ckb", elem_classes="ch_vpadding") + model_name_drop = gr.Dropdown(choices=no_info_model_names, label="Model", value="ckp", multiselect=False) + + model_url_or_id_txtbox = gr.Textbox(label="Civitai URL", lines=1, value="") + get_civitai_model_info_by_id_btn = gr.Button(value="Get Model Info from Civitai", variant="primary") + get_model_by_id_log_md = gr.Markdown("") + + with gr.Box(elem_classes="ch_box"): + with gr.Column(): + gr.Markdown("### Download Model") + with gr.Row(): + 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") + + gr.Markdown(value="2. Pick Subfolder and Model Version") + with gr.Row(): + dl_model_name_txtbox = gr.Textbox(label="Model Name", interactive=False, lines=1, value="") + dl_model_type_txtbox = gr.Textbox(label="Model Type", interactive=False, lines=1, value="") + dl_subfolder_drop = gr.Dropdown(choices=[], label="Sub-folder", value="", interactive=True, multiselect=False) + dl_version_drop = gr.Dropdown(choices=[], label="Model Version", value="", interactive=True, multiselect=False) + dl_all_ckb = gr.Checkbox(label="Download All files", value=False, elem_id="ch_dl_all_ckb", elem_classes="ch_vpadding") + + dl_civitai_model_by_id_btn = gr.Button(value="3. Download Model", variant="primary") + dl_log_md = gr.Markdown(value="Check Console log for Downloading Status") + + with gr.Box(elem_classes="ch_box"): + with gr.Column(): + gr.Markdown("### Check models' new version") + with gr.Row(): + model_types_ckbg = gr.CheckboxGroup(choices=model_types, label="Model Types", value=["lora"]) + check_models_new_version_btn = gr.Button(value="Check New Version from Civitai", variant="primary") + + check_models_new_version_log_md = gr.HTML("It takes time, just wait. Check console log for detail") + + with gr.Box(elem_classes="ch_box"): + with gr.Column(): + gr.Markdown("### Other Setting") + with gr.Row(): + open_url_with_js_ckb = gr.Checkbox(label="Open Url At Client Side", value=open_url_with_js, elem_id="ch_open_url_with_js_ckb") + always_display_ckb = gr.Checkbox(label="Always Display Buttons", value=always_display, elem_id="ch_always_display_ckb") + show_btn_on_thumb_ckb = gr.Checkbox(label="Show Button On Thumb Mode", value=show_btn_on_thumb, elem_id="ch_show_btn_on_thumb_ckb") + + proxy_txtbox = gr.Textbox(label="Proxy", interactive=True, lines=1, value=proxy, info="format: http://127.0.0.1:port") + + save_setting_btn = gr.Button(value="Save Setting") + general_log_md = gr.Markdown(value="") + + + # ====Footer==== + gr.Markdown(f"
version:{util.version}
") + + # ====hidden component for js, not in any tab==== + js_msg_txtbox = gr.Textbox(label="Request Msg From Js", visible=False, lines=1, value="", elem_id="ch_js_msg_txtbox") + py_msg_txtbox = gr.Textbox(label="Response Msg From Python", visible=False, lines=1, value="", elem_id="ch_py_msg_txtbox") + + 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_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==== + # Scan Models for Civitai + scan_model_civitai_btn.click(model_action_civitai.scan_model, inputs=[scan_model_types_ckbg, max_size_preview_ckb, skip_nsfw_preview_ckb], outputs=scan_model_log_md) + + # Get Civitai Model Info by Model Page URL + model_type_drop.change(get_model_names_by_input, inputs=[model_type_drop, empty_info_only_ckb], outputs=model_name_drop) + empty_info_only_ckb.change(get_model_names_by_input, inputs=[model_type_drop, empty_info_only_ckb], outputs=model_name_drop) + + get_civitai_model_info_by_id_btn.click(model_action_civitai.get_model_info_by_input, inputs=[model_type_drop, model_name_drop, model_url_or_id_txtbox, max_size_preview_ckb, skip_nsfw_preview_ckb], outputs=get_model_by_id_log_md) + + # Download Model + dl_model_info_btn.click(get_model_info_by_url, inputs=dl_model_url_or_id_txtbox, outputs=[dl_model_info, dl_model_name_txtbox, dl_model_type_txtbox, dl_subfolder_drop, dl_version_drop]) + dl_civitai_model_by_id_btn.click(model_action_civitai.dl_model_by_input, inputs=[dl_model_info, dl_model_type_txtbox, dl_subfolder_drop, dl_version_drop, dl_all_ckb, max_size_preview_ckb, skip_nsfw_preview_ckb], outputs=dl_log_md) + + # Check models' new version + check_models_new_version_btn.click(model_action_civitai.check_models_new_version_to_md, inputs=model_types_ckbg, outputs=check_models_new_version_log_md) + + # Other Setting + save_setting_btn.click(setting.save_from_input, inputs=[max_size_preview_ckb, skip_nsfw_preview_ckb, open_url_with_js_ckb, always_display_ckb, show_btn_on_thumb_ckb, proxy_txtbox], outputs=general_log_md) + + # js action + 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_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 + return (civitai_helper , "Civitai Helper", "civitai_helper"), + +script_callbacks.on_ui_tabs(on_ui_tabs) + + + diff --git a/Stable-Diffusion-Webui-Civitai-Helper/style.css b/Stable-Diffusion-Webui-Civitai-Helper/style.css new file mode 100644 index 0000000000000000000000000000000000000000..387156e4cd2c93e798fd373e1a4797b2470ac921 --- /dev/null +++ b/Stable-Diffusion-Webui-Civitai-Helper/style.css @@ -0,0 +1,18 @@ +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; +} + diff --git a/a1111-microsoftexcel-locon/.gitignore b/a1111-microsoftexcel-locon/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ed8ebf583f771da9150c35db3955987b7d757904 --- /dev/null +++ b/a1111-microsoftexcel-locon/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/a1111-microsoftexcel-locon/LICENSE b/a1111-microsoftexcel-locon/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..60f8f075357a60b31be65673bfd917f95a0b2ae4 --- /dev/null +++ b/a1111-microsoftexcel-locon/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2023] [KohakuBlueLeaf] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.