Update to v3.3.0

pull/196/head v3.3.0
BlafKing 2024-02-06 13:51:31 +01:00
parent 9e70db57c5
commit d4616e5c1d
8 changed files with 742 additions and 463 deletions

View File

@ -100,6 +100,22 @@ https://github.com/BlafKing/sd-civitai-browser-plus/assets/9644716/44c5c7a0-4854
# Changelog 📋
<h3>v3.3.0</h3>
* Feature: New txt2img and img2img model info overlay on CivitAI button press.
* Feature: Base Model as sub folder option.
* Feature: Ability to multi-download to selected folder.
* Feature: Use the same folder as older versions when updating using multi-download.
* Feature: txt2img and img2img CivitAI buttons can use local HTML file, toggle in settings.
* New setting: Save API info of model when saving model info.
* New setting: Automatically save all images after download.
* New setting: Use local HTML file for model info.
* Bug fix: better JSON decode, now forces UTF-8
* Bug fix: Now uses the proper default file when using multi-download
* Bug fix: Hide early access models fix, now works when published_at does not exist in API.
* Bug fix: Fix attempt for queue clearing upon download fail.
---
<h3>v3.2.5</h3>
* Bug fix: Removed default API Key since it gets blocked after many downloads.

View File

@ -432,7 +432,7 @@ function createCardButtons(event) {
newDiv.classList.add('goto-civitbrowser', 'card-button');
newDiv.addEventListener('click', function (event) {
event.stopPropagation();
sendModelToBrowser(modelName, content_type);
modelInfoPopUp(modelName, content_type);
});
const svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
@ -468,53 +468,125 @@ function createCardButtons(event) {
document.addEventListener('click', createCardButtons);
// Sends the selected model list to a python function
function sendModelToBrowser(modelName, content_type) {
const tabNav = document.querySelector('.tab-nav');
const buttons = tabNav.querySelectorAll('button');
for (const button of buttons) {
if (button.textContent.includes('Browser+')) {
button.click();
const firstButton = document.querySelector('#tab_civitai_interface > div > div > div > button');
if (firstButton) {
firstButton.click();
}
}
}
function modelInfoPopUp(modelName, content_type) {
select_model(modelName, null, true, content_type);
// Create the overlay
var overlay = document.createElement('div');
overlay.classList.add('civitaiOverlayGlobal');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = 'rgba(20, 20, 20, 0.95)';
overlay.style.zIndex = '1001';
overlay.style.overflowY = 'auto';
// Create the pop-up window
var popup = document.createElement('div');
popup.classList.add('civitaiOverlay');
popup.style.display = 'flex';
popup.style.justifyContent = 'center';
popup.style.position = 'absolute';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.width = '56em';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.background = 'var(--body-background-fill)';
popup.style.padding = '2em';
popup.style.borderRadius = 'var(--block-radius)';
popup.style.borderStyle = 'solid';
popup.style.borderWidth = 'var(--block-border-width)';
popup.style.borderColor = 'var(--block-border-color)';
popup.style.zIndex = '1001';
// Add content to the popup
var popupContent = document.createElement('div'); // Change from <p> to <div>
popupContent.classList.add('civitaiLoadingText');
popupContent.textContent = 'Loading model info, please wait!';
popupContent.style.fontSize = '24px';
popupContent.style.color = 'white';
popupContent.style.fontFamily = 'var(--font)';
popup.appendChild(popupContent);
// Create the close button
var closeButton = document.createElement('div');
closeButton.textContent = '×';
closeButton.style.position = 'fixed';
closeButton.style.right = '0.25em';
closeButton.style.top = '0';
closeButton.style.cursor = 'pointer';
closeButton.style.color = 'white';
closeButton.style.fontSize = '32pt';
closeButton.addEventListener('click', hidePopup);
document.addEventListener('keydown', handleKeyPress);
// Append the close button to the overlay
overlay.appendChild(closeButton);
// Append the popup to the overlay
overlay.appendChild(popup);
// Append the overlay to the body
document.body.style.overflow = 'hidden'; // Prevent scrolling on the main page
document.body.appendChild(overlay);
overlay.addEventListener('click', function (event) {
if (event.target === overlay) {
hidePopup();
}
});
}
// Function to hide the popup
function hidePopup() {
var overlay = document.querySelector('.civitaiOverlayGlobal');
if (overlay) {
document.body.removeChild(overlay);
document.body.style.overflow = 'auto';
}
}
// Function to handle key presses
function handleKeyPress(event) {
if (event.key === 'Escape') {
hidePopup();
}
}
// Creates a list of the selected models
var selectedModels = [];
function multi_model_select(modelName, isChecked) {
var selectedTypes = [];
function multi_model_select(modelName, modelType, isChecked) {
if (arguments.length === 0) {
selectedModels = [];
selectedTypes = [];
return;
}
if (isChecked) {
if (!selectedModels.includes(modelName)) {
selectedModels.push(modelName);
}
selectedTypes.push(modelType)
} else {
var index = selectedModels.indexOf(modelName);
if (index > -1) {
selectedModels.splice(index, 1);
var modelIndex = selectedModels.indexOf(modelName);
if (modelIndex > -1) {
selectedModels.splice(modelIndex, 1);
}
var typesIndex = selectedTypes.indexOf(modelType);
if (typesIndex > -1) {
selectedTypes.splice(typesIndex, 1);
}
}
const output = gradioApp().querySelector('#selected_list textarea');
output.value = JSON.stringify(selectedModels);
updateInput(output);
}
const selected_model_list = gradioApp().querySelector('#selected_model_list textarea');
selected_model_list.value = JSON.stringify(selectedModels);
// Clicks the first item in the browser cards list
function clickFirstFigureInColumn() {
const columnDiv = document.querySelector('.column.civmodellist');
if (columnDiv) {
const firstFigure = columnDiv.querySelector('figure');
if (firstFigure) {
firstFigure.click();
}
}
const selected_type_list = gradioApp().querySelector('#selected_type_list textarea');
selected_type_list.value = JSON.stringify(selectedTypes);
updateInput(selected_model_list);
updateInput(selected_type_list);
}
// Metadata button click detector
@ -533,6 +605,37 @@ document.addEventListener('click', function(event) {
}
}, true);
function inputHTMLPreviewContent(html_input) {
var overlay = document.querySelector('.civitaiOverlay')
let startIndex = html_input.indexOf("'value': '");
if (startIndex !== -1) {
startIndex += "'value': '".length;
const endIndex = html_input.indexOf("', 'type': None,", startIndex);
if (endIndex !== -1) {
let extractedText = html_input.substring(startIndex, endIndex);
var modelIdNotFound = extractedText.includes(">Model ID not found.<br>Maybe");
extractedText = extractedText.replace(/\\n\s*</g, '<');
extractedText = extractedText.replace(/\\n/g, ' ');
extractedText = extractedText.replace(/\\t/g, '');
var loadingText = document.querySelector('.civitaiLoadingText');
var modelInfo = document.createElement('div');
loadingText.parentNode.removeChild(loadingText);
if (!modelIdNotFound) {
overlay.style.top = 0;
overlay.style.transform = 'translate(-50%, 0)';
extractedText = extractedText.replace(/\\'/g, "");
} else {
extractedText = extractedText.replace(/\\'/g, "'");
}
modelInfo.innerHTML = extractedText;
overlay.appendChild(modelInfo);
}
}
}
// CivitAI Link Button Creation
function onEditButtonCardClick(nameValue) {
var checkInterval = setInterval(function() {
@ -575,25 +678,24 @@ function onEditButtonCardClick(nameValue) {
}, 100);
}
function sendClick(location) {
const clickEvent = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
location.dispatchEvent(clickEvent);
}
// Selects all models
function selectAllModels() {
const checkboxes = Array.from(document.querySelectorAll('.model-checkbox'));
const allChecked = checkboxes.every(checkbox => checkbox.checked);
const allUnchecked = checkboxes.every(checkbox => !checkbox.checked);
if (allChecked || allUnchecked) {
checkboxes.forEach(clickCheckbox);
checkboxes.forEach(sendClick);
} else {
checkboxes.filter(checkbox => !checkbox.checked).forEach(clickCheckbox);
}
function clickCheckbox(checkbox) {
const clickEvent = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
checkbox.dispatchEvent(clickEvent);
checkboxes.filter(checkbox => !checkbox.checked).forEach(sendClick);
}
}
@ -601,25 +703,20 @@ function selectAllModels() {
function deselectAllModels() {
setTimeout(() => {
const checkboxes = Array.from(document.querySelectorAll('.model-checkbox'));
checkboxes.filter(checkbox => checkbox.checked).forEach(uncheckCheckbox);
function uncheckCheckbox(checkbox) {
const clickEvent = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true
});
checkbox.dispatchEvent(clickEvent);
}
checkboxes.filter(checkbox => checkbox.checked).forEach(sendClick);
}, 1000);
}
// Sends Image URL to Python to pull generation info
function sendImgUrl(image_url) {
const randomNumber = Math.floor(Math.random() * 1000);
const genButton = gradioApp().querySelector('#txt2img_extra_tabs > div > button')
const paddedNumber = String(randomNumber).padStart(3, '0');
const input = gradioApp().querySelector('#civitai_text2img_input textarea');
input.value = paddedNumber + "." + image_url;
updateInput(input);
hidePopup();
sendClick(genButton);
}
// Sends txt2img info to txt2img tab

View File

@ -228,9 +228,11 @@ def model_list_html(json_data):
if hide_early_access:
early_access_days = version['earlyAccessTimeFrame']
if early_access_days != 0:
published_at = datetime.datetime.strptime(version['publishedAt'], "%Y-%m-%dT%H:%M:%S.%fZ")
adjusted_date = published_at + datetime.timedelta(days=early_access_days)
if not current_time > adjusted_date:
published_at_str = version.get('publishedAt')
if published_at_str is not None:
published_at = datetime.datetime.strptime(version['publishedAt'], "%Y-%m-%dT%H:%M:%S.%fZ")
adjusted_date = published_at + datetime.timedelta(days=early_access_days)
if not current_time > adjusted_date or not published_at_str:
continue
versions_to_keep.append(version)
@ -256,7 +258,7 @@ def model_list_html(json_data):
existing_files.add(file)
if file.endswith('.json'):
json_path = os.path.join(root, file)
with open(json_path, 'r') as f:
with open(json_path, 'r', encoding="utf-8") as f:
try:
json_file = json.load(f)
if isinstance(json_file, dict):
@ -322,7 +324,7 @@ def model_list_html(json_data):
model_string = escape(f"{model_name_js} ({model_id})")
model_card = f'<figure class="civmodelcard {nsfw} {installstatus}" base-model="{baseModel}" date="{date}" onclick="select_model(\'{model_string}\', event)">'
if installstatus != "civmodelcardinstalled":
model_card += f'<input type="checkbox" class="model-checkbox" id="checkbox-{model_string}" onchange="multi_model_select(\'{model_string}\', this.checked)" style="opacity: 0; position: absolute; top: 10px; right: 10px;">' \
model_card += f'<input type="checkbox" class="model-checkbox" id="checkbox-{model_string}" onchange="multi_model_select(\'{model_string}\', \'{item["type"]}\', this.checked)" style="opacity: 0; position: absolute; top: 10px; right: 10px;">' \
+ f'<label for="checkbox-{model_string}" class="custom-checkbox"></label>'
if len(item["name"]) > 40:
display_name = item["name"][:40] + '...'
@ -553,62 +555,61 @@ def update_model_list(content_type=None, sort_type=None, period_type=None, use_s
gr.Textbox.update(value=None) # Model Filename
)
def update_model_versions(model_id):
item_id_and_types = {item['id']: (item['type'], item['description']) for item in gl.json_data['items']}
if model_id is not None:
selected_content_type, desc = item_id_and_types.get(model_id, (None, None))
if selected_content_type is None:
return
versions_dict = defaultdict(list)
installed_versions = set()
model_folder = os.path.join(contenttype_folder(selected_content_type, desc))
gl.main_folder = model_folder
item = next((item for item in gl.json_data['items'] if item['id'] == model_id), None)
if item is None:
return
versions = item['modelVersions']
version_files = set()
for version in versions:
versions_dict[version['name']].append(item["name"])
for version_file in version['files']:
file_sha256 = version_file.get('hashes', {}).get('SHA256', "").upper()
version_filename = version_file['name']
version_files.add((version['name'], version_filename, file_sha256))
for root, _, files in os.walk(model_folder):
for file in files:
if file.endswith('.json'):
try:
json_path = os.path.join(root, file)
with open(json_path, 'r') as f:
json_data = json.load(f)
if isinstance(json_data, dict):
if 'sha256' in json_data and json_data['sha256']:
sha256 = json_data.get('sha256', "").upper()
for version_name, _, file_sha256 in version_files:
if sha256 == file_sha256:
installed_versions.add(version_name)
break
except Exception as e:
print(f"failed to read: \"{file}\": {e}")
for version_name, version_filename, _ in version_files:
if file == version_filename:
installed_versions.add(version_name)
break
version_names = list(versions_dict.keys())
display_version_names = [f"{v} [Installed]" if v in installed_versions else v for v in version_names]
default_installed = next((f"{v} [Installed]" for v in installed_versions), None)
default_value = default_installed or next(iter(version_names), None)
return gr.Dropdown.update(choices=display_version_names, value=default_value, interactive=True) # Version List
def update_model_versions(model_id, json_input=None):
if json_input:
api_json = json_input
else:
return gr.Dropdown.update(choices=[], value=None, interactive=False) # Version List
api_json = gl.json_data
for item in api_json['items']:
if int(item['id']) == int(model_id):
content_type = item['type']
desc = item.get('description', "None")
versions_dict = defaultdict(list)
installed_versions = set()
model_folder = os.path.join(contenttype_folder(content_type, desc))
gl.main_folder = model_folder
versions = item['modelVersions']
version_files = set()
for version in versions:
versions_dict[version['name']].append(item["name"])
for version_file in version['files']:
file_sha256 = version_file.get('hashes', {}).get('SHA256', "").upper()
version_filename = version_file['name']
version_files.add((version['name'], version_filename, file_sha256))
for root, _, files in os.walk(model_folder):
for file in files:
if file.endswith('.json'):
try:
json_path = os.path.join(root, file)
with open(json_path, 'r', encoding="utf-8") as f:
json_data = json.load(f)
if isinstance(json_data, dict):
if 'sha256' in json_data and json_data['sha256']:
sha256 = json_data.get('sha256', "").upper()
for version_name, _, file_sha256 in version_files:
if sha256 == file_sha256:
installed_versions.add(version_name)
break
except Exception as e:
print(f"failed to read: \"{file}\": {e}")
for version_name, version_filename, _ in version_files:
if file == version_filename:
installed_versions.add(version_name)
break
version_names = list(versions_dict.keys())
display_version_names = [f"{v} [Installed]" if v in installed_versions else v for v in version_names]
default_installed = next((f"{v} [Installed]" for v in installed_versions), None)
default_value = default_installed or next(iter(version_names), None)
return gr.Dropdown.update(choices=display_version_names, value=default_value, interactive=True) # Version List
return gr.Dropdown.update(choices=[], value=None, interactive=False) # Version List
def cleaned_name(file_name):
if platform.system() == "Windows":
@ -638,22 +639,29 @@ def extract_model_info(input_string):
return name, int(id_number)
def update_model_info(model_string=None, model_version=None):
def update_model_info(model_string=None, model_version=None, only_html=False, input_id=None, json_input=None, from_preview=False):
video_playback = getattr(opts, "video_playback", True)
playback = ""
if video_playback: playback = "autoplay loop"
if json_input:
api_data = json_input
else:
api_data = gl.json_data
BtnDownInt = True
BtnDel = False
BtnImage = False
model_id = None
model_name = None
model_name, model_id = extract_model_info(model_string)
if not input_id:
_, model_id = extract_model_info(model_string)
else:
model_id = input_id
if model_version and "[Installed]" in model_version:
model_version = model_version.replace(" [Installed]", "")
if model_id and model_version:
if model_id:
output_html = ""
output_training = ""
output_basemodel = ""
@ -664,12 +672,13 @@ def update_model_info(model_string=None, model_version=None):
default_file = None
model_filename = None
sha256_value = None
for item in gl.json_data['items']:
if item['id'] == model_id:
for item in api_data['items']:
if int(item['id']) == int(model_id):
content_type = item['type']
if content_type == "LORA":
is_LORA = True
desc = item['description']
model_name = item['name']
model_folder = os.path.join(contenttype_folder(content_type, desc))
model_uploader = item['creator']['username']
uploader_avatar = item['creator']['image']
@ -679,173 +688,182 @@ def update_model_info(model_string=None, model_version=None):
uploader_avatar = f'<div class="avatar"><img src={uploader_avatar}></div>'
tags = item.get('tags', "")
model_desc = item.get('description', "")
for model in item['modelVersions']:
if model['name'] == model_version:
if model['trainedWords']:
output_training = ",".join(model['trainedWords'])
output_training = re.sub(r'<[^>]*:[^>]*>', '', output_training)
output_training = re.sub(r', ?', ', ', output_training)
output_training = output_training.strip(', ')
if model['baseModel']:
output_basemodel = model['baseModel']
for file in model['files']:
dl_dict[file['name']] = file['downloadUrl']
if not model_filename:
model_filename = file['name']
dl_url = file['downloadUrl']
gl.json_info = item
sha256_value = file['hashes'].get('SHA256', 'Unknown')
size = file['metadata'].get('size', 'Unknown')
format = file['metadata'].get('format', 'Unknown')
fp = file['metadata'].get('fp', 'Unknown')
sizeKB = file.get('sizeKB', 0) * 1024
filesize = _download.convert_size(sizeKB)
unique_file_name = f"{size} {format} {fp} ({filesize})"
is_primary = file.get('primary', False)
file_list.append(unique_file_name)
if is_primary:
default_file = unique_file_name
model_filename = file['name']
dl_url = file['downloadUrl']
gl.json_info = item
sha256_value = file['hashes'].get('SHA256', 'Unknown')
if model_version is None:
selected_version = item['modelVersions'][0]
else:
for model in item['modelVersions']:
if model['name'] == model_version:
selected_version = model
break
if selected_version['trainedWords']:
output_training = ",".join(selected_version['trainedWords'])
output_training = re.sub(r'<[^>]*:[^>]*>', '', output_training)
output_training = re.sub(r', ?', ', ', output_training)
output_training = output_training.strip(', ')
if selected_version['baseModel']:
output_basemodel = selected_version['baseModel']
for file in selected_version['files']:
dl_dict[file['name']] = file['downloadUrl']
if not model_filename:
model_filename = file['name']
dl_url = file['downloadUrl']
gl.json_info = item
sha256_value = file['hashes'].get('SHA256', 'Unknown')
if is_LORA and file_list:
extracted_formats = [file.split(' ')[1] for file in file_list]
size = file['metadata'].get('size', 'Unknown')
format = file['metadata'].get('format', 'Unknown')
fp = file['metadata'].get('fp', 'Unknown')
sizeKB = file.get('sizeKB', 0) * 1024
filesize = _download.convert_size(sizeKB)
unique_file_name = f"{size} {format} {fp} ({filesize})"
is_primary = file.get('primary', False)
file_list.append(unique_file_name)
if is_primary:
default_file = unique_file_name
model_filename = file['name']
dl_url = file['downloadUrl']
gl.json_info = item
sha256_value = file['hashes'].get('SHA256', 'Unknown')
if is_LORA and file_list:
extracted_formats = [file.split(' ')[1] for file in file_list]
if "SafeTensor" in extracted_formats and "PickleTensor" in extracted_formats:
if "PickleTensor" in file_list[0].split(' ')[1]:
if float(file_list[0].split(' ')[0]) <= 100:
model_folder = os.path.join(contenttype_folder("TextualInversion"))
if "SafeTensor" in extracted_formats and "PickleTensor" in extracted_formats:
if "PickleTensor" in file_list[0].split(' ')[1]:
if float(file_list[0].split(' ')[0]) <= 100:
model_folder = os.path.join(contenttype_folder("TextualInversion"))
model_url = selected_version['downloadUrl']
model_main_url = f"https://civitai.com/models/{item['id']}"
img_html = '<div class="sampleimgs"><input type="radio" name="zoomRadio" id="resetZoom" class="zoom-radio" checked>'
for index, pic in enumerate(selected_version['images']):
meta_button = False
meta = pic['meta']
if meta and meta.get('prompt'):
meta_button = True
BtnImage = True
# 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=")
nsfw = 'class="model-block"'
if pic['nsfw'] not in ["None", "Soft"]:
nsfw = 'class="civnsfw model-block"'
img_html += f'''
<div {nsfw} style="display:flex;align-items:flex-start;">
<div class="civitai-image-container">
<input type="radio" name="zoomRadio" id="zoomRadio{index}" class="zoom-radio">
<label for="zoomRadio{index}" class="zoom-img-container">
'''
# Check if the pic is an image or video
if pic['type'] == "video":
img_html += f'<video data-sampleimg="true" {playback} muted playsinline><source src="{image_url}" type="video/mp4"></video>'
meta_button = False
else:
img_html += f'<img data-sampleimg="true" src="{image_url}">'
img_html += '''
</label>
<label for="resetZoom" class="zoom-overlay"></label>
'''
if meta_button:
img_html += f'''
<div class="civitai_txt2img" style="margin-top:30px;margin-bottom:30px;">
<label onclick='sendImgUrl("{escape(image_url)}")' class="civitai-txt2img-btn" style="max-width:fit-content;cursor:pointer;">Send to txt2img</label>
</div></div>
'''
else:
img_html += '</div>'
model_url = model['downloadUrl']
model_main_url = f"https://civitai.com/models/{item['id']}"
img_html = '<div class="sampleimgs"><input type="radio" name="zoomRadio" id="resetZoom" class="zoom-radio" checked>'
for index, pic in enumerate(model['images']):
meta_button = False
meta = pic['meta']
if meta and meta.get('prompt'):
meta_button = True
BtnImage = True
# 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=")
nsfw = 'class="model-block"'
if pic['nsfw'] not in ["None", "Soft"]:
nsfw = 'class="civnsfw model-block"'
if meta:
img_html += '<div style="margin:1em 0em 1em 1em;text-align:left;line-height:1.5em;" id="image_info"><dl>'
# Define the preferred order of keys and convert them to lowercase
preferred_order = ["prompt", "negativePrompt", "seed", "Size", "Model", "clipSkip", "sampler", "steps", "cfgScale"]
preferred_order_lower = [key.lower() for key in preferred_order]
# Loop through the keys in the preferred order and add them to the HTML
for key in preferred_order:
if key in meta:
value = meta[key]
img_html += f'<dt>{escape(str(key).capitalize())}</dt><dd>{escape(str(value))}</dd>'
# Check if there are remaining keys in meta
remaining_keys = [key for key in meta if key.lower() not in preferred_order_lower]
img_html += f'''
<div {nsfw} style="display:flex;align-items:flex-start;">
<div class="civitai-image-container">
<input type="radio" name="zoomRadio" id="zoomRadio{index}" class="zoom-radio">
<label for="zoomRadio{index}" class="zoom-img-container">
'''
# Check if the pic is an image or video
if pic['type'] == "video":
img_html += f'<video data-sampleimg="true" {playback} muted playsinline><source src="{image_url}" type="video/mp4"></video>'
meta_button = False
else:
img_html += f'<img data-sampleimg="true" src="{image_url}">'
# Add the rest
if remaining_keys:
img_html += f"""
<div class="tabs">
<div class="tab">
<input type="checkbox" class="accordionCheckbox" id="chck{index}">
<label class="tab-label" for="chck{index}">More details...</label>
<div class="tab-content">
"""
for key in remaining_keys:
value = meta[key]
img_html += f'<dt>{escape(str(key).capitalize())}</dt><dd>{escape(str(value))}</dd>'
img_html = img_html + '</div></div></div>'
img_html += '''
</label>
<label for="resetZoom" class="zoom-overlay"></label>
'''
if meta_button:
img_html += f'''
<div class="civitai_txt2img" style="margin-top:30px;margin-bottom:30px;">
<label onclick='sendImgUrl("{escape(image_url)}")' class="civitai-txt2img-btn" style="max-width:fit-content;cursor:pointer;">Send to txt2img</label>
</div></div>
'''
else:
img_html += '</div>'
if meta:
img_html += '<div style="margin:1em 0em 1em 1em;text-align:left;line-height:1.5em;" id="image_info"><dl>'
# Define the preferred order of keys and convert them to lowercase
preferred_order = ["prompt", "negativePrompt", "seed", "Size", "Model", "clipSkip", "sampler", "steps", "cfgScale"]
preferred_order_lower = [key.lower() for key in preferred_order]
# Loop through the keys in the preferred order and add them to the HTML
for key in preferred_order:
if key in meta:
value = meta[key]
img_html += f'<dt>{escape(str(key).capitalize())}</dt><dd>{escape(str(value))}</dd>'
# Check if there are remaining keys in meta
remaining_keys = [key for key in meta if key.lower() not in preferred_order_lower]
img_html += '</dl></div>'
# Add the rest
if remaining_keys:
img_html += f"""
<div class="tabs">
<div class="tab">
<input type="checkbox" class="accordionCheckbox" id="chck{index}">
<label class="tab-label" for="chck{index}">More details...</label>
<div class="tab-content">
"""
for key in remaining_keys:
value = meta[key]
img_html += f'<dt>{escape(str(key).capitalize())}</dt><dd>{escape(str(value))}</dd>'
img_html = img_html + '</div></div></div>'
img_html += '</dl></div>'
img_html = img_html + '</div>'
img_html = img_html + '</div>'
tags_html = ''.join([f'<span class="civitai-tag">{escape(str(tag))}</span>' for tag in tags])
def perms_svg(color):
return f'<span style="display:inline-block;vertical-align:middle;">'\
f'<svg width="15" height="15" viewBox="0 1.5 24 24" stroke-width="4" stroke-linecap="round" stroke="{color}">'
allow_svg = f'{perms_svg("lime")}<path d="M5 12l5 5l10 -10"></path></svg></span>'
deny_svg = f'{perms_svg("red")}<path d="M18 6l-12 12"></path><path d="M6 6l12 12"></path></svg></span>'
perms_html= '<p style="line-height: 2; font-weight: bold;">'\
f'{allow_svg if item.get("allowNoCredit") else deny_svg} Use the model without crediting the creator<br/>'\
f'{allow_svg if item.get("allowCommercialUse") in ["Image", "Rent", "RentCivit", "Sell"] else deny_svg} Sell images they generate<br/>'\
f'{allow_svg if item.get("allowCommercialUse") in ["Rent", "Sell"] else deny_svg} Run on services that generate images for money<br/>'\
f'{allow_svg if item.get("allowCommercialUse") in ["RentCivit", "Rent", "Sell"] else deny_svg} Run on Civitai<br/>'\
f'{allow_svg if item.get("allowDerivatives") else deny_svg} Share merges using this model<br/>'\
f'{allow_svg if item.get("allowCommercialUse") == "Sell" else deny_svg} Sell this model or merges using this model<br/>'\
f'{allow_svg if item.get("allowDifferentLicense") else deny_svg} Have different permissions when sharing merges'\
'</p>'
output_html = f'''
<div class="model-block">
<h2><a href={model_main_url} target="_blank" id="model_header">{escape(str(model_name))}</a></h2>
<h3 class="model-uploader">Uploaded by <a href="https://civitai.com/user/{escape(str(model_uploader))}" target="_blank">{escape(str(model_uploader))}</a>{uploader_avatar}</h3>
<div class="civitai-version-info" style="display:flex; flex-wrap:wrap; justify-content:space-between;">
<dl id="info_block">
<dt>Version</dt>
<dd>{escape(str(model_version))}</dd>
<dt>Base Model</dt>
<dd>{escape(str(output_basemodel))}</dd>
<dt>CivitAI Tags</dt>
<dd>
<div class="civitai-tags-container">
{tags_html}
</div>
</dd>
<dt>Download Link</dt>
<dd><a href={model_url} target="_blank">{model_url}</a></dd>
</dl>
<div style="align-self:center; min-width:320px;">
<div>
{perms_html}
</div>
img_html = img_html + '</div>'
img_html = img_html + '</div>'
tags_html = ''.join([f'<span class="civitai-tag">{escape(str(tag))}</span>' for tag in tags])
def perms_svg(color):
return f'<span style="display:inline-block;vertical-align:middle;">'\
f'<svg width="15" height="15" viewBox="0 1.5 24 24" stroke-width="4" stroke-linecap="round" stroke="{color}">'
allow_svg = f'{perms_svg("lime")}<path d="M5 12l5 5l10 -10"></path></svg></span>'
deny_svg = f'{perms_svg("red")}<path d="M18 6l-12 12"></path><path d="M6 6l12 12"></path></svg></span>'
perms_html= '<p style="line-height: 2; font-weight: bold;">'\
f'{allow_svg if item.get("allowNoCredit") else deny_svg} Use the model without crediting the creator<br/>'\
f'{allow_svg if item.get("allowCommercialUse") in ["Image", "Rent", "RentCivit", "Sell"] else deny_svg} Sell images they generate<br/>'\
f'{allow_svg if item.get("allowCommercialUse") in ["Rent", "Sell"] else deny_svg} Run on services that generate images for money<br/>'\
f'{allow_svg if item.get("allowCommercialUse") in ["RentCivit", "Rent", "Sell"] else deny_svg} Run on Civitai<br/>'\
f'{allow_svg if item.get("allowDerivatives") else deny_svg} Share merges using this model<br/>'\
f'{allow_svg if item.get("allowCommercialUse") == "Sell" else deny_svg} Sell this model or merges using this model<br/>'\
f'{allow_svg if item.get("allowDifferentLicense") else deny_svg} Have different permissions when sharing merges'\
'</p>'
output_html = f'''
<div class="model-block">
<h2><a href={model_main_url} target="_blank" id="model_header">{escape(str(model_name))}</a></h2>
<h3 class="model-uploader">Uploaded by <a href="https://civitai.com/user/{escape(str(model_uploader))}" target="_blank">{escape(str(model_uploader))}</a>{uploader_avatar}</h3>
<div class="civitai-version-info" style="display:flex; flex-wrap:wrap; justify-content:space-between;">
<dl id="info_block">
<dt>Version</dt>
<dd>{escape(str(model_version))}</dd>
<dt>Base Model</dt>
<dd>{escape(str(output_basemodel))}</dd>
<dt>CivitAI Tags</dt>
<dd>
<div class="civitai-tags-container">
{tags_html}
</div>
</div>
<div class="model-description">
<h2>Description</h2>
{model_desc}
</dd>
<dt>Download Link</dt>
<dd><a href={model_url} target="_blank">{model_url}</a></dd>
</dl>
<div style="align-self:center; min-width:320px;">
<div>
{perms_html}
</div>
</div>
<div align=center>{img_html}</div>
'''
</div>
<div class="model-description">
<h2>Description</h2>
{model_desc}
</div>
</div>
<div align=center>{img_html}</div>
'''
if only_html:
return output_html
folder_location = "None"
default_subfolder = "None"
sub_folders = ["None"]
@ -854,7 +872,7 @@ def update_model_info(model_string=None, model_version=None):
for filename in files:
if filename.endswith('.json'):
json_file_path = os.path.join(root, filename)
with open(json_file_path, 'r') as f:
with open(json_file_path, 'r', encoding="utf-8") as f:
try:
data = json.load(f)
sha256 = data.get('sha256')
@ -896,13 +914,15 @@ def update_model_info(model_string=None, model_version=None):
sub_folders.remove("None")
sub_folders = sorted(sub_folders, key=lambda x: (x.lower(), x))
sub_folders.insert(0, "None")
sub_opt1 = os.path.join(os.sep, cleaned_name(model_uploader))
sub_opt2 = os.path.join(os.sep, cleaned_name(model_name))
sub_opt3 = os.path.join(os.sep, cleaned_name(model_name), cleaned_name(model_version))
sub_opt1 = os.path.join(os.sep, cleaned_name(output_basemodel))
sub_opt2 = os.path.join(os.sep, cleaned_name(model_uploader))
sub_opt3 = os.path.join(os.sep, cleaned_name(model_name))
sub_opt4 = os.path.join(os.sep, cleaned_name(model_name), cleaned_name(model_version))
if insert_sub:
sub_folders.insert(1, sub_opt1)
sub_folders.insert(2, sub_opt2)
sub_folders.insert(3, sub_opt3)
sub_folders.insert(4, sub_opt4)
list = set()
sub_folders = [x for x in sub_folders if not (x in list or list.add(x))]
@ -910,12 +930,14 @@ def update_model_info(model_string=None, model_version=None):
sub_folders = ["None"]
default_sub = sub_folder_value(content_type, desc)
if default_sub == f"{os.sep}Author Name":
if default_sub == f"{os.sep}Base Model":
default_sub = sub_opt1
elif default_sub == f"{os.sep}Model Name":
elif default_sub == f"{os.sep}Author Name":
default_sub = sub_opt2
elif default_sub == f"{os.sep}Model Name{os.sep}Version Name":
elif default_sub == f"{os.sep}Model Name":
default_sub = sub_opt3
elif default_sub == f"{os.sep}Model Name{os.sep}Version Name":
default_sub = sub_opt4
if folder_location == "None":
folder_location = model_folder
@ -929,7 +951,7 @@ def update_model_info(model_string=None, model_version=None):
default_subfolder = f'{os.sep}{relative_path}' if relative_path != "." else default_sub if BtnDel == False else "None"
if gl.isDownloading:
item = gl.download_queue[0]
if model_id == item['model_id']:
if int(model_id) == int(item['model_id']):
BtnDel = False
BtnDownTxt = "Download model"
if len(gl.download_queue) > 0:
@ -998,7 +1020,7 @@ def update_file_info(model_string, model_version, file_metadata):
model_version = model_version.replace(" [Installed]", "")
if model_id and model_version:
for item in gl.json_data['items']:
if item['id'] == model_id:
if int(item['id']) == int(model_id):
content_type = item['type']
if content_type == "LORA":
is_LORA = True
@ -1048,7 +1070,7 @@ def update_file_info(model_string, model_version, file_metadata):
for root, _, files in os.walk(model_folder):
for filename in files:
if filename.endswith('.json'):
with open(os.path.join(root, filename), 'r') as f:
with open(os.path.join(root, filename), 'r', encoding="utf-8") as f:
try:
data = json.load(f)
sha256_value = data.get('sha256')

View File

@ -11,7 +11,6 @@ import stat
import json
import time
from pathlib import Path
from urllib.parse import urlsplit
from modules.shared import opts, cmd_opts
from scripts.civitai_global import print
import scripts.civitai_global as gl
@ -62,7 +61,7 @@ def start_aria2_rpc():
os.rename(start_file, running_file)
return
else:
with open(start_file, 'w'):
with open(start_file, 'w', encoding="utf-8"):
pass
try:
@ -98,7 +97,7 @@ elif os_type == 'Linux':
class TimeOutFunction(Exception):
pass
def create_model_item(dl_url, model_filename, install_path, model_name, version_name, model_sha256, model_id, create_json):
def create_model_item(dl_url, model_filename, install_path, model_name, version_name, model_sha256, model_id, create_json, from_batch=False):
if model_id:
model_id = int(model_id)
if model_sha256:
@ -107,13 +106,13 @@ def create_model_item(dl_url, model_filename, install_path, model_name, version_
filtered_items = []
for item in gl.json_data['items']:
if item['id'] == model_id:
if int(item['id']) == int(model_id):
filtered_items.append(item)
break
model_json = {"items": filtered_items}
model_versions = _api.update_model_versions(model_id)
(preview_html,_,_,_,_,_,_,_,_,_,_,existing_path,_) = _api.update_model_info(None, model_versions.get('value'), False, model_id)
for item in gl.download_queue:
if item['dl_url'] == dl_url:
@ -129,12 +128,15 @@ def create_model_item(dl_url, model_filename, install_path, model_name, version_
"model_id" : model_id,
"create_json" : create_json,
"model_json" : model_json,
"model_versions" : model_versions
"model_versions" : model_versions,
"preview_html" : preview_html['value'],
"existing_path": existing_path['value'],
"from_batch" : from_batch
}
return item
def selected_to_queue(model_list, download_start, create_json):
def selected_to_queue(model_list, subfolder, download_start, create_json):
global total_count, current_count
if gl.download_queue:
number = download_start
@ -144,18 +146,24 @@ def selected_to_queue(model_list, download_start, create_json):
current_count = 0
model_list = json.loads(model_list)
for model_string in model_list:
model_name, model_id = _api.extract_model_info(model_string)
for item in gl.json_data['items']:
if item['id'] == model_id:
if int(item['id']) == int(model_id):
model_id, desc, content_type = item['id'], item['description'], item['type']
version = item.get('modelVersions', [])[0]
version_name = version.get('name')
files = version.get('files', [])
model_filename = _api.cleaned_name(files[0].get('name'))
model_sha256 = files[0].get('hashes', {}).get('SHA256')
dl_url = files[0].get('downloadUrl')
primary_file = next((file for file in files if file.get('primary', False)), None)
if primary_file:
model_filename = _api.cleaned_name(primary_file.get('name'))
model_sha256 = primary_file.get('hashes', {}).get('SHA256')
dl_url = primary_file.get('downloadUrl')
else:
model_filename = _api.cleaned_name(files[0].get('name'))
model_sha256 = files[0].get('hashes', {}).get('SHA256')
dl_url = files[0].get('downloadUrl')
break
model_folder = _api.contenttype_folder(content_type, desc)
@ -168,13 +176,23 @@ def selected_to_queue(model_list, download_start, create_json):
default_sub = sub_opt1
elif default_sub == f"{os.sep}Model Name{os.sep}Version Name":
default_sub = sub_opt2
if subfolder and subfolder != "None":
from_batch = False
if platform.system() == "Windows":
subfolder = re.sub(r'[/:*?"<>|]', '', subfolder)
if default_sub != "None":
install_path = model_folder + default_sub
if not subfolder.startswith(os.sep):
subfolder = os.sep + subfolder
install_path = model_folder + subfolder
else:
install_path = model_folder
from_batch = True
if default_sub != "None":
install_path = model_folder + default_sub
else:
install_path = model_folder
model_item = create_model_item(dl_url, model_filename, install_path, model_name, version_name, model_sha256, model_id, create_json)
model_item = create_model_item(dl_url, model_filename, install_path, model_name, version_name, model_sha256, model_id, create_json, from_batch)
if model_item:
gl.download_queue.append(model_item)
total_count += 1
@ -214,11 +232,11 @@ def download_start(download_start, dl_url, model_filename, install_path, model_s
def download_finish(model_filename, version, model_id):
if model_id:
model_id = int(model_id)
gr_components = _api.update_model_versions(model_id)
model_versions = _api.update_model_versions(model_id)
else:
gr_components = None
if gr_components:
version_choices = gr_components['choices']
model_versions = None
if model_versions:
version_choices = model_versions.get('choices', [])
else:
version_choices = []
prev_version = gl.last_version + " [Installed]"
@ -427,7 +445,7 @@ def info_to_json(install_path, model_id, model_sha256, unpackList=None):
json_file = os.path.splitext(install_path)[0] + ".json"
if os.path.exists(json_file):
try:
with open(json_file, 'r') as f:
with open(json_file, 'r', encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
print(f"Failed to open {json_file}: {e}")
@ -439,7 +457,7 @@ def info_to_json(install_path, model_id, model_sha256, unpackList=None):
if unpackList:
data['unpackList'] = unpackList
with open(json_file, 'w') as f:
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):
@ -479,7 +497,7 @@ def download_file_old(url, file_path, progress=gr.Progress() if queue else None)
headers = {"Range": f"bytes={downloaded_size}-"}
else:
headers = {}
with open(file_path, "ab") as f:
with open(file_path, "ab", encoding="utf-8") as f:
while gl.isDownloading:
try:
if gl.cancel_status:
@ -578,9 +596,13 @@ def download_create_thread(download_finish, queue_trigger, progress=gr.Progress(
gl.cancel_status = False
use_aria2 = getattr(opts, "use_aria2", True)
unpack_zip = getattr(opts, "unpack_zip", False)
save_all_images = getattr(opts, "auto_save_all_img", False)
gl.recent_model = item['model_name']
gl.last_version = item['version_name']
if item['from_batch']:
item['install_path'] = item['existing_path']
gl.isDownloading = True
if not os.path.exists(item['install_path']):
os.makedirs(item['install_path'])
@ -617,9 +639,12 @@ def download_create_thread(download_finish, queue_trigger, progress=gr.Progress(
print(f"Failed to extract {item['model_filename']} with error: {e}")
if not gl.cancel_status:
if item['create_json']:
_file.save_model_info(item['install_path'], item['model_filename'], item['model_sha256'], api_response=item['model_json'])
_file.save_model_info(item['install_path'], item['model_filename'], item['model_sha256'], item['preview_html'], api_response=item['model_json'])
info_to_json(path_to_new_file, item['model_id'], item['model_sha256'], unpackList)
_file.save_preview(path_to_new_file, item['model_json'], True, item['model_sha256'])
if save_all_images:
_file.save_images(item['preview_html'], item['model_filename'], item['install_path'], )
else:
_file.save_preview(path_to_new_file, item['model_json'], True, item['model_sha256'])
base_name = os.path.splitext(item['model_filename'])[0]
base_name_preview = base_name + '.preview'

View File

@ -12,6 +12,8 @@ import requests
import hashlib
from pathlib import Path
from urllib.parse import urlparse
from sympy import preview
from modules.shared import cmd_opts, opts
from scripts.civitai_global import print
import scripts.civitai_global as gl
@ -46,20 +48,20 @@ except:
def delete_model(delete_finish=None, model_filename=None, model_string=None, list_versions=None, sha256=None, selected_list=None, model_ver=None, model_json=None):
deleted = False
model_id = None
if model_string:
_, model_id = _api.extract_model_info(model_string)
if not model_ver:
gr_components = _api.update_model_versions(model_id)
else: gr_components = model_ver
model_versions = _api.update_model_versions(model_id)
else: model_versions = model_ver
(model_name, ver_value, ver_choices) = _file.card_update(gr_components, model_string, list_versions, False)
(model_name, ver_value, ver_choices) = _file.card_update(model_versions, model_string, list_versions, False)
if not model_json:
if model_id != None:
selected_content_type = None
for item in gl.json_data['items']:
if item['id'] == model_id:
if int(item['id']) == int(model_id):
selected_content_type = item['type']
desc = item['description']
break
@ -82,9 +84,11 @@ def delete_model(delete_finish=None, model_filename=None, model_string=None, lis
if file.endswith('.json'):
file_path = os.path.join(root, file)
try:
with open(file_path, 'r') as json_file:
with open(file_path, 'r', encoding="utf-8") as json_file:
data = json.load(json_file)
file_sha256 = data.get('sha256', '').upper()
file_sha256 = data.get('sha256', '')
if file_sha256:
file_sha256 = file_sha256.upper()
except Exception as e:
print(f"Failed to open: {file_path}: {e}")
file_sha256 = "0"
@ -170,7 +174,7 @@ def save_preview(file_path, api_response, overwrite_toggle=False, sha256=None):
if not sha256:
if os.path.exists(json_file):
try:
with open(json_file, 'r') as f:
with open(json_file, 'r', encoding="utf-8") as f:
data = json.load(f)
if 'sha256' in data and data['sha256']:
sha256 = data['sha256'].upper()
@ -199,26 +203,25 @@ def save_preview(file_path, api_response, overwrite_toggle=False, sha256=None):
print(f"No preview images found for \"{name}\"")
return
def save_images(preview_html, model_filename, model_string, install_path, sub_folder):
if model_string:
model_name, model_id = _api.extract_model_info(model_string)
def save_images(preview_html, model_filename, install_path, sub_folder=None):
image_location = getattr(opts, "image_location", r"")
sub_image_location = getattr(opts, "sub_image_location", True)
image_path = install_path
if image_location:
if sub_image_location:
desc = gl.json_info['description']
content_type = gl.json_info['type']
install_path = os.path.join(_api.contenttype_folder(content_type, desc, custom_folder=image_location))
if sub_folder and sub_folder != "None":
install_path = os.path.join(install_path, sub_folder.lstrip("/").lstrip("\\"))
else:
install_path = Path(image_location)
image_path = os.path.join(_api.contenttype_folder(content_type, desc, custom_folder=image_location))
if not sub_folder:
sub_folder = os.path.relpath(install_path, image_path)
if not os.path.exists(install_path):
os.makedirs(install_path)
if sub_folder and sub_folder != "None":
image_path = os.path.join(image_path, sub_folder.lstrip("/").lstrip("\\"))
else:
image_path = Path(image_location)
if not os.path.exists(image_path):
os.makedirs(image_path)
img_urls = re.findall(r'data-sampleimg="true" src=[\'"]?([^\'" >]+)', preview_html)
name = os.path.splitext(model_filename)[0]
@ -227,45 +230,20 @@ def save_images(preview_html, model_filename, model_string, install_path, sub_fo
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
urllib.request.install_opener(opener)
HTML = preview_html
image_count = 0
for i, img_url in enumerate(img_urls):
image_count += 1
filename = f'{name}_{i}.png'
filenamethumb = f'{name}.png'
if model_id is not None:
for item in gl.json_data['items']:
if item['id'] == model_id:
if item['type'] == "TextualInversion":
filename = f'{name}_{i}.preview.png'
filenamethumb = f'{name}.preview.png'
HTML = HTML.replace(img_url,f'{filename}')
img_url = urllib.parse.quote(img_url, safe=':/=')
if i == 0:
filename = f'{name}.preview.png'
else:
filename = f'{name}_{i}.png'
img_url = urllib.parse.quote(img_url, safe=':/=')
try:
with urllib.request.urlopen(img_url) as url:
with open(os.path.join(install_path, filename), 'wb') as f:
with open(os.path.join(install_path if i == 0 else image_path, filename), 'wb') as f:
f.write(url.read())
if i == 0 and not os.path.exists(os.path.join(install_path, filenamethumb)):
shutil.copy2(os.path.join(install_path, filename),os.path.join(install_path, filenamethumb))
print(f"Downloaded image {image_count}")
print(f"Downloaded {filename}")
except urllib.error.URLError as e:
print(f'Error: {e.reason}')
match = re.search(r'(\s*)<div class="model-block">', preview_html)
if match:
indentation = match.group(1)
else:
indentation = ''
css_link = f'<link rel="stylesheet" type="text/css" href="{css_path}">'
head_section = f'{indentation}<head>{indentation} {css_link}{indentation}</head>'
HTML = head_section + HTML
path_to_new_file = os.path.join(install_path, f'{name}.html')
with open(path_to_new_file, 'wb') as f:
f.write(HTML.encode('utf8'))
path_to_new_file = os.path.join(install_path, f'{name}.civitai.info')
with open(path_to_new_file, mode="w", encoding="utf-8") as f:
json.dump(gl.json_info, f, indent=4, ensure_ascii=False)
def card_update(gr_components, model_name, list_versions, is_install):
if gr_components:
@ -316,7 +294,7 @@ def gen_sha256(file_path):
if os.path.exists(json_file):
try:
with open(json_file, 'r') as f:
with open(json_file, 'r', encoding="utf-8") as f:
data = json.load(f)
if 'sha256' in data and data['sha256']:
@ -344,25 +322,27 @@ def gen_sha256(file_path):
if os.path.exists(json_file):
try:
with open(json_file, 'r') as f:
with open(json_file, 'r', encoding="utf-8") as f:
data = json.load(f)
if 'sha256' in data and data['sha256']:
data['sha256'] = hash_value
with open(json_file, 'w') as f:
with open(json_file, 'w', encoding="utf-8") as f:
json.dump(data, f, indent=4)
except Exception as e:
print(f"Failed to open {json_file}: {e}")
else:
data = {'sha256': hash_value}
with open(json_file, 'w') as f:
with open(json_file, 'w', encoding="utf-8") as f:
json.dump(data, f, indent=4)
return hash_value
def model_from_sent(model_name, content_type, click_first_item, tile_count):
def model_from_sent(model_name, content_type, tile_count):
modelID_failed = False
output_html = None
use_local_html = getattr(opts, "use_local_html", False)
model_name = re.sub(r'\.\d{3}$', '', model_name)
content_type = re.sub(r'\.\d{3}$', '', content_type)
content_mapping = {
@ -382,38 +362,68 @@ def model_from_sent(model_name, content_type, click_first_item, tile_count):
if file.startswith(model_name) and file.endswith(tuple(extensions)):
model_file = os.path.join(folder_path, file)
modelID = get_models(model_file, True)
if not modelID or modelID == "Model not found":
HTML = '<div style="font-size: 24px; text-align: center; margin: 50px !important;">Model ID not found.<br>maybe the model doesn\'t exist on CivitAI?</div>'
modelID_failed = True
if modelID == "offline":
HTML = offlineHTML
modelID_failed = True
if not modelID_failed:
gl.json_data = _api.api_to_data(content_type, "Newest", "AllTime", "Model name", None, None, None, tile_count, f"civitai.com/models/{modelID}")
else: gl.json_data = None
if use_local_html:
html_file = os.path.splitext(model_file)[0] + ".html"
if os.path.exists(html_file):
with open(html_file, 'r', encoding='utf-8') as html:
output_html = html.read()
index = output_html.find("</head>")
if index != -1:
output_html = output_html[index + len("</head>"):]
fail_html = '<div style="color: white; font-family: var(--font); font-size: 24px; text-align: center; margin: 50px !important;">Model ID not found.<br>Maybe the model doesn\'t exist on CivitAI?</div>'
if gl.json_data == "timeout":
HTML = offlineHTML
if gl.json_data != None and gl.json_data != "timeout":
HTML = _api.model_list_html(gl.json_data)
(hasPrev, hasNext, current_page, total_pages) = _api.pagecontrol(gl.json_data)
page_string = f"Page: {current_page}/{total_pages}"
number = _download.random_number(click_first_item)
else:
number = click_first_item
hasPrev = False
hasNext = False
page_string = "Page: 0/0"
current_page = 0
total_pages = 0
if not output_html:
modelID = get_models(model_file, True)
if not modelID or modelID == "Model not found":
output_html = fail_html
modelID_failed = True
if modelID == "offline":
output_html = fail_html
modelID_failed = True
if not modelID_failed:
json_data = _api.api_to_data(content_type, "Newest", "AllTime", "Model name", None, None, None, tile_count, f"civitai.com/models/{modelID}")
else: json_data = None
if json_data == "timeout":
output_html = fail_html
if json_data != None and json_data != "timeout":
model_versions = _api.update_model_versions(modelID, json_data)
output_html = _api.update_model_info(None, model_versions.get('value'), True, modelID, json_data)
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)',
'#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)',
'#374151': 'var(--input-border-color)',
'top: 50%;': '',
'padding-top: 0px;': 'padding-top: 475px;',
'.civitai_txt2img': '.civitai_placeholder'
}
for old, new in replacements.items():
css = css.replace(old, new)
style_tag = f'<style>{css}</style>'
head_section = f'<head>{style_tag}</head>'
output_html = output_html.replace('display:flex;align-items:flex-start;', 'display:flex;align-items:flex-start;flex-wrap:wrap;justify-content:center;')
output_html = str(head_section + output_html)
output_html = output_html.replace('zoom-radio', 'zoom-preview-radio')
output_html = output_html.replace('zoomRadio', 'zoomPreviewRadio')
output_html = output_html.replace('zoom-overlay', 'zoom-preview-overlay')
output_html = output_html.replace('resetZoom', 'resetPreviewZoom')
number = _download.random_number()
return (
gr.HTML.update(HTML), # Card HTML
gr.Button.update(interactive=hasPrev), # Prev Button
gr.Button.update(interactive=hasNext), # Next Button
gr.Slider.update(value=current_page, maximum=total_pages, label=page_string), # Page Slider
gr.Textbox.update(number) # Click first card trigger
gr.Textbox.update(value=output_html, placeholder=number), # Preview HTML
)
def is_image_url(url):
@ -436,28 +446,48 @@ def clean_description(desc):
cleaned_text = desc
return cleaned_text
def save_model_info(install_path, file_name, sha256=None, overwrite_toggle=False, api_response=None):
file_path = os.path.join(install_path, file_name)
json_file = os.path.splitext(file_path)[0] + ".json"
def save_model_info(install_path, file_name, sha256=None, preview_html=None, overwrite_toggle=False, api_response=None):
filename = os.path.splitext(file_name)[0]
json_file = os.path.join(install_path, f'{filename}.json')
if not os.path.exists(install_path):
os.makedirs(install_path)
save_api_info = getattr(opts, "save_api_info", False)
if not sha256:
if os.path.exists(json_file):
try:
with open(json_file, 'r') as f:
with open(json_file, 'r', encoding="utf-8") as f:
data = json.load(f)
if 'sha256' in data and data['sha256']:
sha256 = data['sha256'].upper()
except Exception as e:
print(f"Failed to open {json_file}: {e}")
if not api_response:
api_response = gl.json_data
result = find_and_save(api_response, sha256, file_name, json_file, False, overwrite_toggle)
if result == "found":
return
else:
result = find_and_save(api_response, sha256, file_name, json_file, True, overwrite_toggle)
if result != "found":
result = find_and_save(api_response, sha256, file_name, json_file, True, overwrite_toggle)
if preview_html:
match = re.search(r'(\s*)<div class="model-block">', preview_html)
if match:
indentation = match.group(1)
else:
indentation = ''
css_link = f'<link rel="stylesheet" type="text/css" href="{css_path}">'
utf8_meta_tag = f'{indentation}<meta charset="UTF-8">'
head_section = f'{indentation}<head>{indentation} {utf8_meta_tag}{indentation} {css_link}{indentation}</head>'
HTML = head_section + preview_html
path_to_new_file = os.path.join(install_path, f'{filename}.html')
with open(path_to_new_file, 'wb') as f:
f.write(HTML.encode('utf8'))
if save_api_info:
path_to_new_file = os.path.join(install_path, f'{filename}.api_info.json')
with open(path_to_new_file, mode="w", encoding="utf-8") as f:
json.dump(gl.json_info, f, indent=4, ensure_ascii=False)
def find_and_save(api_response, sha256=None, file_name=None, json_file=None, no_hash=None, overwrite_toggle=None):
for item in api_response.get('items', []):
@ -467,6 +497,7 @@ def find_and_save(api_response, sha256=None, file_name=None, json_file=None, no_
sha256_api = file.get('hashes', {}).get('SHA256', '')
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', '')
@ -498,7 +529,7 @@ def find_and_save(api_response, sha256=None, file_name=None, json_file=None, no_
trained_tags = trained_words
if os.path.exists(json_file):
with open(json_file, 'r') as f:
with open(json_file, 'r', encoding="utf-8") as f:
try:
content = json.load(f)
except:
@ -522,7 +553,7 @@ def find_and_save(api_response, sha256=None, file_name=None, json_file=None, no_
content["sd version"] = base_model
changed = True
with open(json_file, 'w') as f:
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}\"")
@ -536,7 +567,7 @@ def get_models(file_path, gen_hash=None):
json_file = os.path.splitext(file_path)[0] + ".json"
if os.path.exists(json_file):
try:
with open(json_file, 'r') as f:
with open(json_file, 'r', encoding="utf-8") as f:
data = json.load(f)
if 'modelId' in data:
@ -575,13 +606,13 @@ def get_models(file_path, gen_hash=None):
if os.path.exists(json_file):
try:
with open(json_file, 'r') as f:
with open(json_file, 'r', encoding="utf-8") as f:
data = json.load(f)
data['modelId'] = modelId
data['sha256'] = sha256.upper()
with open(json_file, 'w') as f:
with open(json_file, 'w', encoding="utf-8") as f:
json.dump(data, f, indent=4)
except Exception as e:
print(f"Failed to open {json_file}: {e}")
@ -590,7 +621,7 @@ def get_models(file_path, gen_hash=None):
'modelId': modelId,
'sha256': sha256.upper()
}
with open(json_file, 'w') as f:
with open(json_file, 'w', encoding="utf-8") as f:
json.dump(data, f, indent=4)
return modelId
@ -611,7 +642,7 @@ def version_match(file_paths, api_response):
for file_path in file_paths:
json_path = f"{os.path.splitext(file_path)[0]}.json"
if os.path.exists(json_path):
with open(json_path, 'r') as f:
with open(json_path, 'r', encoding="utf-8") as f:
try:
json_data = json.load(f)
sha256 = json_data.get('sha256')
@ -624,7 +655,8 @@ def version_match(file_paths, api_response):
for file_path in file_paths:
file_name = os.path.basename(file_path)
file_name_without_ext = os.path.splitext(file_name)[0]
file_sha256 = sha256_hashes.get(file_name, "").upper()
file_sha256 = sha256_hashes.get(file_name, "")
if file_sha256: file_sha256 = file_sha256.upper()
file_names_and_hashes.add((file_name_without_ext, file_sha256))
for item in api_response.get('items', []):
@ -638,7 +670,8 @@ def version_match(file_paths, api_response):
match_found = False
for file_entry in files:
entry_name = os.path.splitext(file_entry.get('name', ''))[0]
entry_sha256 = file_entry.get('hashes', {}).get('SHA256', "").upper()
entry_sha256 = file_entry.get('hashes', {}).get('SHA256', "")
if entry_sha256: entry_sha256 = entry_sha256.upper()
if (entry_name, entry_sha256) in file_names_and_hashes:
match_found = True
@ -741,6 +774,7 @@ def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish,
outdated_models = []
all_model_ids = []
file_paths = []
all_ids = []
for file_path in files:
if gl.cancel_status:
@ -763,6 +797,7 @@ def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish,
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:
print(f"model ID not found for: \"{file_name}\"")
@ -915,9 +950,11 @@ def file_scan(folders, ver_finish, tag_finish, installed_finish, preview_finish,
)
elif from_tag:
for file in file_paths:
install_path, file_name = os.path.split(file)
save_model_info(install_path, file_name, api_response=api_response, overwrite_toggle=overwrite_toggle)
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)
save_model_info(install_path, file_name, preview_html=preview_html, api_response=api_response, overwrite_toggle=overwrite_toggle)
if progress != None:
progress(1, desc=f"All tags succesfully saved!")
gl.scan_files = False

View File

@ -5,7 +5,6 @@ import json
import fnmatch
import re
import subprocess
from pathlib import Path
from modules.shared import opts, cmd_opts
from modules.paths import extensions_dir
from scripts.civitai_global import print
@ -76,30 +75,56 @@ def saveSettings(ust, ct, pt, st, bf, cj, td, ol, hi, sn, ss, ts):
data.update(settings_map)
# Save the modified content back to the file
with open(config, 'w') as file:
with open(config, 'w', encoding="utf-8") as file:
json.dump(data, file, indent=4)
print(f"Updated settings to: {config}")
def all_visible(html_check):
return gr.Button.update(visible="model-checkbox" in html_check)
def show_multi_buttons(input_list, version_value, model_id):
input_list = json.loads(input_list)
BtnDwn = version_value and not version_value.endswith('[Installed]') and not input_list
def show_multi_buttons(model_list, type_list, version_value):
model_list = json.loads(model_list)
type_list = json.loads(type_list)
otherButtons = True
multi_file_subfolder = False
default_subfolder = "Only available if the selected files are of the same model type"
sub_folders = ["None"]
BtnDwn = version_value and not version_value.endswith('[Installed]') and not model_list
BtnDel = version_value.endswith('[Installed]')
multi = bool(input_list) and not len(gl.download_queue) > 0
BtnDwnInt = BtnDwn
if len(gl.download_queue) > 0:
for item in gl.download_queue:
if int(model_id) == int(item['model_id']):
BtnDwnInt = False
break
dot_subfolders = getattr(opts, "dot_subfolders", True)
multi = bool(model_list) and not len(gl.download_queue) > 0
if model_list:
otherButtons = False
if type_list and all(x == type_list[0] for x in type_list):
multi_file_subfolder = True
model_folder = os.path.join(_api.contenttype_folder(type_list[0]))
default_subfolder = "None"
try:
for root, dirs, _ in os.walk(model_folder):
if dot_subfolders:
dirs = [d for d in dirs if not d.startswith('.')]
dirs = [d for d in dirs if not any(part.startswith('.') for part in os.path.join(root, d).split(os.sep))]
for d in dirs:
sub_folder = os.path.relpath(os.path.join(root, d), model_folder)
if sub_folder:
sub_folders.append(f'{os.sep}{sub_folder}')
sub_folders.remove("None")
sub_folders = sorted(sub_folders, key=lambda x: (x.lower(), x))
sub_folders.insert(0, "None")
list = set()
sub_folders = [x for x in sub_folders if not (x in list or list.add(x))]
except:
sub_folders = ["None"]
return (gr.Button.update(visible=multi, interactive=multi), # Download Multi Button
gr.Button.update(visible=BtnDwn if multi else True if not version_value.endswith('[Installed]') else False, interactive=BtnDwnInt), # Download Button
gr.Button.update(visible=BtnDel) # Delete Button
gr.Button.update(visible=BtnDwn if multi else True if not version_value.endswith('[Installed]') else False), # Download Button
gr.Button.update(visible=BtnDel if not model_list else False), # Delete Button
gr.Button.update(visible=otherButtons), # Save model info Button
gr.Button.update(visible=otherButtons), # Save images Button
gr.Dropdown.update(visible=multi, interactive=multi_file_subfolder, choices=sub_folders, value=default_subfolder) # Selected type sub folder
)
def txt2img_output(image_url):
@ -202,7 +227,8 @@ def on_ui_tabs():
save_images = gr.Button(value="Save images", interactive=False)
delete_model = gr.Button(value="Delete model", interactive=False, visible=False)
download_model = gr.Button(value="Download model", interactive=False)
download_selected = gr.Button(value="Download all selected", interactive=False, visible=False)
subfolder_selected = gr.Dropdown(label="Sub folder for selected files:", choices=[], interactive=False, visible=False, value=None, allow_custom_value=True)
download_selected = gr.Button(value="Download all selected", interactive=False, visible=False, elem_id="download_all_button")
with gr.Row():
cancel_all_model = gr.Button(value="Cancel all downloads", interactive=False, visible=False)
cancel_model = gr.Button(value="Cancel current download", interactive=False, visible=False)
@ -241,16 +267,17 @@ def on_ui_tabs():
installed_progress = gr.HTML(value='<div style="min-height: 0px;"></div>')
#Invisible triggers/variables
model_id = gr.Textbox(visible=False)
queue_trigger = gr.Textbox(visible=False)
dl_url = gr.Textbox(visible=False)
text2imgOutput = gr.Textbox(visible=False)
txt2imgInput = gr.Textbox(elem_id="civitai_text2img_input", visible=False)
selected_list = gr.Textbox(elem_id="selected_list", visible=False)
civitai_text2img_output = gr.Textbox(visible=False)
civitai_text2img_input = gr.Textbox(elem_id="civitai_text2img_input", visible=False)
selected_model_list = gr.Textbox(elem_id="selected_model_list", visible=False)
selected_type_list = gr.Textbox(elem_id="selected_type_list", visible=False)
model_select = gr.Textbox(elem_id="model_select", visible=False)
model_sent = gr.Textbox(elem_id="model_sent", visible=False)
type_sent = gr.Textbox(elem_id="type_sent", visible=False)
click_first_item = gr.Textbox(visible=False)
download_start = gr.Textbox(visible=False)
download_finish = gr.Textbox(visible=False)
tag_start = gr.Textbox(visible=False)
@ -264,6 +291,7 @@ def on_ui_tabs():
delete_finish = gr.Textbox(visible=False)
current_model = gr.Textbox(visible=False)
current_sha256 = gr.Textbox(visible=False)
model_preview_html = gr.Textbox(visible=False)
def ToggleDate(toggle_date):
gl.sortNewest = toggle_date
@ -280,14 +308,12 @@ def on_ui_tabs():
list_html.change(fn=None, inputs=hide_installed, _js="(toggleValue) => hideInstalled(toggleValue)")
hide_installed.input(fn=None, inputs=hide_installed, _js="(toggleValue) => hideInstalled(toggleValue)")
text2imgOutput.change(fn=None, inputs=text2imgOutput, _js="(genInfo) => genInfo_to_txt2img(genInfo)")
civitai_text2img_output.change(fn=None, inputs=civitai_text2img_output, _js="(genInfo) => genInfo_to_txt2img(genInfo)")
download_selected.click(fn=None, _js="() => deselectAllModels()")
select_all.click(fn=None, _js="() => selectAllModels()")
click_first_item.change(fn=None, _js="() => clickFirstFigureInColumn()")
list_models.select(fn=None, inputs=list_models, _js="(list_models) => select_model(list_models)")
preview_html.change(fn=None, _js="() => adjustFilterBoxAndButtons()")
@ -306,6 +332,8 @@ def on_ui_tabs():
list_html.change(fn=None, inputs=size_slider, _js="(size) => updateCardSize(size, size * 1.5)")
size_slider.change(fn=None, inputs=size_slider, _js="(size) => updateCardSize(size, size * 1.5)")
model_preview_html.change(fn=None, inputs=model_preview_html, _js="(html_input) => inputHTMLPreviewContent(html_input)")
# Filter button Functions #
save_settings.click(
@ -333,17 +361,17 @@ def on_ui_tabs():
# Model Button Functions #
txt2imgInput.change(fn=txt2img_output,inputs=txt2imgInput,outputs=text2imgOutput)
civitai_text2img_input.change(fn=txt2img_output,inputs=civitai_text2img_input,outputs=civitai_text2img_output)
list_html.change(fn=all_visible,inputs=list_html,outputs=select_all)
def update_models_dropdown(input):
model_string = re.sub(r'\.\d{3}$', '', input)
model_name, model_id = _api.extract_model_info(model_string)
ret_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, ret_versions['value'])
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),
ret_versions,html,tags,base_mdl,filename,install_path,sub_folder,DwnButton,SaveImages,DelButton,filelist,dl_url,id,current_sha256,
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))
model_select.change(
@ -371,8 +399,8 @@ def on_ui_tabs():
model_sent.change(
fn=_file.model_from_sent,
inputs=[model_sent, type_sent, click_first_item, tile_count_slider],
outputs=[list_html, get_prev_page , get_next_page, page_slider, click_first_item]
inputs=[model_sent, type_sent, tile_count_slider],
outputs=[model_preview_html]
)
sub_folder.select(
@ -425,13 +453,16 @@ def on_ui_tabs():
# Download/Save Model Button Functions #
selected_list.change(
selected_model_list.change(
fn=show_multi_buttons,
inputs=[selected_list, list_versions, model_id],
inputs=[selected_model_list, selected_type_list, list_versions],
outputs=[
download_selected,
download_model,
delete_model
delete_model,
save_info,
save_images,
subfolder_selected
]
)
@ -459,7 +490,7 @@ def on_ui_tabs():
download_selected.click(
fn=_download.selected_to_queue,
inputs=[selected_list, download_start, create_json],
inputs=[selected_model_list, subfolder_selected, download_start, create_json],
outputs=[
download_model,
cancel_model,
@ -510,7 +541,7 @@ def on_ui_tabs():
list_models,
list_versions,
current_sha256,
selected_list
selected_model_list
],
outputs=[
download_model,
@ -527,7 +558,8 @@ def on_ui_tabs():
inputs=[
install_path,
model_filename,
current_sha256
current_sha256,
preview_html
],
outputs=[]
)
@ -537,7 +569,6 @@ def on_ui_tabs():
inputs=[
preview_html,
model_filename,
list_models,
install_path,
sub_folder
],
@ -807,9 +838,10 @@ def subfolder_list(folder, desc=None):
sub_folders = sorted(sub_folders, key=lambda x: (x.lower(), x))
sub_folders.insert(0, "None")
if insert_sub:
sub_folders.insert(1, f"{os.sep}Base Model")
sub_folders.insert(2, f"{os.sep}Author Name")
sub_folders.insert(2, f"{os.sep}Model Name")
sub_folders.insert(3, f"{os.sep}Model Name{os.sep}Version Name")
sub_folders.insert(3, f"{os.sep}Model Name")
sub_folders.insert(4, f"{os.sep}Model Name{os.sep}Version Name")
list = set()
sub_folders = [x for x in sub_folders if not (x in list or list.add(x))]
@ -899,6 +931,26 @@ def on_ui_settings():
)
)
shared.opts.add_option(
"save_api_info",
shared.OptionInfo(
False,
"Save API info of model when saving model info",
section=download,
**({'category_id': cat_id} if ver_bool else {})
).info("creates an api_info.json file when saving any model info with all the API data of the model")
)
shared.opts.add_option(
"auto_save_all_img",
shared.OptionInfo(
False,
"Automatically save all images",
section=download,
**({'category_id': cat_id} if ver_bool else {})
).info("Automatically saves all the images of a model after downloading")
)
# Browser Options
shared.opts.add_option(
"custom_api_key",
@ -934,7 +986,7 @@ def on_ui_settings():
"insert_sub",
shared.OptionInfo(
True,
f"Insert [{os.sep}Author Name] & [{os.sep}Model Name] & [{os.sep}Model Name{os.sep}Version Name] as sub folder options",
f"Insert [{os.sep}Base model] &[{os.sep}Author Name] & [{os.sep}Model Name] & [{os.sep}Model Name{os.sep}Version Name] as sub folder options",
section=browser,
**({'category_id': cat_id} if ver_bool else {})
)
@ -949,6 +1001,16 @@ def on_ui_settings():
**({'category_id': cat_id} if ver_bool else {})
)
)
shared.opts.add_option(
"use_local_html",
shared.OptionInfo(
False,
"Use local HTML file for model info",
section=browser,
**({'category_id': cat_id} if ver_bool else {})
).info("Uses the matching local HTML file when pressing CivitAI button on model cards in txt2img and img2img")
)
shared.opts.add_option(
"page_header",

View File

@ -143,6 +143,13 @@
min-height: 650px;
}
#download_all_button {
max-height: 40px;
height: 40px;
align-self: end;
margin-bottom: 1px;
}
#searchBox > label > textarea {
padding-top: 11px !important;
}

View File

@ -51,7 +51,7 @@ p {
dt {
font-size: medium;
color: #80a6c8!important;
color: #80a6c8 !important;
font-size: 16px;
}
@ -78,7 +78,7 @@ a:hover {
/* Preview Image zoom */
.zoom-radio {
display: none!important;
display: none !important;
}
/* Style for when the image is clicked (radio button checked) */
@ -186,6 +186,19 @@ a:hover {
font-size: large;
}
.civitai-tags-container {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.civitai-tag {
background-color: #1F2937;
border-radius: 8px;
padding: 4px 6px;
border: 1px solid #374151;
}
/* Icon */
.tab-label::before {
content: "";