diff --git a/javascript/script.js b/javascript/script.js index 9d55aa45d..f462642e1 100644 --- a/javascript/script.js +++ b/javascript/script.js @@ -193,7 +193,7 @@ function getSortableCellValue(cell, sortType) { return rawValue.toLowerCase(); } -function sortModelListTable(table, columnIndex, sortType, sortOrder) { +function sortTable(table, columnIndex, sortType, sortOrder) { const tbody = table.querySelector('tbody'); if (!tbody) return; const rows = Array.from(tbody.querySelectorAll('tr')); @@ -222,38 +222,33 @@ function applySortIndicators(table, activeHeader, sortOrder) { activeHeader.setAttribute('aria-sort', sortOrder === 'desc' ? 'descending' : 'ascending'); } +function handleSortableTableClick(event) { + const header = event.target.closest('th.sortable'); + if (!header) return; + const table = header.closest('table[data-sortable="true"]'); + if (!table) return; + const headers = Array.from(table.querySelectorAll('th.sortable')); + const columnIndex = headers.indexOf(header); + if (columnIndex < 0) return; + + const currentSortKey = table.dataset.sortKey || table.dataset.defaultSortKey; + const currentSortOrder = table.dataset.sortOrder || table.dataset.defaultSortOrder || 'asc'; + const isCurrentHeader = currentSortKey === header.dataset.sortKey; + const nextOrder = isCurrentHeader && currentSortOrder === 'asc' ? 'desc' : 'asc'; + + table.dataset.sortKey = header.dataset.sortKey; + table.dataset.sortOrder = nextOrder; + sortTable(table, columnIndex, header.dataset.sortType || 'text', nextOrder); + applySortIndicators(table, header, nextOrder); +} + async function initTableSorter() { const t0 = performance.now(); const root = gradioApp(); - for (const table of root.querySelectorAll('table[data-sortable="true"]')) { - console.log('HERE', table); - if (!table || table.dataset.sortBound === 'true') return; - const headers = Array.from(table.querySelectorAll('th.sortable')); - if (headers.length === 0) return; - - for (const [index, header] of headers.entries()) { - header.style.cursor = 'pointer'; - header.addEventListener('click', () => { - const isCurrentHeader = table.dataset.sortKey === header.dataset.sortKey; - const nextOrder = isCurrentHeader && table.dataset.sortOrder === 'asc' ? 'desc' : 'asc'; - table.dataset.sortKey = header.dataset.sortKey; - table.dataset.sortOrder = nextOrder; - sortModelListTable(table, index, header.dataset.sortType || 'text', nextOrder); - applySortIndicators(table, header, nextOrder); - }); - } - - const defaultSortKey = table.dataset.defaultSortKey || 'name'; - const defaultSortOrder = table.dataset.defaultSortOrder || 'asc'; - const defaultHeader = headers.find((header) => header.dataset.sortKey === defaultSortKey) || headers[0]; - const defaultIndex = headers.indexOf(defaultHeader); - table.dataset.sortKey = defaultHeader.dataset.sortKey; - table.dataset.sortOrder = defaultSortOrder; - sortModelListTable(table, defaultIndex, defaultHeader.dataset.sortType || 'text', defaultSortOrder); - applySortIndicators(table, defaultHeader, defaultSortOrder); - table.dataset.sortBound = 'true'; + if (!root.dataset.tableSorterBound) { + root.addEventListener('click', handleSortableTableClick); + root.dataset.tableSorterBound = 'true'; } - onUiUpdate(initTableSorter); const t1 = performance.now(); log('initTableSorter', Math.round(t1 - t0)); timer('initTableSorter', t1 - t0); @@ -261,7 +256,7 @@ async function initTableSorter() { async function deleteFile(filename) { if (!filename) return; - if (!confirm(`Are you sure you want to delete the object? This action cannot be undone. ${filename}`)) return; // eslint-disable-line no-alert + if (!confirm(`Are you sure you want to delete the object - This action cannot be undone? Object: ${filename}`)) return; // eslint-disable-line no-alert const res = await authFetch(`${window.api}/delete-file?file=${encodeURIComponent(filename)}`); if (!res || res.status !== 200) { error('FileDelete', { file: filename, status: res?.status, statusText: res?.statusText }); diff --git a/modules/api/endpoints.py b/modules/api/endpoints.py index 88bb14f24..eec9f3666 100644 --- a/modules/api/endpoints.py +++ b/modules/api/endpoints.py @@ -344,7 +344,7 @@ def get_deletefile(file: str): import os from pathlib import Path allowed_dirs = shared.demo.allowed_paths - if not file.strip(): + if file is None or len(file.strip()) == 0: raise HTTPException(status_code=400, detail="file path is required") if not any(Path(folder).absolute() in Path(file).absolute().parents for folder in allowed_dirs): raise HTTPException(status_code=403, detail=f"file {file}: must be in one of allowed directories") @@ -360,13 +360,14 @@ def get_deletefile(file: str): os.remove(file) return {"deleted": f"{file}"} except Exception as e: + log.error(f'Delete: file="{file}" error: {e}') raise HTTPException(status_code=500, detail=f"error deleting file {file}: {str(e)}") from e def get_deleteimage(file: str): import os from pathlib import Path allowed_dirs = shared.demo.allowed_paths - if not file.strip(): + if file is None or len(file.strip()) == 0: raise HTTPException(status_code=400, detail="file path is required") if not any(Path(folder).absolute() in Path(file).absolute().parents for folder in allowed_dirs): raise HTTPException(status_code=403, detail=f"file {file}: must be in one of allowed directories") @@ -381,6 +382,7 @@ def get_deleteimage(file: str): log.warning(f'Delete: image="{file}"') return {"deleted": f"{file}"} except Exception as e: + log.error(f'Delete: file="{file}" error: {e}') raise HTTPException(status_code=500, detail=f"error deleting file {file}: {str(e)}") from e def get_pnginfo(file: str): diff --git a/modules/sd_checkpoint.py b/modules/sd_checkpoint.py index ce702888d..d840cba9a 100644 --- a/modules/sd_checkpoint.py +++ b/modules/sd_checkpoint.py @@ -24,7 +24,7 @@ warn_once = False class CheckpointInfo: - def __init__(self, filename, name=None, sha=None, subfolder=None, model_type: str = 'checkpoint'): + def __init__(self, filename, name=None, sha=None, subfolder=None, model_type: str = 'checkpoint', folder: str|None = None): self.name = name self.hash = sha self.filename = filename @@ -88,7 +88,7 @@ class CheckpointInfo: self.shorthash = self.sha256[0:10] if self.sha256 else None self.title = self.name if self.shorthash is None else f'{self.name} [{self.shorthash}]' - self.path = self.filename + self.path = folder or self.filename self.model_name = os.path.basename(self.name) self.metadata = read_metadata_from_safetensors(filename) # log.debug(f'Checkpoint: type={self.type} name={self.name} filename={self.filename} hash={self.shorthash} title={self.title}') @@ -146,7 +146,7 @@ def list_models(): checkpoint_info.register() diffusers_list = [] for repo in modelloader.load_diffusers_models(clear=True): - checkpoint_info = CheckpointInfo(repo['name'], sha=repo['hash']) + checkpoint_info = CheckpointInfo(repo['name'], sha=repo['hash'], folder=repo['folder']) diffusers_list.append(checkpoint_info) if checkpoint_info.name is not None: checkpoint_info.register() diff --git a/modules/sd_models_utils.py b/modules/sd_models_utils.py index e6f5d10e8..f45c58c82 100644 --- a/modules/sd_models_utils.py +++ b/modules/sd_models_utils.py @@ -73,6 +73,18 @@ def path_to_repo(checkpoint_info): return repo_id +def repo_to_path(repo_id): + if repo_id.name.startswith('Diffusers'): + folder = repo_id[len('Diffusers'):].lstrip('/') + folder = 'models--' + repo_id.replace('/', '--') + folder = os.path.join(shared.opts.diffusers_dir, repo_id) + if os.path.exists(folder): + return folder + if os.path.exists(repo_id.filename): + return repo_id.filename + return '' + + def convert_to_faketensors(tensor): try: fake_module = torch._subclasses.fake_tensor.FakeTensorMode(allow_non_fake_inputs=True) # pylint: disable=protected-access diff --git a/modules/ui_models.py b/modules/ui_models.py index 5d216c5c7..57f621154 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -15,10 +15,10 @@ extra_ui = [] def get_folder_size(folder): total_size = 0 - for dirpath, _dirnames, filenames in os.walk(folder): + for dirpath, _dirnames, filenames in os.walk(folder, followlinks=False): for f in filenames: fp = os.path.join(dirpath, f) - if os.path.isfile(fp): + if not os.path.islink(fp) and os.path.isfile(fp): total_size += os.path.getsize(fp) return round(total_size / 1024 / 1024 / 1024, 3) @@ -34,11 +34,12 @@ def update_model_hashes(): def create_models_table(rows: list = []): from modules import sd_detect + rows = sorted(rows, key=lambda row: str(getattr(row, 'model_name', '')).lower()) html = """ - +
- + @@ -88,7 +89,7 @@ def create_models_table(rows: list = []): - + """ except Exception as e: @@ -177,11 +178,21 @@ def create_ui(): model_list_btn = gr.Button(value="Refresh list", variant='primary') model_checkhash_btn = gr.Button(value="Calculate hashes", variant='secondary') with gr.Row(): - model_table = gr.HTML(value=create_models_table(), elem_id="model_list_table") + model_table = gr.HTML(value=create_models_table(), elem_id="model_list_table", elem_classes="scroll-auto") model_checkhash_btn.click(fn=update_model_hashes, inputs=[], outputs=[model_table]) model_list_btn.click(fn=lambda: create_models_table(list(sd_models.checkpoints_list.values())), inputs=[], outputs=[model_table]) + with gr.Tab(label="Cache List", elem_id="models_cache_tab"): + with gr.Row(): + gr.HTML('

List models in Hugging Face cache


') + with gr.Row(): + model_cache_btn = gr.Button(value="Refresh list", variant='primary') + with gr.Row(): + cache_table = gr.HTML(value=create_models_table(), elem_id="model_cache_table", elem_classes="scroll-auto") + + model_cache_btn.click(fn=lambda: create_models_table(sd_models.list_hfcache()), inputs=[], outputs=[cache_table]) + with gr.Tab(label="Metadata", elem_id="models_metadata_tab"): from modules.civitai.metadata_civitai import civit_search_metadata, civit_update_metadata with gr.Row():
NameName Family Type Pipeline{size:.3f} GB {stat_mtime} {hash_name}\uf530\uf530