new models tab including civitai downloader

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2025-08-04 14:19:33 -04:00
parent 895c7f41fb
commit 8aff68fe06
11 changed files with 415 additions and 577 deletions
+11 -5
View File
@@ -1,6 +1,6 @@
# Change Log for SD.Next
## Update for 2025-08-02
## Update for 2025-08-04
- **Models**
- [FLUX.1-Krea-Dev](https://www.krea.ai/blog/flux-krea-open-source-release)
@@ -14,15 +14,21 @@
- new embedded docs/wiki search!
**Docs** search: fully-local and works in real-time on all document pages
**Wiki** search: uses github api to search online wiki pages
- quicksettings reset button to restore all quicksettings to default values
because things do sometimes get wrong...
- updated real-time hints, thanks @CalamitousFelicitousness
- updated *models -> current* tab
- rewritten **CivitAI downloader**
in *models -> civitai*
- updated *models -> current* tab
- updated *models -> list models* tab
- updated *models -> metadata* tab
- more css optimizations and styling
- quicksettings reset button to restore all quicksettings to default values
because things do sometimes get wrong...
- redesign *settings -> user interface*
- gallery bypass browser cache for thumbnails
- gallery safer delete operation
- more css optimizations and styling
- *hint*: card layout
card layout is used by networks, gallery, civitai search, etc.
you can change card size in *settings -> user interface*
- **Offloading**
- changed **default** values for offloading based on detected gpu memory
see [offloading docs](https://vladmandic.github.io/sdnext-docs/Offload/) for details
+97 -232
View File
@@ -1,31 +1,76 @@
// hack to get pythons str.format in js
String.prototype.format = function (arguments) { // eslint-disable-line no-extend-native, func-names
String.prototype.format = function (args) { // eslint-disable-line no-extend-native, func-names
let thisString = '';
for (let charPos = 0; charPos < this.length; charPos++) thisString += this[charPos];
for (const key in arguments) { // eslint-disable-line guard-for-in
error(key, arguments[key]);
for (const key in args) { // eslint-disable-line guard-for-in
const stringKey = `{${key}}`;
thisString = thisString.replace(new RegExp(stringKey, 'g'), arguments[key]);
thisString = thisString.replace(new RegExp(stringKey, 'g'), args[key]);
}
return thisString;
};
let selectedURL = '';
let selectedName = '';
let selectedType = '';
function clearModelDetails() {
const el = gradioApp().getElementById('model-details') || gradioApp().getElementById('civitai_models_output') || gradioApp().getElementById('models_outcome');
if (!el) return;
el.innerHTML = '';
}
const modelDetailsHTML = `
<div id="model-details" class="model-details">
<h3>{name}</h3>
<p>Type: {type}</p>
<p>Tags: {tags}</p>
<p>NSFW: {nsfw}/{level}</p>
<p>Availability: {availability}</p>
<p>Downloads: {downloads}</p>
<p>Author: {creator}</p>
<div>{versions}</div>
<div>
<img src="{image}" alt="model image" class="preview" style="display: none">
<button style="float: right" class="lg secondary gradio-button tool extra-details-close" id="model_details_close" data-hint="Close" onclick="clearModelDetails()"> ✕</button>
<table id="model-details-table" class="model-details simple-table">
<tr><td>Name</td><td>{name}</td></tr>
<tr><td>Type</td><td>{type}</td></tr>
<tr><td>Tags</td><td><div>{tags}</div></td></tr>
<tr><td>NSFW</td><td>{nsfw} | {level}</td></tr>
<tr><td>Availability</td><td>{availability}</td></tr>
<tr><td>Downloads</td><td>{downloads}</td></tr>
<tr><td>Author</td><td>{creator}</td></tr>
<tr><td>Description</td><td><div>{desc}</div></td></tr>
</table>
<br>
<table id="model-versions-table" class="model-versions simple-table">
<thead>
<tr>
<th> </th>
<th>Version</th>
<th>Type</th>
<th>Base</th>
<th>File</th>
<th>Updated</th>
<th>Size</th>
<th>Availability</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{versions}
</tbody>
</table>
</div>
`;
const modelVersionsHTML = `
<tr>
<td>{url}</td>
<td>{name}</td>
<td>{type}</td>
<td>{base}</td>
<td>{file}</td>
<td>{mtime}</td>
<td>{size}</td>
<td>{availability}</td>
<td><div>{desc}</div></td>
</tr>
`;
async function modelCardClick(id) {
log('modelCardClick id', id);
const el = gradioApp().getElementById('model-details');
const el = gradioApp().getElementById('model-details') || gradioApp().getElementById('civitai_models_output') || gradioApp().getElementById('models_outcome');
if (!el) return;
const res = await fetch(`${window.api}/civitai?model_id=${encodeURI(id)}`);
if (!res || res.status !== 200) {
@@ -36,229 +81,49 @@ async function modelCardClick(id) {
log('modelCardClick data', data);
if (!data || data.length === 0) return;
data = data[0]; // assuming the first item is the one we want
const obj = {
name: data.name || 'unknown',
const versionsHTML = data.versions.map((v) => modelVersionsHTML.format({
url: `<div class="link" onclick="startCivitDownload('${v.files[0]?.url}', '${v.files[0]?.name}', '${data.type}')"> \udb80\uddda </div>`,
name: v.name || 'unknown',
type: v.files[0]?.type || 'unknown',
base: v.base || 'unknown',
mtime: (new Date(v.mtime)).toLocaleDateString(),
availability: v.availability || 'unknown',
size: v.files[0]?.size ? `${(v.files[0].size / 1024 / 1024).toFixed(2)} MB` : 'unknown',
file: `<a href=${v.files[0]?.url} target="_blank" rel="noopener noreferrer">${v.files[0]?.name || 'unknown'}</a>`,
desc: v.desc || 'no description available',
})).join('');
const url = `<a href="${data.url}" target="_blank" rel="noopener noreferrer">${data.name || 'unknown'}</a>`;
const creator = `<a href="https://civitai.com/user/${data.creator}" target="_blank" rel="noopener noreferrer">${data.creator || 'unknown'}</a>`;
const images = data.versions.map((v) => v.images).flat().map((i) => i.url); // TODO image gallery
const modelHTML = modelDetailsHTML.format({
name: url,
type: data.type || 'unknown',
tags: data.tags?.join(', ') || '',
nsfw: data.nsfw ? 'yes' : 'no',
level: data.level?.toString() || '',
availability: data.availability || 'unknown',
downloads: data.downloads?.toString() || '',
creator: data.creator || 'unknown',
versions: JSON.stringify(data.versions) || '[]',
};
log(obj);
el.innerHTML = modelDetailsHTML.format({
name: data.name || 'unknown',
type: data.type || 'unknown',
tags: data.tags?.join(', ') || '',
nsfw: data.nsfw ? 'yes' : 'no',
level: data.level?.toString() || '',
availability: data.availability || 'unknown',
downloads: data.downloads?.toString() || '',
creator: data.creator || 'unknown',
versions: JSON.stringify(data.versions) || '[]',
creator,
desc: data.desc || 'no description available',
image: images.length > 0 ? images[0] : './sd_extra_networks/thumb?filename=html/card-no-preview.png',
versions: versionsHTML || '',
});
el.innerHTML = modelHTML;
}
const example = {
id: 1157409,
url: 'https://civitai.com/models/1157409',
type: 'Checkpoint',
name: 'Tempest-by-Vlad',
html: '',
desc: 'Base versionFlexible SDXL model with custom encoder and finetuned for larger landscape resolutions with high details and high contrast.Recommended to use medium-low...',
tags: [
'base model',
],
nsfw: false,
level: 15,
availability: 'Public',
downloads: 407,
creator: 'vmandic',
versions: [
{
id: 1301775,
name: 'Base v0.1',
base: 'SDXL 1.0',
mtime: '2025-01-19T02:53:53.903Z',
downloads: 346,
availability: 'Public',
html: '',
desc: 'Initial release',
files: [
{
id: 1206102,
size: 6938089790,
name: 'tempestByVlad_baseV01.safetensors',
type: 'Model',
hashes: [
'79CB1E32',
'8BFAD17222',
'8BFAD1722243955B3F94103C69079C280D348B14729251E86824972C1063B616',
'43E5E3BB',
'DE83D56256411853AB6595CC3D8E865D5310D4A58D49A839DDC104C7F3429D4A',
'4E933E1EBE61',
],
url: 'https://civitai.com/api/download/models/1301775',
dct: {},
},
],
images: [
{
id: 52503951,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/18c749f2-42ec-4024-9d20-0b1202b6bacc/width=1024/52503951.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 52508539,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/634d0ea8-ecdb-4ca6-a4ff-145319bc3fd3/width=1024/52508539.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 52508563,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/be529820-a89e-458f-8a3d-86cb43b154ac/width=1024/52508563.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 52508588,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/b2eb456a-a664-4de8-8c3e-6ecd1c4acb38/width=1024/52508588.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 52508654,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/f7b09e3f-4a48-459b-9b32-fa207904f74c/width=1024/52508654.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 52508659,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/fff89f18-628a-43b3-b9f5-44951dc078f7/width=1024/52508659.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 52508671,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/fdd3f774-ce2e-4ea7-82a0-bdb523fb86f6/width=1024/52508671.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 52512251,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/f26971c8-1123-45e3-a85b-2b97c6334b85/width=1024/52512251.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
],
dct: {},
},
{
id: 1343512,
name: 'Hyper v0.1',
base: 'SDXL 1.0',
mtime: '2025-01-28T22:54:12.734Z',
downloads: 61,
availability: 'Public',
html: '',
desc: 'Time-distilled version',
files: [
{
id: 1246991,
size: 6938085702,
name: 'tempestByVlad_hyperV01.safetensors',
type: 'Model',
hashes: [
'15943FD9',
'4104FC6601',
'4104FC6601F71C4C7A770AD422483FD700C8ECF72D06FCD8C4E8CD4B2D1C7DBB',
'9F87BCEA',
'CB52894625E9C13331285E4435799D707C4EAEF464974159C8B4B217EA32298E',
'A0EE15E503DD',
],
url: 'https://civitai.com/api/download/models/1343512',
dct: {},
},
],
images: [
{
id: 54462987,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/1dd020c8-8a9a-4eb3-afec-fe83613217c5/width=1024/54462987.jpeg',
width: 1024,
height: 768,
type: 'image',
dct: {},
},
{
id: 54462992,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/89edf233-939f-4b2e-97c8-175498704362/width=1536/54462992.jpeg',
width: 1536,
height: 640,
type: 'image',
dct: {},
},
{
id: 54463002,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/87aea8fb-7687-48d0-98e6-27555b2ff87f/width=768/54463002.jpeg',
width: 768,
height: 1024,
type: 'image',
dct: {},
},
{
id: 54463010,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/f1ee57cc-8920-4b8d-853a-ad1cfc7d9a5a/width=1024/54463010.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 54463011,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dda6411b-fd36-484f-a9f0-0db847463128/width=1024/54463011.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 54463016,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/5f266a8c-f215-4e0f-8a64-aacc97f81d70/width=1024/54463016.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
{
id: 54463019,
url: 'https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/0ad04bdc-41bd-433c-9c25-f5365fbe082c/width=1024/54463019.jpeg',
width: 1024,
height: 1024,
type: 'image',
dct: {},
},
],
dct: {},
},
],
dct: {},
};
function startCivitDownload(url, name, type) {
log('startCivitDownload', { url, name, type });
selectedURL = url;
selectedName = name;
selectedType = type;
const civitDownloadBtn = gradioApp().getElementById('civitai_download_btn');
if (civitDownloadBtn) civitDownloadBtn.click();
}
function downloadCivitModel(modelUrl, modelName, modelType, modelPath, civitToken, innerHTML) {
log('downloadCivitModel', { modelUrl, modelName, modelType, modelPath, civitToken });
const el = gradioApp().getElementById('civitai_models_output') || gradioApp().getElementById('models_outcome');
const currentHTML = el?.innerHTML || '';
return [selectedURL, selectedName, selectedType, modelPath, civitToken, currentHTML];
}
+2 -2
View File
@@ -110,8 +110,8 @@ function readCardDescription(page, item) {
function getCardsForActivePage() {
const pagename = getENActivePage();
if (!pagename) return [];
const allCards = Array.from(gradioApp().querySelectorAll('.extra-network-cards > .card'));
const cards = allCards.filter((el) => el.dataset.page.toLowerCase().includes(pagename.toLowerCase()));
let allCards = Array.from(gradioApp().querySelectorAll('.extra-network-cards > .card'));
allCards = allCards.filter((el) => el.dataset.page?.toLowerCase().includes(pagename.toLowerCase()));
// log('getCardsForActivePage', pagename, cards.length);
return allCards;
}
+56 -13
View File
@@ -66,6 +66,10 @@ button {
min-width: unset !important;
}
h4 {
margin: 0.2em 0em 0.2em 0em;
}
input[type='color'] {
height: 32px;
width: 64px;
@@ -122,6 +126,17 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
overflow: auto;
}
.link {
background-color: var(--background-fill-primary);
cursor: pointer;
border-radius: var(--input-radius);
width: 2em;
}
.link:hover {
background-color: var(--button-primary-background-fill);
}
.gradio-dropdown, .block.gradio-slider, .block.gradio-checkbox, .block.gradio-textbox, .block.gradio-radio, .block.gradio-checkboxgroup, .block.gradio-number, .block.gradio-colorpicker {
border-width: 0 !important;
box-shadow: none !important;
@@ -1226,11 +1241,13 @@ table.settings-value-table td {
}
.extra-network-cards .card {
height: fit-content;
margin: 0 0 0.5em 0.5em;
position: relative;
scroll-margin-top: 0;
scroll-snap-align: start;
margin: 0 0 0.5em 0.5em;
position: relative;
scroll-margin-top: 0;
scroll-snap-align: start;
height: var(--card-size);
width: var(--card-size);
contain: strict;
}
.extra-network-cards .card .overlay {
@@ -1923,10 +1940,6 @@ div:has(>#tab-gallery-folders) {
padding: 0.2em;
}
.docs-results {
background-color: var(--sd-group-background-color);
}
.docs-card {
margin: 1em 0;
background-color: var(--background-fill-primary);
@@ -1972,6 +1985,13 @@ div:has(>#tab-gallery-folders) {
overflow: auto;
}
.model-config {
font-size: 0.8em !important;
opacity: 0.8;
max-height: 6em;
overflow-y: auto;
}
.simple-table tr {
vertical-align: baseline;
}
@@ -1980,13 +2000,36 @@ div:has(>#tab-gallery-folders) {
padding: 0.2em !important;
}
.model-config {
font-size: 0.8em !important;
opacity: 0.8;
max-height: 6em;
.simple-table tr {
vertical-align: baseline;
}
.simple-table thead tr {
background-color: var(--button-primary-border-color) !important;
}
.simple-table tr:nth-child(odd) {
background-color: var(--neutral-900);
}
.simple-table td {
padding: 0.2em !important;
white-space: pre-wrap;
}
.simple-table td div {
padding: 0.2em !important;
white-space: pre-wrap;
max-height: 7em;
overflow-x: hidden;
overflow-y: auto;
}
.simple-table td:nth-child(1) {
color: var(--button-primary-border-color);
font-weight: bold;
}
@keyframes move {
from {
background-position-x: 0, -40px;
+191
View File
@@ -0,0 +1,191 @@
import os
import json
import rich.progress as p
from PIL import Image
from modules import shared, errors, paths
pbar = None
def save_video_frame(filepath: str):
from modules import video
try:
frames, fps, duration, w, h, codec, frame = video.get_video_params(filepath, capture=True)
except Exception as e:
shared.log.error(f'Video: file={filepath} {e}')
return None
if frame is not None:
basename = os.path.splitext(filepath)
thumb = f'{basename[0]}.thumb.jpg'
shared.log.debug(f'Video: file={filepath} frames={frames} fps={fps} size={w}x{h} codec={codec} duration={duration} thumb={thumb}')
frame.save(thumb)
else:
shared.log.error(f'Video: file={filepath} no frames found')
return frame
def download_civit_meta(model_path: str, model_id):
fn = os.path.splitext(model_path)[0] + '.json'
url = f'https://civitai.com/api/v1/models/{model_id}'
r = shared.req(url)
if r.status_code == 200:
try:
data = r.json()
shared.writefile(data, filename=fn, mode='w', silent=True)
shared.log.info(f'CivitAI download: id={model_id} url={url} file="{fn}"')
return r.status_code, len(data), '' # code/size/note
except Exception as e:
errors.display(e, 'civitai meta')
shared.log.error(f'CivitAI meta: id={model_id} url={url} file="{fn}" {e}')
return r.status_code, '', str(e)
return r.status_code, '', ''
def download_civit_preview(model_path: str, preview_url: str):
global pbar # pylint: disable=global-statement
if model_path is None:
pbar = None
return 500, '', ''
ext = os.path.splitext(preview_url)[1]
preview_file = os.path.splitext(model_path)[0] + ext
is_video = preview_file.lower().endswith('.mp4')
is_json = preview_file.lower().endswith('.json')
if is_json:
shared.log.warning(f'CivitAI download: url="{preview_url}" skip json')
return 500, '', 'exepected preview image got json'
if os.path.exists(preview_file):
return 304, '', 'already exists'
# res = f'CivitAI download: url={preview_url} file="{preview_file}"'
r = shared.req(preview_url, stream=True)
total_size = int(r.headers.get('content-length', 0))
block_size = 16384 # 16KB blocks
written = 0
img = None
shared.state.begin('CivitAI')
if pbar is None:
pbar = p.Progress(p.TextColumn('[cyan]Download'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn(), p.TextColumn('[yellow]{task.description}'), console=shared.console)
try:
with open(preview_file, 'wb') as f:
with pbar:
task = pbar.add_task(description=preview_file, total=total_size)
for data in r.iter_content(block_size):
written = written + len(data)
f.write(data)
pbar.update(task, advance=block_size)
if written < 1024: # min threshold
os.remove(preview_file)
return 400, '', 'removed invalid download'
if is_video:
img = save_video_frame(preview_file)
else:
img = Image.open(preview_file)
except Exception as e:
shared.log.error(f'CivitAI download error: url={preview_url} file="{preview_file}" written={written} {e}')
return 500, '', str(e)
shared.state.end()
if img is None:
return 500, '', 'image is none'
shared.log.info(f'CivitAI download: url={preview_url} file="{preview_file}" size={total_size} image={img.size}')
img.close()
return 200, str(total_size), '' # code/size/note
def download_civit_model_thread(model_name: str, model_url: str, model_path: str = "", model_type: str = "Model", token: str = None):
import hashlib
sha256 = hashlib.sha256()
sha256.update(model_url.encode('utf-8'))
temp_file = sha256.hexdigest()[:8] + '.tmp'
headers = {}
starting_pos = 0
if os.path.isfile(temp_file):
starting_pos = os.path.getsize(temp_file)
headers['Range'] = f'bytes={starting_pos}-'
if token is None or len(token) == 0:
token = shared.opts.civitai_token
if token is not None and len(token) > 0:
headers['Authorization'] = f'Bearer {token}'
r = shared.req(model_url, headers=headers, stream=True)
total_size = int(r.headers.get('content-length', 0))
if model_name is None or len(model_name) == 0:
cn = r.headers.get('content-disposition', '')
model_name = cn.split('filename=')[-1].strip('"')
model_path = model_path.strip()
if len(model_path) > 0:
if os.path.isabs(model_path):
pass
else:
model_path = os.path.join(paths.models_path, model_path)
elif model_type.lower() == 'lora':
model_path = shared.opts.lora_dir
elif model_type.lower() == 'embedding':
model_path = shared.opts.embeddings_dir
elif model_type.lower() == 'vae':
model_path = shared.opts.vae_dir
else:
model_path = shared.opts.ckpt_dir
model_file = os.path.join(model_path, model_name)
temp_file = os.path.join(model_path, temp_file)
res = f'Model download: name="{model_name}" url="{model_url}" path="{model_path}" temp="{temp_file}"'
if os.path.isfile(model_file):
res += ' already exists'
shared.log.warning(res)
return res
res += f' size={round((starting_pos + total_size)/1024/1024, 2)}Mb'
shared.log.info(res)
shared.state.begin('CivitAI')
block_size = 16384 # 16KB blocks
written = starting_pos
global pbar # pylint: disable=global-statement
if pbar is None:
pbar = p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn(), p.TextColumn('[cyan]{task.fields[name]}'), console=shared.console)
with pbar:
task = pbar.add_task(description="Download starting", total=starting_pos+total_size, name=model_name)
try:
with open(temp_file, 'ab') as f:
for data in r.iter_content(block_size):
if written == 0:
try: # check if response is JSON message instead of bytes
shared.log.error(f'Model download: response={json.loads(data.decode("utf-8"))}')
raise ValueError('response: type=json expected=bytes')
except Exception: # this is good
pass
written = written + len(data)
f.write(data)
pbar.update(task, description="Download", completed=written)
if written < 1024: # min threshold
os.remove(temp_file)
raise ValueError(f'removed invalid download: bytes={written}')
except Exception as e:
shared.log.error(f'{res} {e}')
finally:
pbar.stop_task(task)
pbar.remove_task(task)
if starting_pos+total_size != written:
shared.log.warning(f'{res} written={round(written/1024/1024)}Mb incomplete download')
elif os.path.exists(temp_file):
shared.log.debug(f'Model download complete: temp="{temp_file}" path="{model_file}"')
os.rename(temp_file, model_file)
shared.state.end()
if os.path.exists(model_file):
return model_file
else:
return None
def download_civit_model(model_url: str, model_name: str = '', model_path: str = '', model_type: str = '', token: str = None):
import threading
if model_url is None or len(model_url) == 0:
err = 'Model download: no url provided'
shared.log.error(err)
return err
thread = threading.Thread(target=download_civit_model_thread, args=(model_name, model_url, model_path, model_type, token))
thread.start()
thread.join()
from modules.sd_models import list_models # pylint: disable=W0621
list_models()
+4 -66
View File
@@ -1,7 +1,6 @@
import os
import re
import time
import json
import gradio as gr
from modules.shared import log, opts, req, readfile, max_workers
@@ -58,7 +57,8 @@ def civit_update_metadata():
return html.format(tbody=tbody)
log.debug('CivitAI update metadata: models')
from modules import ui_extra_networks, modelloader
from modules import ui_extra_networks
from modules.civitai.download_civitai import download_civit_meta
pages = ui_extra_networks.get_pages('Model')
if len(pages) == 0:
return 'CivitAI update metadata: no models found'
@@ -75,7 +75,7 @@ def civit_update_metadata():
if r.status_code == 200:
d = r.json()
model.id = d['modelId']
modelloader.download_civit_meta(model.fn, model.id)
download_civit_meta(model.fn, model.id)
fn = os.path.splitext(item['filename'])[0] + '.json'
model.meta = readfile(fn, silent=True)
model.name = model.meta.get('name', model.name)
@@ -158,64 +158,8 @@ def civit_search_model(name, tag, model_type):
return res, gr.update(visible=len(data1) > 0, value=data1 if len(data1) > 0 else []), gr.update(visible=False, value=None), gr.update(visible=False, value=None)
def civit_select1(evt: gr.SelectData, in_data):
model_id = in_data[evt.index[0]][0]
data2 = []
preview_img = None
for model in data:
if model['id'] == model_id:
for d in model['modelVersions']:
try:
if d.get('images') is not None and len(d['images']) > 0 and len(d['images'][0]['url']) > 0:
preview_img = d['images'][0]['url']
data2.append([d.get('id', None), d.get('modelId', None) or model_id, d.get('name', None), d.get('baseModel', None), d.get('createdAt', None) or d.get('publishedAt', None)])
except Exception as e:
log.error(f'CivitAI select: model="{in_data[evt.index[0]]}" {e}')
log.error(f'CivitAI version data={type(d)}: {d}')
log.debug(f'CivitAI select: model="{in_data[evt.index[0]]}" versions={len(data2)}')
return data2, None, preview_img
def civit_select2(evt: gr.SelectData, in_data):
variant_id = in_data[evt.index[0]][0]
model_id = in_data[evt.index[0]][1]
data3 = []
for model in data:
if model['id'] == model_id:
for variant in model['modelVersions']:
if variant['id'] == variant_id:
for f in variant['files']:
try:
if os.path.splitext(f['name'])[1].lower() in ['.safetensors', '.ckpt', '.pt', '.pth', '.bin']:
data3.append([f['name'], round(f['sizeKB']), json.dumps(f['metadata']), f['downloadUrl']])
except Exception:
pass
log.debug(f'CivitAI select: model="{in_data[evt.index[0]]}" files={len(data3)}')
return data3
def civit_select3(evt: gr.SelectData, in_data):
log.debug(f'CivitAI select: variant={in_data[evt.index[0]]}')
return in_data[evt.index[0]][3], in_data[evt.index[0]][0], gr.update(interactive=True)
def civit_download_model(model_url: str, model_name: str, model_path: str, model_type: str, token: str = None):
if model_url is None or len(model_url) == 0:
return 'No model selected'
try:
from modules.modelloader import download_civit_model
res = download_civit_model(model_url, model_name, model_path, model_type, token=token)
except Exception as e:
res = f"CivitAI model downloaded error: model={model_url} {e}"
log.error(res)
return res
from modules.sd_models import list_models # pylint: disable=W0621
list_models()
return res
def atomic_civit_search_metadata(item, results):
from modules.modelloader import download_civit_preview, download_civit_meta
from modules.civitai.download_civitai import download_civit_preview, download_civit_meta
if item is None:
return
try:
@@ -344,9 +288,3 @@ def civit_search_metadata(title: str = None):
t1 = time.time()
log.debug(f'CivitAI search metadata: scanned={scanned} skipped={skipped} time={t1-t0:.2f}')
return create_search_metadata_table(results)
def civitai_update_token(token):
log.debug('CivitAI update token')
opts.civitai_token = token
opts.save()
+1 -182
View File
@@ -1,14 +1,11 @@
import io
import os
import time
import json
import shutil
import importlib
import contextlib
from typing import Dict
from urllib.parse import urlparse
from PIL import Image
import rich.progress as p
import huggingface_hub as hf
from installer import install, log
from modules import shared, errors, files_cache
@@ -48,185 +45,6 @@ def hf_login(token=None):
return True
def save_video_frame(filepath: str):
from modules import video
try:
frames, fps, duration, w, h, codec, frame = video.get_video_params(filepath, capture=True)
except Exception as e:
shared.log.error(f'Video: file={filepath} {e}')
return None
if frame is not None:
basename = os.path.splitext(filepath)
thumb = f'{basename[0]}.thumb.jpg'
shared.log.debug(f'Video: file={filepath} frames={frames} fps={fps} size={w}x{h} codec={codec} duration={duration} thumb={thumb}')
frame.save(thumb)
else:
shared.log.error(f'Video: file={filepath} no frames found')
return frame
def download_civit_meta(model_path: str, model_id):
fn = os.path.splitext(model_path)[0] + '.json'
url = f'https://civitai.com/api/v1/models/{model_id}'
r = shared.req(url)
if r.status_code == 200:
try:
data = r.json()
shared.writefile(data, filename=fn, mode='w', silent=True)
shared.log.info(f'CivitAI download: id={model_id} url={url} file="{fn}"')
return r.status_code, len(data), '' # code/size/note
except Exception as e:
errors.display(e, 'civitai meta')
shared.log.error(f'CivitAI meta: id={model_id} url={url} file="{fn}" {e}')
return r.status_code, '', str(e)
return r.status_code, '', ''
def download_civit_preview(model_path: str, preview_url: str):
global pbar # pylint: disable=global-statement
if model_path is None:
pbar = None
return 500, '', ''
ext = os.path.splitext(preview_url)[1]
preview_file = os.path.splitext(model_path)[0] + ext
is_video = preview_file.lower().endswith('.mp4')
is_json = preview_file.lower().endswith('.json')
if is_json:
shared.log.warning(f'CivitAI download: url="{preview_url}" skip json')
return 500, '', 'exepected preview image got json'
if os.path.exists(preview_file):
return 304, '', 'already exists'
# res = f'CivitAI download: url={preview_url} file="{preview_file}"'
r = shared.req(preview_url, stream=True)
total_size = int(r.headers.get('content-length', 0))
block_size = 16384 # 16KB blocks
written = 0
img = None
shared.state.begin('CivitAI')
if pbar is None:
pbar = p.Progress(p.TextColumn('[cyan]Download'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn(), p.TextColumn('[yellow]{task.description}'), console=shared.console)
try:
with open(preview_file, 'wb') as f:
with pbar:
task = pbar.add_task(description=preview_file, total=total_size)
for data in r.iter_content(block_size):
written = written + len(data)
f.write(data)
pbar.update(task, advance=block_size)
if written < 1024: # min threshold
os.remove(preview_file)
return 400, '', 'removed invalid download'
if is_video:
img = save_video_frame(preview_file)
else:
img = Image.open(preview_file)
except Exception as e:
shared.log.error(f'CivitAI download error: url={preview_url} file="{preview_file}" written={written} {e}')
return 500, '', str(e)
shared.state.end()
if img is None:
return 500, '', 'image is none'
shared.log.info(f'CivitAI download: url={preview_url} file="{preview_file}" size={total_size} image={img.size}')
img.close()
return 200, str(total_size), '' # code/size/note
download_pbar = None
def download_civit_model_thread(model_name: str, model_url: str, model_path: str = "", model_type: str = "Model", token: str = None):
import hashlib
sha256 = hashlib.sha256()
sha256.update(model_url.encode('utf-8'))
temp_file = sha256.hexdigest()[:8] + '.tmp'
headers = {}
starting_pos = 0
if os.path.isfile(temp_file):
starting_pos = os.path.getsize(temp_file)
headers['Range'] = f'bytes={starting_pos}-'
if token is None:
token = shared.opts.civitai_token
if token is not None and len(token) > 0:
headers['Authorization'] = f'Bearer {token}'
r = shared.req(model_url, headers=headers, stream=True)
total_size = int(r.headers.get('content-length', 0))
if model_name is None or len(model_name) == 0:
cn = r.headers.get('content-disposition', '')
model_name = cn.split('filename=')[-1].strip('"')
if model_type == 'LoRA':
model_file = os.path.join(shared.opts.lora_dir, model_path, model_name)
temp_file = os.path.join(shared.opts.lora_dir, model_path, temp_file)
elif model_type == 'Embedding':
model_file = os.path.join(shared.opts.embeddings_dir, model_path, model_name)
temp_file = os.path.join(shared.opts.embeddings_dir, model_path, temp_file)
elif model_type == 'VAE':
model_file = os.path.join(shared.opts.vae_dir, model_path, model_name)
temp_file = os.path.join(shared.opts.vae_dir, model_path, temp_file)
else:
model_file = os.path.join(shared.opts.ckpt_dir, model_path, model_name)
temp_file = os.path.join(shared.opts.ckpt_dir, model_path, temp_file)
res = f'Model download: name="{model_name}" url="{model_url}" path="{model_path}" temp="{temp_file}"'
if os.path.isfile(model_file):
res += ' already exists'
shared.log.warning(res)
return res
res += f' size={round((starting_pos + total_size)/1024/1024, 2)}Mb'
shared.log.info(res)
shared.state.begin('CivitAI')
block_size = 16384 # 16KB blocks
written = starting_pos
global download_pbar # pylint: disable=global-statement
if download_pbar is None:
download_pbar = p.Progress(p.TextColumn('[cyan]{task.description}'), p.DownloadColumn(), p.BarColumn(), p.TaskProgressColumn(), p.TimeRemainingColumn(), p.TimeElapsedColumn(), p.TransferSpeedColumn(), p.TextColumn('[cyan]{task.fields[name]}'), console=shared.console)
with download_pbar:
task = download_pbar.add_task(description="Download starting", total=starting_pos+total_size, name=model_name)
try:
with open(temp_file, 'ab') as f:
for data in r.iter_content(block_size):
if written == 0:
try: # check if response is JSON message instead of bytes
shared.log.error(f'Model download: response={json.loads(data.decode("utf-8"))}')
raise ValueError('response: type=json expected=bytes')
except Exception: # this is good
pass
written = written + len(data)
f.write(data)
download_pbar.update(task, description="Download", completed=written)
if written < 1024: # min threshold
os.remove(temp_file)
raise ValueError(f'removed invalid download: bytes={written}')
except Exception as e:
shared.log.error(f'{res} {e}')
finally:
download_pbar.stop_task(task)
download_pbar.remove_task(task)
if starting_pos+total_size != written:
shared.log.warning(f'{res} written={round(written/1024/1024)}Mb incomplete download')
elif os.path.exists(temp_file):
shared.log.debug(f'Model download complete: temp="{temp_file}" path="{model_file}"')
os.rename(temp_file, model_file)
shared.state.end()
if os.path.exists(model_file):
return model_file
else:
return None
def download_civit_model(model_url: str, model_name: str, model_path: str, model_type: str, token: str = None):
import threading
if model_name is None or len(model_name) == 0:
err = 'Model download: no target model name provided'
shared.log.error(err)
return err
thread = threading.Thread(target=download_civit_model_thread, args=(model_name, model_url, model_path, model_type, token))
thread.start()
return f'Model download: name={model_name} url={model_url} path={model_path}'
def download_diffusers_model(hub_id: str, cache_dir: str = None, download_config: Dict[str, str] = None, token = None, variant = None, revision = None, mirror = None, custom_pipeline = None):
if hub_id is None or len(hub_id) == 0:
return None
@@ -430,6 +248,7 @@ def load_civitai(model: str, url: str):
return name # already downloaded
else:
shared.log.debug(f'Reference download start: model="{name}"')
from modules.civitai.download_civitai import download_civit_model_thread
download_civit_model_thread(model_name=model, model_url=url, model_path='', model_type='safetensors', token=shared.opts.civitai_token)
shared.log.debug(f'Reference download complete: model="{name}"')
sd_models.list_models()
+2 -1
View File
@@ -244,7 +244,8 @@ def get_closet_checkpoint_match(s: str) -> CheckpointInfo:
# civitai search
if shared.opts.sd_checkpoint_autodownload and s.startswith("https://civitai.com/api/download/models"):
fn = modelloader.download_civit_model_thread(model_name=None, model_url=s, model_path='', model_type='Model', token=None)
from modules.civitai.download_civitai import download_civit_model_thread
fn = download_civit_model_thread(model_name=None, model_url=s, model_path='', model_type='Model', token=None)
if fn is not None:
checkpoint_info = CheckpointInfo(fn)
return checkpoint_info
+25 -11
View File
@@ -514,26 +514,43 @@ options_templates.update(options_section(('image-metadata', "Image Metadata"), {
}))
options_templates.update(options_section(('ui', "User Interface"), {
"themes_sep_ui": OptionInfo("<h2>Theme options</h2>", "", gr.HTML),
"theme_type": OptionInfo("Standard", "Theme type", gr.Radio, {"choices": ["Modern", "Standard", "None"]}),
"theme_style": OptionInfo("Auto", "Theme mode", gr.Radio, {"choices": ["Auto", "Dark", "Light"]}),
"gradio_theme": OptionInfo("black-teal", "UI theme", gr.Dropdown, lambda: {"choices": theme.list_themes()}, refresh=theme.refresh_themes),
"ui_locale": OptionInfo("Auto", "UI locale", gr.Dropdown, lambda: {"choices": theme.list_locales()}),
"subpath": OptionInfo("", "Mount URL subpath"),
"quicksetting_sep_images": OptionInfo("<h2>Quicksettings</h2>", "", gr.HTML),
"quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", gr.Dropdown, lambda: {"multiselect":True, "choices": opts.list()}),
"server_sep_ui": OptionInfo("<h2>Startup & Server Options</h2>", "", gr.HTML),
"autolaunch": OptionInfo(False, "Autolaunch browser upon startup"),
"motd": OptionInfo(False, "Show MOTD"),
"subpath": OptionInfo("", "Mount URL subpath"),
"ui_request_timeout": OptionInfo(30000, "UI request timeout", gr.Slider, {"minimum": 1000, "maximum": 120000, "step": 10}),
"cards_sep_ui": OptionInfo("<h2>Card options</h2>", "", gr.HTML),
"extra_networks_card_size": OptionInfo(140, "UI card size (px)", gr.Slider, {"minimum": 20, "maximum": 2000, "step": 1}),
"extra_networks_card_cover": OptionInfo("sidebar", "UI position", gr.Radio, {"choices": ["cover", "inline", "sidebar"]}),
"extra_networks_card_square": OptionInfo(True, "UI disable variable aspect ratio"),
"other_sep_ui": OptionInfo("<h2>Other...</h2>", "", gr.HTML),
"ui_locale": OptionInfo("Auto", "UI locale", gr.Dropdown, lambda: {"choices": theme.list_locales()}),
"font_size": OptionInfo(14, "Font size", gr.Slider, {"minimum": 8, "maximum": 32, "step": 1}),
"aspect_ratios": OptionInfo("1:1, 4:3, 3:2, 16:9, 16:10, 21:9, 2:3, 3:4, 9:16, 10:16, 9:21", "Allowed aspect ratios"),
"logmonitor_show": OptionInfo(True, "Show log view"),
"logmonitor_refresh_period": OptionInfo(5000, "Log view update period", gr.Slider, {"minimum": 0, "maximum": 30000, "step": 25}),
"ui_request_timeout": OptionInfo(30000, "UI request timeout", gr.Slider, {"minimum": 1000, "maximum": 120000, "step": 10}),
"motd": OptionInfo(False, "Show MOTD"),
"compact_view": OptionInfo(False, "Compact view"),
"ui_columns": OptionInfo(4, "Gallery view columns", gr.Slider, {"minimum": 1, "maximum": 8, "step": 1}),
"images_sep_log": OptionInfo("<h2>Log Display</h2>", "", gr.HTML),
"logmonitor_show": OptionInfo(True, "Show log view"),
"logmonitor_refresh_period": OptionInfo(5000, "Log view update period", gr.Slider, {"minimum": 0, "maximum": 30000, "step": 25}),
"images_sep_ui": OptionInfo("<h2>Outputs & Images</h2>", "", gr.HTML),
"return_grid": OptionInfo(True, "Show grid in results"),
"return_mask": OptionInfo(False, "Inpainting include greyscale mask in results"),
"return_mask_composite": OptionInfo(False, "Inpainting include masked composite in results"),
"send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface", gr.Checkbox, {"visible": False}),
"send_size": OptionInfo(False, "Send size when sending prompt or image to another interface", gr.Checkbox, {"visible": False}),
"quicksettings_list": OptionInfo(["sd_model_checkpoint"], "Quicksettings list", gr.Dropdown, lambda: {"multiselect":True, "choices": opts.list()}),
}))
options_templates.update(options_section(('live-preview', "Live Previews"), {
@@ -642,11 +659,8 @@ options_templates.update(options_section(('extra_networks', "Networks"), {
"extra_networks": OptionInfo(["All"], "Available networks", gr.Dropdown, lambda: {"multiselect":True, "choices": ['All'] + [en.title for en in extra_networks]}),
"extra_networks_sort": OptionInfo("Default", "Sort order", gr.Dropdown, {"choices": ['Default', 'Name [A-Z]', 'Name [Z-A]', 'Date [Newest]', 'Date [Oldest]', 'Size [Largest]', 'Size [Smallest]']}),
"extra_networks_view": OptionInfo("gallery", "UI view", gr.Radio, {"choices": ["gallery", "list"]}),
"extra_networks_card_cover": OptionInfo("sidebar", "UI position", gr.Radio, {"choices": ["cover", "inline", "sidebar"]}),
"extra_networks_height": OptionInfo(0, "UI height (%)", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), # set in ui_javascript
"extra_networks_sidebar_width": OptionInfo(35, "UI sidebar width (%)", gr.Slider, {"minimum": 10, "maximum": 80, "step": 1}),
"extra_networks_card_size": OptionInfo(140, "UI card size (px)", gr.Slider, {"minimum": 20, "maximum": 2000, "step": 1}),
"extra_networks_card_square": OptionInfo(True, "UI disable variable aspect ratio"),
"extra_networks_height": OptionInfo(0, "UI height (%)", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), # set in ui_javascript
"extra_networks_fetch": OptionInfo(True, "UI fetch network info on mouse-over"),
"extra_network_skip_indexing": OptionInfo(False, "Build info on first access", gr.Checkbox),
+25 -64
View File
@@ -475,13 +475,28 @@ def create_ui():
html = create_model_cards(results)
return html
def civitai_update_token(token):
log.debug('CivitAI update token')
opts.civitai_token = token
opts.save()
def civitai_download(model_url, model_name, model_type, model_path, civit_token, model_output):
from modules.civitai.download_civitai import download_civit_model
msg = f"<h4>Initiating download</h4><div>{model_name} | {model_type} | <a href='{model_url}'>{model_url}</a></div><br>"
yield msg + model_output
download_civit_model(model_url, model_name, model_path, model_type, civit_token)
yield model_output
with gr.Row():
gr.HTML('<h2>Search & Download</h2>')
with gr.Row(elem_id='civitai_search_row'):
civit_search_text = gr.Textbox(label='', placeholder='keyword', elem_id="civit_search_text")
civit_search_tag = gr.Textbox(label='', placeholder='tag', elem_id="civit_search_text")
civit_search_text_btn = ToolButton(value=ui_symbols.search, interactive=True)
with gr.Accordion(label='Search options', open=False, elem_id="civitai_search_options"):
with gr.Accordion(label='Advanced', open=False, elem_id="civitai_search_options"):
civit_download_btn = gr.Button(value="Download model", variant='primary', elem_id="civitai_download_btn", visible=False)
with gr.Row():
civit_token = gr.Textbox(opts.civitai_token, label='CivitAI token', placeholder='optional access token for private or gated models')
with gr.Row():
civit_nsfw = gr.Checkbox(label='NSFW allowed', value=True)
with gr.Row():
@@ -489,71 +504,17 @@ def create_ui():
with gr.Row():
civit_base = gr.Textbox(label='Base model', placeholder='SDXL, ...')
with gr.Row():
civit_token = gr.Textbox(opts.civitai_token, label='CivitAI token', placeholder='optional access token for private or gated models')
civit_folder = gr.Textbox(label='Download folder', placeholder='optional folder for downloads')
with gr.Row():
civitai_models_output = gr.HTML('', elem_id="civitai_models_output")
# sort, period, limit
_dummy = gr.Label(visible=False) # dummy component to get argspec later
civit_inputs = [civit_search_text, civit_search_tag, civit_nsfw, civit_type, civit_base, civit_token]
civit_search_text_btn.click(fn=civitai_search, inputs=civit_inputs, outputs=[models_outcome])
civit_search_text.submit(fn=civitai_search, inputs=civit_inputs, outputs=[models_outcome])
civit_search_tag.submit(fn=civitai_search, inputs=civit_inputs, outputs=[models_outcome])
"""
from modules.civitai.legacy_civitai import civitai_update_token, civit_search_model, civit_search_metadata, civit_select1, civit_select2, civit_select3, civit_download_model
with gr.Row():
gr.HTML('<h2>Search for models</h2>')
with gr.Row():
with gr.Column(scale=1):
civit_model_type = gr.Dropdown(label='CivitAI model type', choices=['Model', 'LoRA', 'Embedding', 'VAE', 'Other'], value='Model')
with gr.Column(scale=15):
with gr.Row():
civit_search_text = gr.Textbox('', label='Search models', placeholder='keyword')
civit_search_tag = gr.Textbox('', label='', placeholder='tags')
civit_search_btn = ToolButton(value=ui_symbols.search, interactive=True)
with gr.Row():
civit_search_res = gr.HTML('')
with gr.Row():
gr.HTML('<h2>&nbspCivitAI download model<br></h2>')
with gr.Row():
civit_download_model_btn = gr.Button(value="Download", variant='primary')
gr.HTML('<span style="line-height: 2em">Select a model, model version and and model variant from the search results to download or enter model URL manually</span><br>')
with gr.Row():
civit_token = gr.Textbox(opts.civitai_token, label='CivitAI token', placeholder='optional access token for private or gated models')
civit_token.change(fn=civitai_update_token, inputs=[civit_token], outputs=[])
with gr.Row():
civit_name = gr.Textbox('', label='Model name', placeholder='select model from search results', visible=True)
civit_selected = gr.Textbox('', label='Model URL', placeholder='select model from search results', visible=True)
civit_path = gr.Textbox('', label='Download path', placeholder='optional subfolder path where to save model', visible=True)
with gr.Row():
gr.HTML('<h2>Search results</h2>')
with gr.Row():
civit_headers1 = ['ID', 'Name', 'Tags', 'Downloads', 'Rating']
civit_types1 = ['number', 'str', 'str', 'number', 'number']
civit_results1 = gr.DataFrame(value=None, label=None, show_label=False, interactive=False, wrap=True, headers=civit_headers1, datatype=civit_types1, type='array', visible=False)
with gr.Row():
with gr.Column():
civit_headers2 = ['ID', 'ModelID', 'Name', 'Base', 'Created', 'Preview']
civit_types2 = ['number', 'number', 'str', 'str', 'date', 'str']
civit_results2 = gr.DataFrame(value=None, label='Model versions', show_label=True, interactive=False, wrap=True, headers=civit_headers2, datatype=civit_types2, type='array', visible=False)
with gr.Column():
civit_headers3 = ['Name', 'Size', 'Metadata', 'URL']
civit_types3 = ['str', 'number', 'str', 'str']
civit_results3 = gr.DataFrame(value=None, label='Model variants', show_label=True, interactive=False, wrap=True, headers=civit_headers3, datatype=civit_types3, type='array', visible=False)
def is_visible(component):
visible = len(component) > 0 if component is not None else False
return gr.update(visible=visible)
civit_search_text.submit(fn=civit_search_model, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_search_res, civit_results1, civit_results2, civit_results3])
civit_search_tag.submit(fn=civit_search_model, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_search_res, civit_results1, civit_results2, civit_results3])
civit_search_btn.click(fn=civit_search_model, inputs=[civit_search_text, civit_search_tag, civit_model_type], outputs=[civit_search_res, civit_results1, civit_results2, civit_results3])
civit_results1.select(fn=civit_select1, inputs=[civit_results1], outputs=[civit_results2, civit_results3, models_image])
civit_results2.select(fn=civit_select2, inputs=[civit_results2], outputs=[civit_results3])
civit_results3.select(fn=civit_select3, inputs=[civit_results3], outputs=[civit_selected, civit_name, civit_search_btn])
civit_results1.change(fn=is_visible, inputs=[civit_results1], outputs=[civit_results1])
civit_results2.change(fn=is_visible, inputs=[civit_results2], outputs=[civit_results2])
civit_results3.change(fn=is_visible, inputs=[civit_results3], outputs=[civit_results3])
civit_download_model_btn.click(fn=civit_download_model, inputs=[civit_selected, civit_name, civit_path, civit_model_type, civit_token], outputs=[models_outcome])
"""
civit_search_text_btn.click(fn=civitai_search, inputs=civit_inputs, outputs=[civitai_models_output])
civit_search_text.submit(fn=civitai_search, inputs=civit_inputs, outputs=[civitai_models_output])
civit_search_tag.submit(fn=civitai_search, inputs=civit_inputs, outputs=[civitai_models_output])
civit_token.change(fn=civitai_update_token, inputs=[civit_token], outputs=[])
civit_download_btn.click(fn=civitai_download, _js="downloadCivitModel", inputs=[_dummy, _dummy, _dummy, civit_folder, civit_token, civitai_models_output], outputs=[civitai_models_output])
with gr.Tab(label="Huggingface", elem_id="models_huggingface_tab"):
from modules.models_hf import hf_search, hf_select, hf_download_model, hf_update_token