From 2cfa149e3cb5b5772d5056c64800e4f20b2cf8ef Mon Sep 17 00:00:00 2001 From: BlafKing Date: Mon, 11 Mar 2024 01:05:36 +0100 Subject: [PATCH] Update to v3.4.2 --- README.md | 14 ++- install.py | 3 +- javascript/civitai-html.js | 210 ++++++++++++++++----------------- scripts/civitai_api.py | 144 ++++++++++++---------- scripts/civitai_download.py | 36 +++--- scripts/civitai_file_manage.py | 70 ++++++----- scripts/civitai_global.py | 4 + scripts/civitai_gui.py | 110 +++++++++++++---- style.css | 83 +++++++++++-- style_html.css | 59 +++++++++ 10 files changed, 484 insertions(+), 249 deletions(-) diff --git a/README.md b/README.md index 1278bd1..1af82e2 100644 --- a/README.md +++ b/README.md @@ -100,12 +100,22 @@ https://github.com/BlafKing/sd-civitai-browser-plus/assets/9644716/44c5c7a0-4854 # Changelog 📋 +

v3.4.2

+ +* Feature: Ability to set-up a custom proxy for API requests and downloads. +* Feature: Use image API for prompt info, should speed up loading. +* Feature: Optimized javascript code, improved webpage speed for some users. +* New setting: Proxy settings to set-up custom proxy. +* New setting: Toggle for saving description to model json file. (this displays description on the cards) +* Bug fix: Broken default sub folder option fixed [#217](https://github.com/BlafKing/sd-civitai-browser-plus/issues/217) + +---

v3.4.1

-* Bug fix: Fixed prompt info and model selection after CivitAI API update. -* Bug fix: Fixed "/" missing from default path/sub-folder. * Feature: Local images now work in HTML files as preview. (credit: [mx](https://github.com/mx)) * Feature: Updated available base models. +* Bug fix: Fixed prompt info and model selection after CivitAI API update. +* Bug fix: Fixed "/" missing from default path/sub-folder. ---

v3.4.0

diff --git a/install.py b/install.py index e81b40f..ffb0b0e 100644 --- a/install.py +++ b/install.py @@ -16,4 +16,5 @@ install_req("send2trash") install_req("zip_unicode", "ZipUnicode") install_req("bs4", "beautifulsoup4") install_req("fake_useragent") -install_req("packaging") \ No newline at end of file +install_req("packaging") +install_req("pysocks") \ No newline at end of file diff --git a/javascript/civitai-html.js b/javascript/civitai-html.js index 8ba714c..2cd9819 100644 --- a/javascript/civitai-html.js +++ b/javascript/civitai-html.js @@ -102,7 +102,7 @@ function updateCard(modelNameWithSuffix) { } // Enables refresh with alt+enter and ctrl+enter -document.addEventListener('keydown', function(e) { +function keydownHandler(e) { var handled = false; if (e.key !== undefined) { @@ -126,7 +126,8 @@ document.addEventListener('keydown', function(e) { e.preventDefault(); } } -}); +} +document.addEventListener('keydown', keydownHandler); // Function for the back to top button function BackToTop() { @@ -204,14 +205,21 @@ function pressRefresh() { setTimeout(() => { const input = document.querySelector("#pageSlider > div:nth-child(2) > div > input"); if (document.activeElement === input) { - input.addEventListener('keydown', function(event) { + function keydownHandler(event) { if (event.key === 'Enter' || event.keyCode === 13) { input.blur(); + input.removeEventListener('keydown', keydownHandler); + input.removeEventListener('blur', blurHandler); } - }); - input.addEventListener('blur', function() { - return; - }); + } + + function blurHandler() { + input.removeEventListener('keydown', keydownHandler); + input.removeEventListener('blur', blurHandler); + } + + input.addEventListener('keydown', keydownHandler); + input.addEventListener('blur', blurHandler); return; } @@ -328,24 +336,6 @@ function updateBackToTopVisibility(entries) { } } -// Options for the Intersection Observer -var options = { - root: null, - rootMargin: '0px 0px -60px 0px', - threshold: 0 -}; - -// Create an Intersection Observer instance -const observer = new IntersectionObserver(updateBackToTopVisibility, options); - -function handleCivitaiDivChanges() { - var civitaiDiv = document.getElementById('civitai_preview_html'); - observer.unobserve(civitaiDiv); - observer.observe(civitaiDiv); -} - -document.addEventListener("scroll", handleCivitaiDivChanges) - // Create the accordion dropdown inside the settings tab function createAccordion(containerDiv, subfolders, name) { if (containerDiv == null || subfolders.length == 0) { @@ -371,14 +361,19 @@ function createAccordion(containerDiv, subfolders, name) { } // Adds a button to the cards in txt2img and img2img -function createCardButtons(event) { - const clickedElement = event.target; +function createCivitAICardButtons(clickedElement=null) { + addOnClickToButtons(); const validButtonNames = ['Textual Inversion', 'Hypernetworks', 'Checkpoints', 'Lora']; const validParentIds = ['txt2img_textual_inversion_cards_html', 'txt2img_hypernetworks_cards_html', 'txt2img_checkpoints_cards_html', 'txt2img_lora_cards_html']; - const hasMatchingButtonName = clickedElement && clickedElement.innerText && validButtonNames.some(buttonName => - clickedElement.innerText.trim() === buttonName - ); + let hasMatchingButtonName = null; + if (clickedElement) { + hasMatchingButtonName = clickedElement && clickedElement.innerText && validButtonNames.some(buttonName => + clickedElement.innerText.trim() === buttonName + ); + } else { + hasMatchingButtonName = true; + } const flexboxDivs = document.querySelectorAll('.layoutkit-flexbox'); let isLobeTheme = false; @@ -429,10 +424,6 @@ function createCardButtons(event) { const newDiv = document.createElement('div'); newDiv.classList.add('goto-civitbrowser', 'card-button'); - newDiv.addEventListener('click', function (event) { - event.stopPropagation(); - modelInfoPopUp(modelName, content_type); - }); const svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); if (isLobeTheme) { @@ -451,7 +442,10 @@ function createCardButtons(event) { svgIcon.setAttribute('viewBox', `75 ${viewBoxHeight} 500 500`); svgIcon.setAttribute('fill', 'white'); svgIcon.setAttribute('style', `scale: ${cardScale}%;`); - + newDiv.onclick = function() { + modelInfoPopUp(modelName, content_type); + }; + svgIcon.innerHTML = ` @@ -461,10 +455,45 @@ function createCardButtons(event) { buttonRow.insertBefore(newDiv, buttonRow.firstChild); }); } - }, 100); + }, 200); + + setTimeout(() => { + clearInterval(checkForCardDivs); + }, 5000); } } -document.addEventListener('click', createCardButtons); + +function addOnClickToButtons() { + const img2img_extra_tabs = document.getElementById('img2img_extra_tabs'); + const txt2img_extra_tabs = document.getElementById('txt2img_extra_tabs'); + const txt2img_refresh_btn = document.getElementById('txt2img_checkpoints_extra_refresh'); + const img2img_refresh_btn = document.getElementById('img2img_checkpoints_extra_refresh'); + + txt2img_refresh_btn.onclick = function() { + createCivitAICardButtons(this); + }; + + img2img_refresh_btn.onclick = function() { + createCivitAICardButtons(this); + }; + + function addButtonClickEvent(div) { + var firstChildDiv = div.querySelector('div'); + if (firstChildDiv) { + var buttons = firstChildDiv.querySelectorAll('button'); + buttons.forEach(function(button, index) { + if (index !== 0) { + button.onclick = function() { + createCivitAICardButtons(this); + }; + } + }); + } + } + + addButtonClickEvent(img2img_extra_tabs); + addButtonClickEvent(txt2img_extra_tabs); +} function modelInfoPopUp(modelName, content_type) { select_model(modelName, null, true, content_type); @@ -480,6 +509,7 @@ function modelInfoPopUp(modelName, content_type) { overlay.style.backgroundColor = 'rgba(20, 20, 20, 0.95)'; overlay.style.zIndex = '1001'; overlay.style.overflowY = 'auto'; + overlay.addEventListener('keydown', handleKeyPress); // Create the close button var closeButton = document.createElement('div'); @@ -493,7 +523,6 @@ function modelInfoPopUp(modelName, content_type) { closeButton.style.color = 'white'; closeButton.style.fontSize = '32pt'; closeButton.addEventListener('click', hidePopup); - document.addEventListener('keydown', handleKeyPress); // Create the pop-up window var inner = document.createElement('div'); @@ -503,7 +532,7 @@ function modelInfoPopUp(modelName, content_type) { inner.style.left = '50%'; inner.style.width = 'auto'; inner.style.transform = 'translate(-50%, -50%)'; - inner.style.background = 'var(--body-background-fill)'; + inner.style.background = 'var(--neutral-950)'; inner.style.padding = '2em'; inner.style.borderRadius = 'var(--block-radius)'; inner.style.borderStyle = 'solid'; @@ -587,6 +616,8 @@ function inputHTMLPreviewContent(html_input) { } modelInfo.innerHTML = extractedText; inner.appendChild(modelInfo); + + setDescriptionToggle(); } } } @@ -695,64 +726,6 @@ function multi_model_select(modelName, modelType, isChecked) { updateInput(selected_type_list); } -// Metadata button click detector -document.addEventListener('click', function(event) { - var target = event.target; - if (target.classList.contains('edit-button') && target.classList.contains('card-button')) { - var parentDiv = target.parentElement; - var actionsDiv = parentDiv.nextElementSibling; - if (actionsDiv && actionsDiv.classList.contains('actions')) { - var nameSpan = actionsDiv.querySelector('.name'); - if (nameSpan) { - var nameValue = nameSpan.textContent; - onEditButtonCardClick(nameValue); - } - } - } -}, true); - -// CivitAI Link Button Creation -function onEditButtonCardClick(nameValue) { - var checkInterval = setInterval(function() { - var globalPopupInner = document.querySelector('.global-popup-inner'); - var titleElement = globalPopupInner.querySelector('.extra-network-name'); - if (titleElement.textContent.trim() === nameValue.trim()) { - var descriptionSpan = Array.from(globalPopupInner.querySelectorAll('span')).find(span => span.textContent.trim() === "Description"); - if (descriptionSpan) { - var descriptionTextarea = descriptionSpan.nextElementSibling; - if (descriptionTextarea.value.startsWith('Model URL:')) { - var matches = descriptionTextarea.value.match(/"([^"]+)"/); - if (matches && matches[1]) { - var modelUrl = matches[1]; - - var grandParentDiv = descriptionTextarea.parentElement.parentElement.parentElement.parentElement; - var imageDiv = grandParentDiv.nextElementSibling - var openInCivitaiDiv = document.querySelector('.open-in-civitai'); - if (!openInCivitaiDiv) { - openInCivitaiDiv = document.createElement('div'); - openInCivitaiDiv.classList.add('open-in-civitai'); - imageDiv.appendChild(openInCivitaiDiv); - } - openInCivitaiDiv.innerHTML = 'Open on CivitAI'; - } - else { - var openInCivitaiDiv = document.querySelector('.open-in-civitai'); - if (openInCivitaiDiv) { - openInCivitaiDiv.remove(); - } - } - } else { - var openInCivitaiDiv = document.querySelector('.open-in-civitai'); - if (openInCivitaiDiv) { - openInCivitaiDiv.remove(); - } - } - } - clearInterval(checkInterval); - } - }, 100); -} - function sendClick(location) { const clickEvent = new MouseEvent('click', { view: window, @@ -961,59 +934,82 @@ function hideInstalled(toggleValue) { }); } +function setDescriptionToggle() { + const popUp = document.querySelector(".civitai-overlay-inner"); + let toggleButton = null; + let descriptionDiv = null; + + if (popUp) { + descriptionDiv = popUp.querySelector(".model-description"); + toggleButton = popUp.querySelector(".description-toggle-label"); + } else { + descriptionDiv = document.querySelector(".model-description"); + toggleButton = document.querySelector(".description-toggle-label"); + } + + if (descriptionDiv && descriptionDiv.scrollHeight <= 400) { + toggleButton.style.visibility = "hidden"; + toggleButton.style.height = "0"; + descriptionDiv.style.position = "unset"; + } +} + // Runs all functions when the page is fully loaded function onPageLoad() { const divElement = document.getElementById('setting_custom_api_key'); - let civitaiDiv = document.getElementById('civitai_preview_html'); - let queue_list = document.querySelector("#queue_list"); const infoElement = divElement?.querySelector('.info'); if (!infoElement) { return; } - clearInterval(intervalID); + updateSVGIcons(); let subfolderDiv = document.querySelector("#settings_civitai_browser_plus > div > div"); let downloadDiv = document.querySelector("#settings_civitai_browser_download > div > div"); + let upscalerDiv = document.querySelector("#settings_civitai_browser_plus > div > div > #settings-accordion > div"); + let downloadDivSub = document.querySelector("#settings_civitai_browser_download > div > div > #settings-accordion > div"); + let settingsDiv = document.querySelector("#settings_civitai_browser > div > div"); + if (subfolderDiv || downloadDiv) { let div = subfolderDiv || downloadDiv; let subfolders = div.querySelectorAll("[id$='subfolder']"); createAccordion(div, subfolders, "Default sub folders"); } - let upscalerDiv = document.querySelector("#settings_civitai_browser_plus > div > div > #settings-accordion > div"); - let downloadDivSub = document.querySelector("#settings_civitai_browser_download > div > div > #settings-accordion > div"); if (upscalerDiv || downloadDivSub) { let div = upscalerDiv || downloadDivSub; let upscalers = div.querySelectorAll("[id$='upscale_subfolder']"); createAccordion(div, upscalers, "Upscalers"); } - let settingsDiv = document.querySelector("#settings_civitai_browser > div > div"); if (subfolderDiv || settingsDiv) { let div = subfolderDiv || settingsDiv; let subfolders = div.querySelectorAll("[id^='setting_insert_sub']"); createAccordion(div, subfolders, "Insert sub folder options"); + + let proxy = div.querySelectorAll("[id$='proxy']"); + createAccordion(div, proxy, "Proxy options"); } - let toggle4L = document.getElementById('toggle4L'); let toggle4 = document.getElementById('toggle4'); + let hash_toggle_hover = document.querySelector('#skip_hash_toggle > label'); + let hash_toggle = document.querySelector('#skip_hash_toggle'); + if (toggle4L || toggle4) { let like_toggle = toggle4L || toggle4; let insertText = 'Requires an API Key\nConfigurable in CivitAI settings tab'; createTooltip(like_toggle, like_toggle, insertText); } - let hash_toggle_hover = document.querySelector('#skip_hash_toggle > label'); - let hash_toggle = document.querySelector('#skip_hash_toggle'); if (hash_toggle) { let insertText = 'This option generates unique hashes for models that were not downloaded with this extension.\nA hash is required for any of the options below to work, a model with no hash will be skipped.\nInitial hash generation is a one-time process per file.'; createTooltip(hash_toggle, hash_toggle_hover, insertText); } - observer.observe(civitaiDiv); + addOnClickToButtons(); + createCivitAICardButtons(); adjustFilterBoxAndButtons(); setupClickOutsideListener(); createLink(infoElement); diff --git a/scripts/civitai_api.py b/scripts/civitai_api.py index 9c8eb0c..cf43b51 100644 --- a/scripts/civitai_api.py +++ b/scripts/civitai_api.py @@ -8,15 +8,10 @@ import os import re import datetime import platform -import time from PIL import Image from io import BytesIO from collections import defaultdict from modules.images import read_info_from_image -try: - from modules.generation_parameters_copypaste import parse_generation_parameters -except: - from modules.infotext_utils import parse_generation_parameters from modules.shared import cmd_opts, opts from modules.paths import models_path, extensions_dir, data_path @@ -372,7 +367,7 @@ def update_next_page(content_type, sort_type, period_type, use_search_term, sear if 'LoCon' not in content_type: content_type.append('LoCon') - if gl.json_data is None or gl.json_data == "timeout": + if gl.json_data is None or gl.json_data == "timeout" or gl.json_data == "error": timeOut = True return_values = update_model_list(content_type, sort_type, period_type, use_search_term, search_term, current_page, base_filter, only_liked, nsfw, timeOut=timeOut, isNext=isNext) timeOut = False @@ -443,8 +438,14 @@ def update_next_page(content_type, sort_type, period_type, use_search_term, sear hasPrev = current_page not in [0, 1] hasNext = current_page == 1 or hasPrev model_dict = {} + + if gl.json_data == "error": + HTML = '
The Civit-API has failed to return due to an error.
Check the logs for more details.
' + hasPrev = current_page not in [0, 1] + hasNext = current_page == 1 or hasPrev + model_dict = {} - if gl.json_data != None and gl.json_data != "timeout": + if gl.json_data != None and gl.json_data != "timeout" and gl.json_data != "error": (hasPrev, hasNext, current_page, total_pages) = pagecontrol(gl.json_data) model_dict = {} try: @@ -517,13 +518,19 @@ def update_model_list(content_type=None, sort_type=None, period_type=None, use_s hasPrev = current_page not in [0, 1] hasNext = current_page == 1 or hasPrev + if gl.json_data == "error": + HTML = '
The Civit-API has failed to return due to an error.
Check the logs for more details.
' + hasPrev = current_page not in [0, 1] + hasNext = current_page == 1 or hasPrev + model_dict = {} + if gl.json_data is None: return if from_installed or from_ver: gl.json_data = gl.ver_json - if gl.json_data != None and gl.json_data != "timeout": + if gl.json_data != None and gl.json_data != "timeout" and gl.json_data != "error": if not from_ver: (hasPrev, hasNext, current_page, total_pages) = pagecontrol(gl.json_data) else: @@ -629,10 +636,11 @@ def cleaned_name(file_name): return f"{clean_name}{extension}" def fetch_and_process_image(image_url): + proxies, ssl = get_proxies() try: parsed_url = urllib.parse.urlparse(image_url) if parsed_url.scheme and parsed_url.netloc: - response = requests.get(image_url) + response = requests.get(image_url, proxies=proxies, verify=ssl) if response.status_code == 200: image = Image.open(BytesIO(response.content)) geninfo, _ = read_info_from_image(image) @@ -644,34 +652,6 @@ def fetch_and_process_image(image_url): except: return None -def image_url_to_promptInfo(image_url): - response = requests.get(image_url) - if response.status_code == 200: - image = Image.open(BytesIO(response.content)) - - prompt, _ = read_info_from_image(image) - if prompt: - prompt_dict = parse_generation_parameters(prompt) - - invalid_values = [None, 0, "", "Use same sampler", "Use same checkpoint"] - keys_to_remove = [key for key, value in prompt_dict.items() if key != "Clip skip" and value in invalid_values] - for key in keys_to_remove: - prompt_dict.pop(key, None) - - if "Size-1" in prompt_dict and "Size-2" in prompt_dict: - prompt_dict["Size"] = f'{prompt_dict["Size-1"]}x{prompt_dict["Size-2"]}' - prompt_dict.pop("Size-1", None) - prompt_dict.pop("Size-2", None) - if "Hires resize-1" in prompt_dict and "Hires resize-2" in prompt_dict: - prompt_dict["Hires resize"] = f'{prompt_dict["Hires resize-1"]}x{prompt_dict["Hires resize-2"]}' - prompt_dict.pop("Hires resize-1", None) - prompt_dict.pop("Hires resize-2", None) - - return prompt_dict - else: - return [] - return [] - def extract_model_info(input_string): last_open_parenthesis = input_string.rfind("(") last_close_parenthesis = input_string.rfind(")") @@ -797,19 +777,31 @@ def update_model_info(model_string=None, model_version=None, only_html=False, in model_url = selected_version.get('downloadUrl', '') model_main_url = f"https://civitai.com/models/{item['id']}" img_html = '
' + + url = f"https://civitai.com/api/v1/images?modelId={item['id']}&modelVersionId={selected_version['id']}&username={model_uploader}" + model_images = request_civit_api(url) + for index, pic in enumerate(selected_version['images']): + + if from_preview: + index = f"preview_{index}" + + for item in model_images['items']: + if item['id'] == pic['id']: + current_image = item + # Change width value in URL to original image width image_url = re.sub(r'/width=\d+', f'/width={pic["width"]}', pic["url"]) if pic['type'] == "video": image_url = image_url.replace("width=", "transcode=true,width=") prompt_dict = [] else: - prompt_dict = image_url_to_promptInfo(image_url) + prompt_dict = current_image['meta'] nsfw = 'class="model-block"' meta_button = False - if prompt_dict and prompt_dict.get('Prompt'): + if prompt_dict and prompt_dict.get('prompt'): meta_button = True BtnImage = True @@ -846,16 +838,29 @@ def update_model_info(model_string=None, model_version=None, only_html=False, in if prompt_dict: img_html += '
' - # Define the preferred order of keys and convert them to lowercase - preferred_order = ["Prompt", "Negative prompt", "Seed", "Size", "Model", "Clip skip", "Sampler", "Steps", "CFG scale"] + # Define the preferred order of keys + preferred_order = ["prompt", "negativePrompt", "seed", "Size", "Model", "Clip skip", "sampler", "steps", "cfgScale"] # Loop through the keys in the preferred order and add them to the HTML for key in preferred_order: if key in prompt_dict: value = prompt_dict[key] + key_map = { + "prompt": "Prompt", + "negativePrompt": "Negative prompt", + "seed": "Seed", + "Size": "Size", + "Model": "Model", + "Clip skip": "Clip skip", + "sampler": "Sampler", + "steps": "Steps", + "cfgScale": "CFG scale" + } + key = key_map.get(key, key) + if meta_btn: - img_html += f'
{escape(str(key).capitalize())}
{escape(str(value))}
' + img_html += f'
{escape(str(key))}
{escape(str(value))}
' else: - img_html += f'
{escape(str(key).capitalize())}
{escape(str(value))}
' + img_html += f'
{escape(str(key))}
{escape(str(value))}
' # Check if there are remaining keys in meta remaining_keys = [key for key in prompt_dict if key not in preferred_order] @@ -917,10 +922,12 @@ def update_model_info(model_string=None, model_version=None, only_html=False, in
-
+ +

Description

{model_desc}
+
{img_html}
''' @@ -991,7 +998,7 @@ def update_model_info(model_string=None, model_version=None, only_html=False, in sub_folders.remove("None") sub_folders = sorted(sub_folders, key=lambda x: (x.lower(), x)) sub_folders.insert(0, "None") - base = cleaned_name(model_uploader) + base = cleaned_name(output_basemodel) author = cleaned_name(model_uploader) name = cleaned_name(model_name) ver = cleaned_name(model_version) @@ -1226,37 +1233,54 @@ def update_file_info(model_string, model_version, file_metadata): gr.Dropdown.update(choices=None, value=None, interactive=False) # Sub Folder List ) -def get_headers(): +def get_proxies(): + custom_proxy = getattr(opts, "custom_civitai_proxy", "") + disable_ssl = getattr(opts, "disable_sll_proxy", False) + cabundle_path = getattr(opts, "cabundle_path_proxy", "") + + ssl = True + proxies = {} + if custom_proxy: + if not disable_ssl: + if cabundle_path: + ssl = os.path(cabundle_path) + else: + ssl = False + proxies = { + 'http': custom_proxy, + 'https': custom_proxy, + } + return proxies, ssl + +def get_headers(referer=None, no_api=None): + api_key = getattr(opts, "custom_api_key", "") try: user_agent = UserAgent().chrome except ImportError: - user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" headers = { - 'User-Agent': user_agent, - 'Sec-Ch-Ua': '"Brave";v="119", "Chromium";v="119", "Not?A_Brand";v="24"', - 'Sec-Ch-Ua-Mobile': '?0', - 'Sec-Ch-Ua-Platform': '"Windows"', - 'Sec-Fetch-Dest': 'document', - 'Sec-Fetch-Mode': 'navigate', - 'Sec-Fetch-Site': 'none', - 'Sec-Fetch-User': '?1', - 'Sec-Gpc': '1', - 'Upgrade-Insecure-Requests': '1', + "Connection": "keep-alive", + "Sec-Ch-Ua-Platform": "Windows", + "User-Agent": user_agent, + "Content-Type": "application/json" } - if api_key: + if referer: + headers['Referer'] = f"https://civitai.com/models/{referer}" + if api_key and not no_api: headers['Authorization'] = f'Bearer {api_key}' return headers def request_civit_api(api_url=None): headers = get_headers() + proxies, ssl = get_proxies() try: - response = requests.get(api_url, headers=headers, timeout=(10, 30)) + response = requests.get(api_url, headers=headers, timeout=(60,30), proxies=proxies, verify=ssl) response.raise_for_status() except requests.exceptions.RequestException as e: print(f"Error: {e}") - return "timeout" + return "error" else: response.encoding = "utf-8" try: diff --git a/scripts/civitai_download.py b/scripts/civitai_download.py index 71bfcea..c41461f 100644 --- a/scripts/civitai_download.py +++ b/scripts/civitai_download.py @@ -68,7 +68,10 @@ def start_aria2_rpc(): try: show_log = getattr(opts, "show_log", False) aria2_flags = getattr(opts, "aria2_flags", "") - cmd = f'"{aria2}" --enable-rpc --rpc-listen-all --rpc-listen-port=24000 --rpc-secret {rpc_secret} --check-certificate=false --ca-certificate=" " --file-allocation=none {aria2_flags}' + custom_proxy = getattr(opts, "custom_proxy", "") + if custom_proxy: + custom_proxy = f"--all-proxy={custom_proxy} " + cmd = f'"{aria2}" --enable-rpc --rpc-listen-all --rpc-listen-port=24000 --rpc-secret {rpc_secret} --check-certificate=false --ca-certificate=" " {custom_proxy}--file-allocation=none {aria2_flags}' subprocess_args = {'shell': True} if not show_log: subprocess_args.update({'stdout': subprocess.DEVNULL, 'stderr': subprocess.DEVNULL}) @@ -323,10 +326,11 @@ def convert_size(size): size /= 1024 return f"{size:.2f} GB" -def get_download_link(url): - headers = _api.get_headers() - - response = requests.get(url, headers=headers, allow_redirects=False) +def get_download_link(url, model_id): + headers = _api.get_headers(model_id) + proxies, ssl = _api.get_proxies() + + response = requests.get(url, headers=headers, allow_redirects=False, proxies=proxies, verify=ssl) if 300 <= response.status_code <= 308: if "login?returnUrl" in response.text and "reason=download-auth" in response.text: @@ -337,7 +341,7 @@ def get_download_link(url): else: return None -def download_file(url, file_path, install_path, progress=gr.Progress() if queue else None): +def download_file(url, file_path, install_path, model_id, progress=gr.Progress() if queue else None): try: disable_dns = getattr(opts, "disable_dns", False) split_aria2 = getattr(opts, "split_aria2", 64) @@ -347,7 +351,7 @@ def download_file(url, file_path, install_path, progress=gr.Progress() if queue file_name = os.path.basename(file_path) - download_link = get_download_link(url) + download_link = get_download_link(url, model_id) if not download_link: print(f'File: "{file_name}" not found on CivitAI servers, it looks like the file is not available for download.') gl.download_fail = True @@ -485,7 +489,7 @@ def info_to_json(install_path, model_id, model_sha256, unpackList=None): with open(json_file, 'w', encoding="utf-8") as f: json.dump(data, f, indent=4) -def download_file_old(url, file_path, progress=gr.Progress() if queue else None): +def download_file_old(url, file_path, model_id, progress=gr.Progress() if queue else None): try: gl.download_fail = False max_retries = 5 @@ -499,7 +503,7 @@ def download_file_old(url, file_path, progress=gr.Progress() if queue else None) last_update_time = 0 update_interval = 0.25 - download_link = get_download_link(url) + download_link = get_download_link(url, model_id) if not download_link: print(f'File: "{file_name_display}" not found on CivitAI servers, it looks like the file is not available for download.') if progress != None: @@ -516,6 +520,9 @@ def download_file_old(url, file_path, progress=gr.Progress() if queue else None) time.sleep(5) return + headers = _api.get_headers(model_id, True) + proxies, ssl = _api.get_proxies() + while True: if gl.cancel_status: if progress != None: @@ -523,9 +530,8 @@ def download_file_old(url, file_path, progress=gr.Progress() if queue else None) return if os.path.exists(file_path): downloaded_size = os.path.getsize(file_path) - headers = {"Range": f"bytes={downloaded_size}-"} - else: - headers = {} + headers['Range'] = f"bytes={downloaded_size}-" + with open(file_path, "ab") as f: while gl.isDownloading: try: @@ -538,7 +544,7 @@ def download_file_old(url, file_path, progress=gr.Progress() if queue else None) if progress != None: progress(0, desc=f"Download cancelled.") return - response = requests.get(download_link, headers=headers, stream=True, timeout=4) + response = requests.get(download_link, headers=headers, stream=True, timeout=10, proxies=proxies, verify=ssl) if response.status_code == 404: if progress != None: progress(0, desc=f"Encountered an error during download of: {file_name_display}, file is not found on CivitAI servers.") @@ -645,9 +651,9 @@ def download_create_thread(download_finish, queue_trigger, progress=gr.Progress( path_to_new_file = os.path.join(item['install_path'], item['model_filename']) if use_aria2 and os_type != 'Darwin': - thread = threading.Thread(target=download_file, args=(item['dl_url'], path_to_new_file, item['install_path'], progress)) + thread = threading.Thread(target=download_file, args=(item['dl_url'], path_to_new_file, item['install_path'], item['model_id'], progress)) else: - thread = threading.Thread(target=download_file_old, args=(item['dl_url'], path_to_new_file, progress)) + thread = threading.Thread(target=download_file_old, args=(item['dl_url'], path_to_new_file, item['model_id'], progress)) thread.start() thread.join() diff --git a/scripts/civitai_file_manage.py b/scripts/civitai_file_manage.py index 2f96f9b..ac2258e 100644 --- a/scripts/civitai_file_manage.py +++ b/scripts/civitai_file_manage.py @@ -161,6 +161,7 @@ def delete_associated_files(directory, base_name): print(f"Associated file deleted: {path_to_delete}") def save_preview(file_path, api_response, overwrite_toggle=False, sha256=None): + proxies, ssl = _api.get_proxies() json_file = os.path.splitext(file_path)[0] + ".json" install_path, file_name = os.path.split(file_path) name = os.path.splitext(file_name)[0] @@ -191,7 +192,7 @@ def save_preview(file_path, api_response, overwrite_toggle=False, sha256=None): if image["type"] == "image": url_with_width = re.sub(r'/width=\d+', f'/width={image["width"]}', image["url"]) - response = requests.get(url_with_width) + response = requests.get(url_with_width, proxies=proxies, verify=ssl) if response.status_code == 200: with open(image_path, 'wb') as img_file: img_file.write(response.content) @@ -373,6 +374,7 @@ def model_from_sent(model_name, content_type, tile_count): not_found = div + "Model ID not found.
Maybe the model doesn\'t exist on CivitAI?" path_not_found = div + "Model ID not found.
Could not locate the model path." offline = div + "CivitAI failed to respond.
The servers are likely offline." + error = div + "CivitAI failed to respond due to an error.
Check the logs for more details." model_name = re.sub(r'\.\d{3}$', '', model_name) content_type = re.sub(r'\.\d{3}$', '', content_type) @@ -426,22 +428,24 @@ def model_from_sent(model_name, content_type, tile_count): if json_data == "timeout": output_html = offline - if json_data != None and json_data != "timeout": + if json_data == "error": + output_html = error + if json_data != None and json_data != "timeout" and json_data != "error": model_versions = _api.update_model_versions(modelID, json_data) - output_html = _api.update_model_info(None, model_versions.get('value'), True, modelID, json_data) + output_html = _api.update_model_info(None, model_versions.get('value'), True, modelID, json_data, True) css_path = Path(__file__).resolve().parents[1] / "style_html.css" with open(css_path, 'r', encoding='utf-8') as css_file: css = css_file.read() replacements = { - '#0b0f19': 'var(--body-background-fill)', + '#0b0f19': 'var(--neutral-950)', '#F3F4F6': 'var(--body-text-color)', 'white': 'var(--body-text-color)', '#80a6c8': 'var(--secondary-300)', '#60A5FA': 'var(--link-text-color-hover)', - '#1F2937': 'var(--input-background-fill)', + '#1F2937': 'var(--neutral-700)', '#374151': 'var(--input-border-color)', - '#111827': 'var(--error-background-fill)', + '#111827': 'var(--neutral-800)', 'top: 50%;': '', 'padding-top: 0px;': 'padding-top: 475px;', '.civitai_txt2img': '.civitai_placeholder' @@ -554,6 +558,7 @@ def save_model_info(install_path, file_name, sub_folder, sha256=None, preview_ht def find_and_save(api_response, sha256=None, file_name=None, json_file=None, no_hash=None, overwrite_toggle=None): + save_desc = getattr(opts, "model_desc_to_json", True) for item in api_response.get('items', []): for model_version in item.get('modelVersions', []): for file in model_version.get('files', []): @@ -563,15 +568,11 @@ def find_and_save(api_response, sha256=None, file_name=None, json_file=None, no_ if file_name == file_name_api if no_hash else sha256 == sha256_api: gl.json_info = item trained_words = model_version.get('trainedWords', []) - model_id = model_version.get('modelId', '') - if model_id: - model_url = f'Model URL: \"https://civitai.com/models/{model_id}\"\n' - - description = item.get('description', '') - if description != None: - description = clean_description(description) - description = model_url + description + if save_desc: + description = item.get('description', '') + if description != None: + description = clean_description(description) base_model = model_version.get('baseModel', '') @@ -605,22 +606,25 @@ def find_and_save(api_response, sha256=None, file_name=None, json_file=None, no_ if "activation text" not in content or not content["activation text"]: content["activation text"] = trained_tags changed = True - if "description" not in content or not content["description"]: - content["description"] = description - changed = True + if save_desc: + if "description" not in content or not content["description"]: + content["description"] = description + changed = True if "sd version" not in content or not content["sd version"]: content["sd version"] = base_model changed = True else: content["activation text"] = trained_tags - content["description"] = description + if save_desc: + content["description"] = description content["sd version"] = base_model changed = True with open(json_file, 'w', encoding="utf-8") as f: json.dump(content, f, indent=4) - if changed: print(f"Model info saved to \"{json_file}\"") + if changed: + print(f"Model info saved to \"{json_file}\"") return "found" return "not found" @@ -651,10 +655,10 @@ def get_models(file_path, gen_hash=None): return modelId else: return None - + proxies, ssl = _api.get_proxies() try: if not modelId or modelId == "Model not found": - response = requests.get(by_hash, timeout=(10,30)) + response = requests.get(by_hash, timeout=(60,30), proxies=proxies, verify=ssl) if response.status_code == 200: api_response = response.json() if 'error' in api_response: @@ -761,9 +765,9 @@ def get_content_choices(scan_choices=False): return content_list return content_list -def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish, overwrite_toggle, tile_count, gen_hash, progress=gr.Progress() if queue else None): +def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish, overwrite_toggle, tile_count, gen_hash, create_html, progress=gr.Progress() if queue else None): global from_ver, from_installed, no_update - update_log = getattr(opts, "update_log", True) + proxies, ssl = _api.get_proxies() gl.scan_files = True no_update = False if from_ver: @@ -777,7 +781,7 @@ def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish, if not folders: if progress != None: - progress(0, desc=f"No folder selected.") + progress(0, desc=f"No model type selected.") no_update = True gl.scan_files = False from_ver, from_installed = False, False @@ -857,13 +861,13 @@ def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish, model_id = get_models(file_path, gen_hash) if model_id == "offline": print("The CivitAI servers did not respond, unable to retrieve Model ID") - elif model_id == "Model not found" and update_log: + elif model_id == "Model not found": print(f"model: \"{file_name}\" not found on CivitAI servers.") elif model_id != None: all_model_ids.append(f"&ids={model_id}") all_ids.append(model_id) file_paths.append(file_path) - elif not model_id and update_log: + elif not model_id: print(f"model ID not found for: \"{file_name}\"") files_done += 1 @@ -901,7 +905,7 @@ def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish, try: if progress is not None: progress(url_done / url_count, desc=f"Sending API request... {url_done}/{url_count}") - response = requests.get(url, timeout=(10, 30)) + response = requests.get(url, timeout=(60,30), proxies=proxies, verify=ssl) if response.status_code == 200: api_response_json = response.json() @@ -947,9 +951,8 @@ def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish, all_model_ids = [model[0] for model in outdated_set] all_model_names = [model[1] for model in outdated_set] - if update_log: - for model_name in all_model_names: - print(f'"{model_name}" is currently outdated.') + for model_name in all_model_names: + print(f'"{model_name}" is currently outdated.') if len(all_model_ids) == 0: no_update = True @@ -969,7 +972,7 @@ def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish, api_url = gl.url_list_with_numbers.get(1) if not url_error: - response = requests.get(api_url, timeout=(10,30)) + response = requests.get(api_url, timeout=(60,30), proxies=proxies, verify=ssl) try: if response.status_code == 200: response.encoding = "utf-8" @@ -1017,7 +1020,10 @@ def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish, for file_path, id_value in zip(file_paths, all_ids): install_path, file_name = os.path.split(file_path) model_versions = _api.update_model_versions(id_value, api_response) - preview_html = _api.update_model_info(None, model_versions.get('value'), True, id_value, api_response) + if create_html: + preview_html = _api.update_model_info(None, model_versions.get('value'), True, id_value, api_response, True) + else: + preview_html = None sub_folder = os.path.normpath(os.path.relpath(install_path, gl.main_folder)) save_model_info(install_path, file_name, sub_folder, preview_html=preview_html, api_response=api_response, overwrite_toggle=overwrite_toggle) if progress != None: diff --git a/scripts/civitai_global.py b/scripts/civitai_global.py index d322db6..71567dd 100644 --- a/scripts/civitai_global.py +++ b/scripts/civitai_global.py @@ -1,4 +1,8 @@ def init(): + import warnings + from urllib3.exceptions import InsecureRequestWarning + warnings.simplefilter('ignore', InsecureRequestWarning) + global download_queue, last_version, cancel_status, recent_model, json_data, json_info, main_folder, previous_inputs, download_fail, sortNewest, isDownloading, old_download, scan_files, ver_json, file_scan, url_list_with_numbers, print cancel_status = None diff --git a/scripts/civitai_gui.py b/scripts/civitai_gui.py index 365938e..1a9e53c 100644 --- a/scripts/civitai_gui.py +++ b/scripts/civitai_gui.py @@ -189,7 +189,7 @@ def on_ui_tabs(): base_filter = gr.Dropdown(label='Base model:', multiselect=True, choices=["SD 1.4","SD 1.5","SD 1.5 LCM","SD 2.0","SD 2.0 768","SD 2.1","SD 2.1 768","SD 2.1 Unclip","SDXL 0.9","SDXL 1.0","SDXL 1.0 LCM","SDXL Distilled","SDXL Turbo","SDXL Lightning","Stable Cascade","Pony","SVD","SVD XT","Playground v2","PixArt a","Other"], value=None, type="value", elem_id="centerText") with gr.Row(): period_type = gr.Dropdown(label='Time period:', choices=["All Time", "Year", "Month", "Week", "Day"], value="All Time", type="value", elem_id="centerText") - sort_type = gr.Dropdown(label='Sort by:', choices=["Newest","Most Downloaded","Highest Rated","Most Liked", "Most Buzz","Most Discussed","Most Collected","Most Images"], value="Most Downloaded", type="value", elem_id="centerText") + sort_type = gr.Dropdown(label='Sort by:', choices=["Newest","Oldest","Most Downloaded","Highest Rated","Most Liked","Most Buzz","Most Discussed","Most Collected","Most Images"], value="Most Downloaded", type="value", elem_id="centerText") with gr.Row(elem_id=component_id): create_json = gr.Checkbox(label=f"Save info after download", value=True, elem_id=toggle1, min_width=171) show_nsfw = gr.Checkbox(label="NSFW content", value=False, elem_id=toggle2, min_width=107) @@ -198,7 +198,7 @@ def on_ui_tabs(): hide_installed = gr.Checkbox(label="Hide installed models", value=False, elem_id=toggle5, min_width=170) with gr.Row(): size_slider = gr.Slider(minimum=4, maximum=20, value=8, step=0.25, label='Tile size:') - tile_count_slider = gr.Slider(label="Tile count:", minimum=1, maximum=100, value=15, step=1, max_width=100) + tile_count_slider = gr.Slider(label="Tile count:", minimum=1, maximum=100, value=15, step=1) with gr.Row(elem_id="save_set_box"): save_settings = gr.Button(value="Save settings as default", elem_id="save_set_btn") search_term = gr.Textbox(label="", placeholder="Search CivitAI", elem_id="searchBox") @@ -229,7 +229,7 @@ def on_ui_tabs(): with gr.Column(scale=4): trained_tags = gr.Textbox(label='Trained tags (if any):', value=None, interactive=False, lines=1) with gr.Column(scale=2, elem_id="spanWidth"): - base_model = gr.Textbox(label='Base model: ', value='', interactive=False, lines=1, elem_id="baseMdl") + base_model = gr.Textbox(label='Base model: ', value=None, interactive=False, lines=1, elem_id="baseMdl") model_filename = gr.Textbox(label="Model filename:", interactive=False, value=None) with gr.Row(): save_info = gr.Button(value="Save model info", interactive=False) @@ -248,10 +248,10 @@ def on_ui_tabs(): with gr.Tab("Update Models"): with gr.Row(): selected_tags = gr.CheckboxGroup(elem_id="selected_tags", label="Scan for:", choices=scan_choices) - with gr.Row(): - overwrite_toggle = gr.Checkbox(elem_id="overwrite_toggle", label="Overwrite any existing previews, tags or descriptions.", value=True) - with gr.Row(): - skip_hash_toggle = gr.Checkbox(elem_id="skip_hash_toggle", label="One-Time Hash Generation for externally downloaded models.", value=True) + with gr.Row(elem_id="civitai_update_toggles"): + overwrite_toggle = gr.Checkbox(elem_id="overwrite_toggle", label="Overwrite any existing previews, tags or descriptions.", value=True, min_width=300) + skip_hash_toggle = gr.Checkbox(elem_id="skip_hash_toggle", label="One-Time Hash Generation for externally downloaded models.", value=True, min_width=300) + do_html_gen = gr.Checkbox(elem_id="do_html_gen", label="Save HTML file for each model when updating info & tags (increases process time).", value=False, min_width=300) with gr.Row(): save_all_tags = gr.Button(value="Update model info & tags", interactive=True, visible=True) cancel_all_tags = gr.Button(value="Cancel updating model info & tags", interactive=False, visible=False) @@ -349,6 +349,7 @@ def on_ui_tabs(): list_models.select(fn=None, inputs=list_models, _js="(list_models) => select_model(list_models)") preview_html.change(fn=None, _js="() => adjustFilterBoxAndButtons()") + preview_html.change(fn=None, _js="() => setDescriptionToggle()") back_to_top.click(fn=None, _js="() => BackToTop()") @@ -421,13 +422,36 @@ def on_ui_tabs(): list_html.change(fn=all_visible,inputs=list_html,outputs=select_all) def update_models_dropdown(input): + if not gl.json_data: + return ( + gr.Dropdown.update(value=None, choices=[], interactive=False), # List models + gr.Dropdown.update(value=None, choices=[], interactive=False), # List version + gr.HTML.update(value=None), # Preview HTML + gr.Textbox.update(value=None, interactive=False), # Trained Tags + gr.Textbox.update(value=None, interactive=False), # Base Model + gr.Textbox.update(value=None, interactive=False), # Model filename + gr.Textbox.update(value=None, interactive=False), # Install path + gr.Dropdown.update(value=None, choices=[], interactive=False), # Sub folder + gr.Button.update(interactive=False), # Download model btn + gr.Button.update(interactive=False), # Save image btn + gr.Button.update(interactive=False, visible=False), # Delete model btn + gr.Dropdown.update(value=None, choices=[], interactive=False), # File list + gr.Textbox.update(value=None), # DL Url + gr.Textbox.update(value=None), # Model ID + gr.Textbox.update(value=None), # Current sha256 + gr.Button.update(interactive=False), # Save model info + gr.HTML.update(value='
Click the search icon to load models.
Use the filter icon to filter results.
') # Model list + ) + model_string = re.sub(r'\.\d{3}$', '', input) model_name, model_id = _api.extract_model_info(model_string) model_versions = _api.update_model_versions(model_id) (html, tags, base_mdl, DwnButton, SaveImages, DelButton, filelist, filename, dl_url, id, current_sha256, install_path, sub_folder) = _api.update_model_info(model_string, model_versions.get('value')) return (gr.Dropdown.update(value=model_string, interactive=True), model_versions,html,tags,base_mdl,filename,install_path,sub_folder,DwnButton,SaveImages,DelButton,filelist,dl_url,id,current_sha256, - gr.Button.update(interactive=True)) + gr.Button.update(interactive=True), + gr.HTML.update() + ) model_select.change( fn=update_models_dropdown, @@ -448,7 +472,8 @@ def on_ui_tabs(): dl_url, model_id, current_sha256, - save_info + save_info, + list_html ] ) @@ -687,7 +712,8 @@ def on_ui_tabs(): preview_finish, overwrite_toggle, tile_count_slider, - skip_hash_toggle + skip_hash_toggle, + do_html_gen ] load_to_browser_inputs = [ @@ -927,7 +953,7 @@ def subfolder_list(folder, desc=None): if insert_sub_3: sub_folders.insert(3, f"{os.sep}Base model{os.sep}Author name{os.sep}Model name") if insert_sub_4: - sub_folders.insert(4, f"{os.sep}Base model{os.sep}Author name{os.sep}Model name{os.sep}Version name") + sub_folders.insert(4, f"{os.sep}Base model{os.sep}Author name{os.sep}Model name{os.sep}Model version") if insert_sub_5: sub_folders.insert(5, f"{os.sep}Base model{os.sep}Model name") if insert_sub_6: @@ -1108,6 +1134,16 @@ def on_ui_settings(): ).info("Uses the matching local HTML file when pressing CivitAI button on model cards in txt2img and img2img") ) + shared.opts.add_option( + "local_path_in_html", + shared.OptionInfo( + False, + "Use local images in the HTML", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Only works if all images of the corresponding model are downloaded") + ) + shared.opts.add_option( "page_header", shared.OptionInfo( @@ -1137,15 +1173,15 @@ def on_ui_settings(): **({'category_id': cat_id} if ver_bool else {}) ).info("Turns individual prompts from an example image into a button to send it to txt2img") ) - + shared.opts.add_option( - "update_log", + "model_desc_to_json", shared.OptionInfo( True, - 'Show console logs during update scanning', + 'Save model description to json', section=browser, **({'category_id': cat_id} if ver_bool else {}) - ).info('Shows the "is currently outdated" messages in the console when scanning models for available updates') + ).info('This saves the models description to the description field on model cards') ) shared.opts.add_option( @@ -1168,16 +1204,6 @@ def on_ui_settings(): ).info("Will append any content type and sub folders to the custom path.") ) - shared.opts.add_option( - "local_path_in_html", - shared.OptionInfo( - False, - "Use local images in the HTML", - section=browser, - **({'category_id': cat_id} if ver_bool else {}) - ).info("Only works if all images of the corresponding model are downloaded") - ) - shared.opts.add_option( "save_to_custom", shared.OptionInfo( @@ -1187,6 +1213,40 @@ def on_ui_settings(): **({'category_id': cat_id} if ver_bool else {}) ) ) + + shared.opts.add_option( + "custom_civitai_proxy", + shared.OptionInfo( + r"", + "Proxy address", + gr.Textbox, + {"placeholder": "socks4://0.0.0.0:00000 | socks5://0.0.0.0:00000"}, + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Only works with proxies that support HTTPS, Requires UI reload for Aria2 compatibility") + ) + + shared.opts.add_option( + "cabundle_path_proxy", + shared.OptionInfo( + r"", + "Path to custom CA Bundle", + gr.Textbox, + {"placeholder": "/path/to/custom/cabundle.pem"}, + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Specify custom CA bundle for SSL certificate checks if required") + ) + + shared.opts.add_option( + "disable_sll_proxy", + shared.OptionInfo( + False, + "Disable SSL certificate checks", + section=browser, + **({'category_id': cat_id} if ver_bool else {}) + ).info("Not recommended for security, may be required if you do not have the correct CA Bundle available") + ) id_and_sub_options = { "1" : f"{os.sep}Base model", diff --git a/style.css b/style.css index 3c7667b..c7269e1 100644 --- a/style.css +++ b/style.css @@ -130,7 +130,18 @@ justify-content: center; } -#toggle1L, #toggle2L, #toggle3L, #toggle4L, #toggle4L_api, #toggle5L, #overwrite_toggle, #skip_hash_toggle{ +#civitai_update_toggles > div { + display: flex; + flex-direction: column; +} + +#civitai_update_toggles { + margin-top: calc(-1 * var(--layout-gap)); + margin-bottom: var(--layout-gap); +} + +#toggle1L, #toggle2L, #toggle3L, #toggle4L, #toggle4L_api, #toggle5L, +#overwrite_toggle, #skip_hash_toggle, #do_html_gen { display: flex; justify-content: center; } @@ -325,7 +336,7 @@ .civitai-tag, .civitai-meta, .civitai-meta-btn { - background-color: var(--error-background-fill); + background-color: var(--neutral-800); border-radius: 8px; padding: 4px 6px; border: 1px solid var(--input-border-color); @@ -333,7 +344,7 @@ .civitai-meta-btn:hover { cursor: pointer; - background-color: var(--input-background-fill); + background-color: var(--neutral-700); } #select_all_models_container { @@ -591,10 +602,68 @@ } #civitai_preview_html .model-description { - border-top: 1px solid; - padding-bottom: 10px; - margin-bottom: 10px; - } + border-top: 1px solid; + overflow-wrap: break-word; + overflow: hidden; + position: relative; + max-height: 400px; +} + +#civitai_preview_html .model-description::after { + content: ""; + position: absolute; + bottom: 0; + width: 100%; + height: 75px; + background: linear-gradient(to bottom, rgb(255 255 255 / 0%), var(--background-fill-primary)); + z-index: 1; +} + +.description-toggle-label { + cursor: pointer; +} + +.description-toggle-checkbox { + position: absolute; + opacity: 0; + z-index: -1; +} + +.description-toggle-checkbox:checked + .model-description { + max-height: unset !important; + position: unset !important; +} + +.model-description + .description-toggle-label::before { + content: "❯"; + width: 1em; + height: 1em; + text-align: center; + transition: all 0.3s; +} + +.model-description + .description-toggle-label { + display: flex; + padding: 10px 0px 0px 0px; + font-weight: bold; + cursor: pointer; + font-size: large; +} + +.description-toggle-checkbox:checked + .model-description + .description-toggle-label::before { + transform: rotate(-90deg); + margin-right: 10px; +} + +.description-toggle-checkbox:checked + .model-description + .description-toggle-label::after { + content: "Show Less..."; +} + +.description-toggle-checkbox:not(:checked) + .model-description + .description-toggle-label::after { + content: "Show All..."; +} +/*------------------------------------------*/ +/*End CSS accordion for toggling description*/ /*Avatar CSS mostly copied from CivitAI, but 48px instead of 32px*/ #civitai_preview_html .avatar { diff --git a/style_html.css b/style_html.css index 92568e7..59a5af1 100644 --- a/style_html.css +++ b/style_html.css @@ -162,6 +162,65 @@ a:hover { border-style: none; } +.model-description { + border-top: 1px solid; + overflow-wrap: break-word; + overflow: hidden; + position: relative; + max-height: 400px; +} + +.model-description::after { + content: ""; + position: absolute; + bottom: 0; + width: 100%; + height: 75px; + background: linear-gradient(to bottom, rgb(255 255 255 / 0%), #0b0f19); + z-index: 1; +} + +.description-toggle-label { + display: flex; + padding: 10px 0px 0px 0px; + font-weight: bold; + cursor: pointer; + font-size: large; + color: white; +} + +.description-toggle-checkbox { + position: absolute; + opacity: 0; + z-index: -1; +} + +.description-toggle-checkbox:checked + .model-description { + max-height: none !important; + position: unset !important; +} + +.model-description + .description-toggle-label::before { + content: "❯"; + width: 1em; + height: 1em; + text-align: center; + transition: all 0.3s; +} + +.description-toggle-checkbox:checked + .model-description + .description-toggle-label::before { + transform: rotate(-90deg); + margin-right: 10px; +} + +.description-toggle-checkbox:checked + .model-description + .description-toggle-label::after { + content: "Show Less..."; +} + +.description-toggle-checkbox:not(:checked) + .model-description + .description-toggle-label::after { + content: "Show All..."; +} + /*CSS accordion for toggling extra metadata*/ /*-----------------------------------------*/ .accordionCheckbox {