mirror of
https://github.com/vladmandic/automatic
synced 2026-09-20 01:31:13 +02:00
add storage analyzer
Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
+8
-2
@@ -1,6 +1,6 @@
|
||||
# Change Log for SD.Next
|
||||
|
||||
## Update for 2026-07-31
|
||||
## Update for 2026-08-02
|
||||
|
||||
- **Models**
|
||||
- [SeFi-Image](https://huggingface.co/SeFi-Image/SeFi-Image-5B-RL) in *Base* and *Turbo* (distilled) variants
|
||||
@@ -11,11 +11,17 @@
|
||||
Mage-Flow is a 4B-scale generative stack for efficient text-to-image generation and instruction-based image editing
|
||||
*note*: Microsoft released and then unpublished the model, but we still have a mirror available for download
|
||||
- **Features**
|
||||
- video: support for scripts/extensions
|
||||
- storage analyzer: new feature that analyzes your used storage by sdnext per type and location
|
||||
*system -> storage*
|
||||
- video: support for scripts/extensions
|
||||
video processing now supports scripts and extensions (if they support video processing)
|
||||
*example*: use nudenet to automatically censor video frames :)
|
||||
- prompt enhance: support for video generation
|
||||
- startup: optimized server startup
|
||||
- process: preserve audio when processing video
|
||||
- remove background: new [lucida](https://huggingface.co/egeorcun/lucida) model
|
||||
- **API**
|
||||
- add `/sdapi/v1/storage` endpoint to return storage usage info
|
||||
- **Fixes**
|
||||
- seedvr quality
|
||||
- skip-all do not skip env init
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
- Control tab verify overrides handling, @vladmandic
|
||||
- Cloud providers, @CalamitousFelicitousness
|
||||
- Video processing add/verify full API support, @CalamitousFelicitousness
|
||||
- Storage analyzer, @vladmandic
|
||||
- Lora: new handler, @CalamitousFelicitousness
|
||||
- Processing -> Video capabilities, @vladmandic
|
||||
- `RIFE` in processing
|
||||
|
||||
Submodule extensions-builtin/sdnext-modernui updated: dc4ac41a5f...20f032f601
+2
-1
@@ -51,7 +51,6 @@ class Api:
|
||||
self.add_api_route("/sdapi/v1/status", server.get_status, methods=["GET"], response_model=models.ResStatus, tags=["Server"])
|
||||
self.add_api_route("/sdapi/v1/platform", server.get_platform, methods=["GET"], tags=["Server"])
|
||||
self.add_api_route("/sdapi/v1/progress", server.get_progress, methods=["GET"], response_model=models.ResProgress, tags=["Server"])
|
||||
self.add_api_route("/sdapi/v1/history", server.get_history, methods=["GET"], response_model=list[models.ResHistory], tags=["Server"])
|
||||
self.add_api_route("/sdapi/v1/interrupt", server.post_interrupt, methods=["POST"], status_code=204, tags=["Server"])
|
||||
self.add_api_route("/sdapi/v1/skip", server.post_skip, methods=["POST"], status_code=204, tags=["Server"])
|
||||
self.add_api_route("/sdapi/v1/shutdown", server.post_shutdown, methods=["POST"], status_code=204, tags=["Server"])
|
||||
@@ -60,6 +59,8 @@ class Api:
|
||||
self.add_api_route("/sdapi/v1/cmd-flags", server.get_cmd_flags, methods=["GET"], response_model=models.FlagsModel, tags=["Server"])
|
||||
self.add_api_route("/sdapi/v1/gpu", gpu.get_gpu, methods=["GET"], tags=["Server"], response_model=list[dict])
|
||||
self.add_api_route("/sdapi/v1/gpu-smi", gpu.get_gpu_smi, methods=["GET"], response_model=list[models.ResGPU], tags=["Server"])
|
||||
self.add_api_route("/sdapi/v1/history", server.get_history, methods=["GET"], response_model=list[models.ResHistory], tags=["Server"])
|
||||
self.add_api_route("/sdapi/v1/storage", server.get_storage, methods=["GET"], response_model=list[models.ResStorage], tags=["Server"])
|
||||
|
||||
# core api using locking
|
||||
self.add_api_route("/sdapi/v1/txt2img", self.generate.post_text2img, methods=["POST"], response_model=models.ResTxt2Img, tags=["Generation"])
|
||||
|
||||
+17
-1
@@ -443,7 +443,6 @@ class ReqGetLog(BaseModel):
|
||||
lines: int = Field(default=100, title="Lines", description="How many lines to return")
|
||||
clear: bool = Field(default=False, title="Clear", description="Should the log be cleared after returning the lines?")
|
||||
|
||||
|
||||
class ReqPostLog(BaseModel):
|
||||
json: dict | None = Field(default=None, title="Data", description="The data to log")
|
||||
message: str | None = Field(default=None, title="Message", description="The info message to log")
|
||||
@@ -453,6 +452,10 @@ class ReqPostLog(BaseModel):
|
||||
class ReqHistory(BaseModel):
|
||||
id: int | str | None = Field(default=None, title="Task ID", description="Task ID")
|
||||
|
||||
class ReqStorage(BaseModel):
|
||||
folder: str | None = Field(default=None, title="Folder", description="Storage folder(s)")
|
||||
types: str | None = Field(default=None, title="Types", description="Storage types to filter by")
|
||||
|
||||
class ReqProgress(BaseModel):
|
||||
skip_current_image: bool = Field(default=False, title="Skip current image", description="Skip current image serialization")
|
||||
|
||||
@@ -472,6 +475,19 @@ class ResHistory(BaseModel):
|
||||
duration: float | None = Field(default=None, title="Duration", description="Job duration")
|
||||
outputs: list[str] = Field(title="Outputs", description="List of filenames")
|
||||
|
||||
class ResStorage(BaseModel):
|
||||
name: str = Field(title="Name", description="Storage location name")
|
||||
type: str = Field(title="Type", description="Storage location type")
|
||||
folders: list[str] = Field(title="Folders", description="List of folders in the storage location")
|
||||
paths: list[str] = Field(title="Paths", description="List of resolved paths in the storage location")
|
||||
size: int = Field(title="Size", description="Total size of the storage location in bytes")
|
||||
mtime: float = Field(title="Last modified", description="Last modified timestamp of the storage location")
|
||||
nfiles: int = Field(title="Files", description="Number files in the storage location")
|
||||
nfolders: int = Field(title="Folders", description="Number of folders in the storage location")
|
||||
nsymlinks: int = Field(title="Symlinks", description="Number of symbolic links in the storage location")
|
||||
nerrors: int = Field(title="Errors", description="Number of errors in the storage location")
|
||||
time: float = Field(title="Time", description="Time taken to scan the storage location in seconds")
|
||||
|
||||
class ResStatus(BaseModel):
|
||||
status: str = Field(title="Status", description="Current status")
|
||||
task: str = Field(title="Task", description="Current job")
|
||||
|
||||
@@ -111,6 +111,15 @@ def get_history(req: models.ReqHistory = Depends()):
|
||||
res = [models.ResHistory(**item) for item in res]
|
||||
return res
|
||||
|
||||
def get_storage(req: models.ReqStorage = Depends()):
|
||||
from modules.storage import check_storage
|
||||
res = check_storage(folders=req.folder,
|
||||
types=req.types.split(',') if req.types else None,
|
||||
silent=True,
|
||||
)
|
||||
res = [models.ResStorage(**loc.dict()) for loc in res]
|
||||
return res
|
||||
|
||||
def get_progress(req: models.ReqProgress = Depends()):
|
||||
if shared.state.job_count == 0 and shared.state.sampling_step == 0: # truly idle
|
||||
return models.ResProgress(id=shared.state.id, progress=0, eta_relative=0, state=shared.state.dict(), textinfo=shared.state.textinfo)
|
||||
|
||||
@@ -407,9 +407,9 @@ def cleanup_models():
|
||||
|
||||
def move_files(src_path: str, dest_path: str, ext_filter: str | None = None):
|
||||
try:
|
||||
if not os.path.exists(dest_path):
|
||||
os.makedirs(dest_path)
|
||||
if os.path.exists(src_path):
|
||||
if not os.path.exists(dest_path):
|
||||
os.makedirs(dest_path)
|
||||
for file in os.listdir(src_path):
|
||||
fullpath = os.path.join(src_path, file)
|
||||
if os.path.isfile(fullpath):
|
||||
|
||||
+27
-9
@@ -13,43 +13,61 @@ def walk(folder: str):
|
||||
return files
|
||||
|
||||
|
||||
def stat(folder: str) -> tuple[int, datetime]:
|
||||
def stat(folder: str, follow: bool = False, extended: bool = False, exclude: list[str] = []):
|
||||
_files = 0
|
||||
_folders = 0
|
||||
_symlinks = 0
|
||||
_errors = 0
|
||||
_size = 0
|
||||
_mtime = 0.0
|
||||
|
||||
def recurse(folder: str):
|
||||
nonlocal _size, _mtime
|
||||
nonlocal _size, _mtime, _files, _folders, _symlinks, _errors
|
||||
with os.scandir(folder) as entries:
|
||||
for entry in entries:
|
||||
try:
|
||||
if entry.is_file(follow_symlinks=False):
|
||||
if any(part == ex for part in entry.path.split(os.sep) for ex in exclude):
|
||||
continue
|
||||
if entry.is_file(follow_symlinks=follow):
|
||||
try:
|
||||
_stat = entry.stat(follow_symlinks=False)
|
||||
_stat = entry.stat(follow_symlinks=follow)
|
||||
except Exception:
|
||||
_stat = os.stat(entry.path, follow_symlinks=False)
|
||||
_stat = os.stat(entry.path, follow_symlinks=follow)
|
||||
_size += _stat.st_size
|
||||
_files += 1
|
||||
if _stat.st_mtime > _mtime:
|
||||
_mtime = _stat.st_mtime
|
||||
elif entry.is_dir(follow_symlinks=False):
|
||||
elif entry.is_symlink():
|
||||
_symlinks += 1
|
||||
elif entry.is_dir(follow_symlinks=follow):
|
||||
_folders += 1
|
||||
recurse(entry.path)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
_errors += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
if os.path.isfile(folder):
|
||||
_stat = os.stat(folder, follow_symlinks=False)
|
||||
s_folder = str(folder)
|
||||
if any(s_folder in ex for ex in exclude):
|
||||
return _size, datetime.fromtimestamp(_mtime).replace(microsecond=0), _files, _folders, _symlinks, _errors
|
||||
elif os.path.isfile(folder):
|
||||
_stat = os.stat(folder, follow_symlinks=follow)
|
||||
_size = _stat.st_size
|
||||
_mtime = _stat.st_mtime
|
||||
_files = 1
|
||||
elif os.path.isdir(folder):
|
||||
_folders = 1
|
||||
recurse(folder)
|
||||
else:
|
||||
pass
|
||||
except (FileNotFoundError, PermissionError):
|
||||
pass
|
||||
_errors += 1
|
||||
try:
|
||||
_datetime = datetime.fromtimestamp(_mtime).replace(microsecond=0)
|
||||
except (OSError, ValueError):
|
||||
_datetime = datetime.fromtimestamp(0)
|
||||
if extended:
|
||||
return _size, _datetime, _files, _folders, _symlinks, _errors
|
||||
return _size, _datetime
|
||||
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ def path_to_repo(checkpoint_info: CheckpointInfo | str):
|
||||
for opt in [shared.opts.ckpt_dir, shared.opts.diffusers_dir, shared.opts.hfcache_dir]:
|
||||
remove_prefix.append(opt.replace('\\', '/'))
|
||||
try:
|
||||
relative = os.path.relpath(opt, start=shared.opts.models_dir).replace('\\', '/')
|
||||
relative = os.path.relpath(opt, start=shared.models_path).replace('\\', '/')
|
||||
if not relative.startswith('.'):
|
||||
remove_prefix.append(relative)
|
||||
except Exception:
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from modules import paths
|
||||
from modules.logger import log
|
||||
from modules.shared import opts, max_workers
|
||||
from modules.modelstats import stat
|
||||
|
||||
|
||||
class Location:
|
||||
name: str
|
||||
folders: list[str]
|
||||
paths: list[Path]
|
||||
nfiles: int = 0
|
||||
nfolders: int = 0
|
||||
nsymlinks: int = 0
|
||||
nerrors: int = 0
|
||||
type: str = ''
|
||||
size: int = 0
|
||||
time: float = 0.0
|
||||
mtime: datetime = datetime.fromtimestamp(0)
|
||||
|
||||
def __init__(self, name: str | None, folders: str | list[str], what: str = ''):
|
||||
self.type = what
|
||||
if isinstance(folders, str):
|
||||
folders = [folders]
|
||||
self.name = name if name is not None else ', '.join(folders)
|
||||
self.folders = folders
|
||||
self.paths = [Path(f).resolve(strict=False) for f in self.folders if f is not None and f != '']
|
||||
|
||||
def __repr__(self):
|
||||
return f'Location(type={self.type} name="{self.name}" folders={self.folders} size={self.size/1024/1024:.3f} mtime="{self.mtime}" files={self.nfiles} folders={self.nfolders} symlinks={self.nsymlinks} errors={self.nerrors} time={self.time:.3f})'
|
||||
|
||||
def dict(self):
|
||||
return {
|
||||
'name': self.name,
|
||||
'type': self.type,
|
||||
'folders': self.folders,
|
||||
'paths': [str(p) for p in self.paths],
|
||||
'size': self.size,
|
||||
'mtime': self.mtime.timestamp(),
|
||||
'nfiles': self.nfiles,
|
||||
'nfolders': self.nfolders,
|
||||
'nsymlinks': self.nsymlinks,
|
||||
'nerrors': self.nerrors,
|
||||
'time': self.time,
|
||||
}
|
||||
|
||||
def get_all_locations(types: list[str] | None = []) -> list[Location]:
|
||||
locations = []
|
||||
if types is None or 'All' in types or 'Models' in types:
|
||||
locations.append(Location('SD Models', opts.ckpt_dir, 'Models'))
|
||||
locations.append(Location('Diffusers Models', opts.diffusers_dir, 'Models'))
|
||||
locations.append(Location('Huggingface Modules', opts.hfcache_dir, 'Models'))
|
||||
locations.append(Location('VAE', [opts.vae_dir, os.path.join(paths.models_path, "TAESD")], 'Models'))
|
||||
locations.append(Location('UNet', opts.unet_dir, 'Models'))
|
||||
locations.append(Location('TextEncoder', opts.te_dir, 'Models'))
|
||||
locations.append(Location('LoRA', opts.lora_dir, 'Models'))
|
||||
locations.append(Location('ControlNets', opts.control_dir, 'Models'))
|
||||
locations.append(Location('Embeddings', opts.embeddings_dir, 'Models'))
|
||||
locations.append(Location('Detailers', [opts.yolo_dir, os.path.join(paths.models_path, 'Ultralytics')], 'Models'))
|
||||
locations.append(Location('Upscalers', [opts.esrgan_models_path, opts.bsrgan_models_path, opts.realesrgan_models_path, opts.scunet_models_path, opts.swinir_models_path, os.path.join(paths.models_path, 'chaiNNer'), os.path.join(paths.models_path, 'GFPGAN'), os.path.join(paths.models_path, 'Spandrel'), os.path.join(paths.models_path, 'SeedVR2')], 'Models')) # chainners extension has late opts init
|
||||
locations.append(Location('CLiP', opts.clip_models_path, 'Models'))
|
||||
locations.append(Location('Rembg', os.path.join(paths.models_path, 'Rembg'), 'Models'))
|
||||
locations.append(Location('RIFE', os.path.join(paths.models_path, 'RIFE'), 'Models'))
|
||||
if types is None or 'All' in types or 'Data' in types:
|
||||
locations.append(Location('Configs', ['data', paths.sd_configs_path], 'Data'))
|
||||
locations.append(Location('AutoComplete', opts.autocomplete_dir, 'Data'))
|
||||
locations.append(Location('Styles', opts.styles_dir, 'Data'))
|
||||
locations.append(Location('Wildcards', opts.wildcards_dir, 'Data'))
|
||||
locations.append(Location('Reference', paths.reference_path, 'Data'))
|
||||
locations.append(Location('LUTs', os.path.join(paths.models_path, 'LUTs'), 'Data'))
|
||||
locations.append(Location('Wiki', 'wiki', 'Data'))
|
||||
if types is None or 'All' in types or 'Cache' in types:
|
||||
locations.append(Location('Temp', opts.temp_dir, 'Cache'))
|
||||
locations.append(Location('XET', opts.xetcache_dir, 'Cache'))
|
||||
locations.append(Location('OpenVINO', opts.openvino_cache_path, 'Cache'))
|
||||
locations.append(Location('ONNX', opts.onnx_cached_models_path, 'Cache'))
|
||||
locations.append(Location('VENV', 'venv', 'Cache'))
|
||||
locations.append(Location('Torch', [opts.tunable_dir, os.getenv("TORCHINDUCTOR_CACHE_DIR", None), os.getenv("TRITON_CACHE_DIR", None)], 'Cache'))
|
||||
if types is None or 'All' in types or 'Code' in types:
|
||||
locations.append(Location('Modules', 'modules', 'Code'))
|
||||
locations.append(Location('Pipelines', 'pipelines', 'Code'))
|
||||
locations.append(Location('Scripts', 'scripts', 'Code'))
|
||||
locations.append(Location('UI', 'ui', 'Code'))
|
||||
locations.append(Location('Builtin', paths.extensions_builtin_dir, 'Code'))
|
||||
locations.append(Location('Extensions', paths.extensions_dir, 'Code'))
|
||||
if types is None or 'All' in types or 'Images' in types:
|
||||
locations.append(Location('Text', [opts.outdir_txt2img_samples], 'Images'))
|
||||
locations.append(Location('Image', [opts.outdir_img2img_samples], 'Images'))
|
||||
locations.append(Location('Control', [opts.outdir_control_samples], 'Images'))
|
||||
locations.append(Location('Extras', [opts.outdir_extras_samples], 'Images'))
|
||||
locations.append(Location('Save', [opts.outdir_save], 'Images'))
|
||||
locations.append(Location('Grids', [opts.outdir_txt2img_grids, opts.outdir_img2img_grids, opts.outdir_control_grids], 'Images'))
|
||||
if types is None or 'All' in types or 'Videos' in types:
|
||||
locations.append(Location('Video', [opts.outdir_video], 'Videos'))
|
||||
return locations
|
||||
|
||||
|
||||
def get_other_locations(locations: list[Location], name: str, folder: str, what: str = 'Other') -> list[Location]:
|
||||
# get list of first level subfolders in `folder` check each if its already in `locations` by comparing resolved paths if not, add each to the list as a new Location with type `what`
|
||||
existing_paths = set()
|
||||
for location in locations:
|
||||
for path in location.paths:
|
||||
existing_paths.add(path.resolve(strict=False))
|
||||
try:
|
||||
with os.scandir(folder) as entries:
|
||||
for entry in entries:
|
||||
if entry.is_dir(follow_symlinks=False):
|
||||
path = Path(entry.path).resolve(strict=False)
|
||||
if path not in existing_paths:
|
||||
locations.append(Location(name, entry.path, what))
|
||||
except (FileNotFoundError, PermissionError):
|
||||
pass
|
||||
return locations
|
||||
|
||||
|
||||
def print_summary(locations: list[Location]):
|
||||
summary = {}
|
||||
for location in locations:
|
||||
if location.type not in summary:
|
||||
summary[location.type] = {
|
||||
'size': 0,
|
||||
'mtime': datetime.fromtimestamp(0),
|
||||
'nfiles': 0,
|
||||
'nfolders': 0,
|
||||
'nerrors': 0,
|
||||
}
|
||||
summary[location.type]['size'] += location.size
|
||||
if location.mtime > summary[location.type]['mtime']:
|
||||
summary[location.type]['mtime'] = location.mtime
|
||||
summary[location.type]['nfiles'] += location.nfiles
|
||||
summary[location.type]['nfolders'] += location.nfolders
|
||||
summary[location.type]['nerrors'] += location.nerrors
|
||||
for k, v in summary.items():
|
||||
log.debug(f'Storage: type={k} size={v["size"]/1024/1024:.3f} files={v["nfiles"]} folders={v["nfolders"]}')
|
||||
|
||||
|
||||
def check_storage(folders: str | list[str] | None = None, types: list[str] | None = None, silent: bool = False) -> list[Location]:
|
||||
if isinstance(folders, str):
|
||||
folders = [folders]
|
||||
if folders is not None and len(folders) > 0:
|
||||
locations = [Location(None, folder) for _i, folder in enumerate(folders)]
|
||||
else:
|
||||
locations = get_all_locations(types)
|
||||
if types is None or 'Other' in types or 'All' in types:
|
||||
locations = get_other_locations(locations, 'Other', paths.models_path)
|
||||
log.debug(f'Storage: locations={len(locations)} workers={max_workers} types={types} folders={folders} start')
|
||||
|
||||
def update_stats(location: Location) -> Location:
|
||||
t0 = time.time()
|
||||
for f in location.paths:
|
||||
size, mtime, files, folders, symlinks, errors = stat(f, extended=True, exclude=['__pycache__', '.'])
|
||||
location.size += size
|
||||
if mtime > location.mtime:
|
||||
location.mtime = mtime
|
||||
location.nfiles += files
|
||||
location.nfolders += folders
|
||||
location.nsymlinks += symlinks
|
||||
location.nerrors += errors
|
||||
location.time = time.time() - t0
|
||||
return location
|
||||
|
||||
t0 = time.time()
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
future_items = {executor.submit(update_stats, location): location for location in locations}
|
||||
for future in as_completed(future_items):
|
||||
location = future.result()
|
||||
if location.size > 0 and not silent:
|
||||
log.debug(location)
|
||||
t1 = time.time()
|
||||
print_summary(locations)
|
||||
log.debug(f'Storage: time={t1-t0:.3f} end')
|
||||
return locations
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import gradio as gr
|
||||
from modules import timer, shared, paths, theme, sd_models, modelloader, generation_parameters_copypaste, call_queue, script_callbacks
|
||||
from modules import ui_common, ui_loadsave, ui_history, ui_components, ui_symbols
|
||||
from modules import ui_common, ui_loadsave, ui_history, ui_storage, ui_components, ui_symbols
|
||||
from modules.logger import log
|
||||
|
||||
|
||||
@@ -313,6 +313,10 @@ def create_ui(disabled_tabs=None):
|
||||
with gr.TabItem("History", id="system_history", elem_id="tab_history"):
|
||||
ui_history.create_ui()
|
||||
|
||||
if 'storage' not in disabled_tabs:
|
||||
with gr.TabItem("Storage", id="system_storage", elem_id="tab_storage"):
|
||||
ui_storage.create_ui()
|
||||
|
||||
if 'monitor' not in disabled_tabs:
|
||||
with gr.TabItem("GPU Monitor", id="system_gpu", elem_id="tab_gpu"):
|
||||
with gr.Row(elem_id='gpu-controls'):
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import gradio as gr
|
||||
|
||||
|
||||
def create_ui():
|
||||
types = ['All', 'Images', 'Videos', 'Models', 'Data', 'Cache', 'Code', 'Other']
|
||||
with gr.Row():
|
||||
btn_refresh = gr.Button("Calculate", elem_id='btn_storage_refresh')
|
||||
storage_type = gr.Dropdown(label="Storage type", elem_id='storage_type', choices=types, value=[types[0]], multiselect=True)
|
||||
with gr.Row():
|
||||
_storage_table = gr.HTML('', elem_id='storage_table')
|
||||
with gr.Row():
|
||||
_storage_timeline = gr.HTML('', elem_id='storage_timeline')
|
||||
btn_refresh.click(_js='refreshStorage', fn=None, inputs=[storage_type], outputs=[], show_progress='full')
|
||||
Vendored
+99
-35
@@ -2759,15 +2759,15 @@ var require_jquery = __commonJS({
|
||||
function returnFalse() {
|
||||
return false;
|
||||
}
|
||||
function on(elem, types, selector, data, fn, one) {
|
||||
function on(elem, types2, selector, data, fn, one) {
|
||||
var origFn, type;
|
||||
if (typeof types === "object") {
|
||||
if (typeof types2 === "object") {
|
||||
if (typeof selector !== "string") {
|
||||
data = data || selector;
|
||||
selector = void 0;
|
||||
}
|
||||
for (type in types) {
|
||||
on(elem, type, selector, data, types[type], one);
|
||||
for (type in types2) {
|
||||
on(elem, type, selector, data, types2[type], one);
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
@@ -2798,11 +2798,11 @@ var require_jquery = __commonJS({
|
||||
fn.guid = origFn.guid || (origFn.guid = jQuery3.guid++);
|
||||
}
|
||||
return elem.each(function() {
|
||||
jQuery3.event.add(this, types, fn, data, selector);
|
||||
jQuery3.event.add(this, types2, fn, data, selector);
|
||||
});
|
||||
}
|
||||
jQuery3.event = {
|
||||
add: function(elem, types, handler, data, selector) {
|
||||
add: function(elem, types2, handler, data, selector) {
|
||||
var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.get(elem);
|
||||
if (!acceptData(elem)) {
|
||||
return;
|
||||
@@ -2826,10 +2826,10 @@ var require_jquery = __commonJS({
|
||||
return typeof jQuery3 !== "undefined" && jQuery3.event.triggered !== e.type ? jQuery3.event.dispatch.apply(elem, arguments) : void 0;
|
||||
};
|
||||
}
|
||||
types = (types || "").match(rnothtmlwhite) || [""];
|
||||
t = types.length;
|
||||
types2 = (types2 || "").match(rnothtmlwhite) || [""];
|
||||
t = types2.length;
|
||||
while (t--) {
|
||||
tmp = rtypenamespace.exec(types[t]) || [];
|
||||
tmp = rtypenamespace.exec(types2[t]) || [];
|
||||
type = origType = tmp[1];
|
||||
namespaces = (tmp[2] || "").split(".").sort();
|
||||
if (!type) {
|
||||
@@ -2871,20 +2871,20 @@ var require_jquery = __commonJS({
|
||||
}
|
||||
},
|
||||
// Detach an event or set of events from an element
|
||||
remove: function(elem, types, handler, selector, mappedTypes) {
|
||||
remove: function(elem, types2, handler, selector, mappedTypes) {
|
||||
var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.hasData(elem) && dataPriv.get(elem);
|
||||
if (!elemData || !(events = elemData.events)) {
|
||||
return;
|
||||
}
|
||||
types = (types || "").match(rnothtmlwhite) || [""];
|
||||
t = types.length;
|
||||
types2 = (types2 || "").match(rnothtmlwhite) || [""];
|
||||
t = types2.length;
|
||||
while (t--) {
|
||||
tmp = rtypenamespace.exec(types[t]) || [];
|
||||
tmp = rtypenamespace.exec(types2[t]) || [];
|
||||
type = origType = tmp[1];
|
||||
namespaces = (tmp[2] || "").split(".").sort();
|
||||
if (!type) {
|
||||
for (type in events) {
|
||||
jQuery3.event.remove(elem, type + types[t], handler, selector, true);
|
||||
jQuery3.event.remove(elem, type + types2[t], handler, selector, true);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -3234,26 +3234,26 @@ var require_jquery = __commonJS({
|
||||
};
|
||||
});
|
||||
jQuery3.fn.extend({
|
||||
on: function(types, selector, data, fn) {
|
||||
return on(this, types, selector, data, fn);
|
||||
on: function(types2, selector, data, fn) {
|
||||
return on(this, types2, selector, data, fn);
|
||||
},
|
||||
one: function(types, selector, data, fn) {
|
||||
return on(this, types, selector, data, fn, 1);
|
||||
one: function(types2, selector, data, fn) {
|
||||
return on(this, types2, selector, data, fn, 1);
|
||||
},
|
||||
off: function(types, selector, fn) {
|
||||
off: function(types2, selector, fn) {
|
||||
var handleObj, type;
|
||||
if (types && types.preventDefault && types.handleObj) {
|
||||
handleObj = types.handleObj;
|
||||
jQuery3(types.delegateTarget).off(
|
||||
if (types2 && types2.preventDefault && types2.handleObj) {
|
||||
handleObj = types2.handleObj;
|
||||
jQuery3(types2.delegateTarget).off(
|
||||
handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
|
||||
handleObj.selector,
|
||||
handleObj.handler
|
||||
);
|
||||
return this;
|
||||
}
|
||||
if (typeof types === "object") {
|
||||
for (type in types) {
|
||||
this.off(type, selector, types[type]);
|
||||
if (typeof types2 === "object") {
|
||||
for (type in types2) {
|
||||
this.off(type, selector, types2[type]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -3265,7 +3265,7 @@ var require_jquery = __commonJS({
|
||||
fn = returnFalse;
|
||||
}
|
||||
return this.each(function() {
|
||||
jQuery3.event.remove(this, types, fn, selector);
|
||||
jQuery3.event.remove(this, types2, fn, selector);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -5823,17 +5823,17 @@ var require_jquery = __commonJS({
|
||||
};
|
||||
});
|
||||
jQuery3.fn.extend({
|
||||
bind: function(types, data, fn) {
|
||||
return this.on(types, null, data, fn);
|
||||
bind: function(types2, data, fn) {
|
||||
return this.on(types2, null, data, fn);
|
||||
},
|
||||
unbind: function(types, fn) {
|
||||
return this.off(types, null, fn);
|
||||
unbind: function(types2, fn) {
|
||||
return this.off(types2, null, fn);
|
||||
},
|
||||
delegate: function(selector, types, data, fn) {
|
||||
return this.on(types, selector, data, fn);
|
||||
delegate: function(selector, types2, data, fn) {
|
||||
return this.on(types2, selector, data, fn);
|
||||
},
|
||||
undelegate: function(selector, types, fn) {
|
||||
return arguments.length === 1 ? this.off(selector, "**") : this.off(types, selector || "**", fn);
|
||||
undelegate: function(selector, types2, fn) {
|
||||
return arguments.length === 1 ? this.off(selector, "**") : this.off(types2, selector || "**", fn);
|
||||
},
|
||||
hover: function(fnOver, fnOut) {
|
||||
return this.on("mouseenter", fnOver).on("mouseleave", fnOut || fnOver);
|
||||
@@ -16448,7 +16448,7 @@ var Timesheet = class {
|
||||
// ui/history.ts
|
||||
var inferenceTypes = ["inference", "vae", "te"];
|
||||
var ioTypes = ["load", "save"];
|
||||
function refreshHistory() {
|
||||
async function refreshHistory() {
|
||||
log("refreshHistory");
|
||||
authFetch(`${window.api}/history`, { priority: "low" }).then((res) => {
|
||||
if (!res) return;
|
||||
@@ -16500,6 +16500,70 @@ function refreshHistory() {
|
||||
}
|
||||
window.refreshHistory = refreshHistory;
|
||||
|
||||
// ui/storage.ts
|
||||
var types = ["Images", "Videos", "Models", "Data", "Cache", "Code", "Other"];
|
||||
function buildTable(type, data) {
|
||||
const totalSize = data.reduce((acc, entry) => acc + entry.size, 0);
|
||||
const totalLoc = data.length;
|
||||
const totalFiles = data.reduce((acc, entry) => acc + entry.nfiles, 0);
|
||||
const totalFolders = data.reduce((acc, entry) => acc + entry.nfolders, 0);
|
||||
let title = `Locations: ${totalLoc}
|
||||
Total Size: ${(totalSize / (1024 * 1024)).toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} MB
|
||||
Total Files: ${totalFiles}
|
||||
Total Folders: ${totalFolders}
|
||||
`;
|
||||
let html = `<h2 title="${title}">${type}</h2><table><tbody>`;
|
||||
for (const entry of data) {
|
||||
if (entry.size === 0) continue;
|
||||
const size = (entry.size / (1024 * 1024)).toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + " MB";
|
||||
const mtime = entry.mtime > 0 ? new Date(entry.mtime * 1e3).toLocaleString() : "";
|
||||
title = `Type: ${entry.type}
|
||||
Name: ${entry.name}
|
||||
Size: ${size}
|
||||
Last modified: ${mtime}
|
||||
`;
|
||||
title += `Folders: ${entry.folders.join(", ")}
|
||||
Resolved paths: ${entry.paths.join(", ")}
|
||||
`;
|
||||
title += `Subfolders: ${entry.nfolders}
|
||||
Files: ${entry.nfiles}
|
||||
Symlinks: ${entry.nsymlinks}
|
||||
Errors: ${entry.nerrors}
|
||||
`;
|
||||
title += `Time to scan: ${entry.time.toFixed(3)} seconds`;
|
||||
const perc = Math.round(entry.size / totalSize * 100);
|
||||
const color = `rgb(${perc}, 50, 80)`;
|
||||
const css = `background: linear-gradient(to right, ${color} ${perc}%, transparent ${perc}%);`;
|
||||
html += `<tr title="${title}"><td style="${css}">${entry.name}</td><td>${size}</td><td>${mtime}</td></tr>`;
|
||||
}
|
||||
html += "</tbody></table>";
|
||||
return html;
|
||||
}
|
||||
async function refreshStorage(storageTypes) {
|
||||
log("refreshStorage", storageTypes);
|
||||
authFetch(`${window.api}/storage?types=${storageTypes.join(",")}`, { priority: "low" }).then((res) => {
|
||||
if (!res) return;
|
||||
const timeline = document.getElementById("storage_timeline");
|
||||
const table = document.getElementById("storage_table");
|
||||
if (!timeline || !table) return;
|
||||
timeline.innerHTML = "";
|
||||
res.json().then((rawData) => {
|
||||
const data = rawData;
|
||||
if (!data || !data.length) {
|
||||
table.innerHTML = "<p>No storage data available.</p>";
|
||||
return;
|
||||
}
|
||||
table.innerHTML = "";
|
||||
if (storageTypes.includes("All")) storageTypes = types;
|
||||
for (const type of storageTypes) {
|
||||
const typeData = data.filter((entry) => entry.type === type);
|
||||
if (typeData.length > 0) table.innerHTML += buildTable(type, typeData);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
window.refreshStorage = refreshStorage;
|
||||
|
||||
// ui/aspectRatioOverlay.ts
|
||||
var currentWidth = null;
|
||||
var currentHeight = null;
|
||||
|
||||
Vendored
+4
-4
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -119,6 +119,7 @@ declare global {
|
||||
disableGPU?: () => Promise<void>; // ui/gpu.ts
|
||||
startGPU?: () => Promise<void>; // ui/gpu.ts
|
||||
refreshHistory?: () => void; // ui/history.ts
|
||||
refreshStorage?: (storageTypes: string[]) => void; // ui/storage.ts
|
||||
inputAccordionChecked?: (id: string, checked: boolean) => void; // ui/inputAccordion.ts
|
||||
debug?: (...args: unknown[]) => Promise<void>; // ui/logger.ts
|
||||
error?: (...args: unknown[]) => Promise<void>; // ui/logger.ts
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ interface TimelineEntry {
|
||||
const inferenceTypes = ['inference', 'vae', 'te'];
|
||||
const ioTypes = ['load', 'save'];
|
||||
|
||||
export function refreshHistory() {
|
||||
export async function refreshHistory() {
|
||||
log('refreshHistory');
|
||||
authFetch(`${window.api}/history`, { priority: 'low' }).then((res) => {
|
||||
if (!res) return;
|
||||
|
||||
@@ -27,6 +27,7 @@ import './promptChecker';
|
||||
import './setHints';
|
||||
import './monitor';
|
||||
import './history';
|
||||
import './storage';
|
||||
import './aspectRatioOverlay';
|
||||
import './resolutionLock';
|
||||
import './authWrap';
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Timesheet } from './timesheet';
|
||||
import { log } from './logger';
|
||||
import { authFetch } from './authWrap';
|
||||
|
||||
const types = ['Images', 'Videos', 'Models', 'Data', 'Cache', 'Code', 'Other'];
|
||||
|
||||
interface LocationEntry {
|
||||
name: string;
|
||||
type: 'Images' | 'Videos' | 'Models' | 'Data' | 'Cache' | 'Code' | 'Other';
|
||||
folders: string[];
|
||||
paths: string[];
|
||||
size: number;
|
||||
mtime: number;
|
||||
nfiles: number;
|
||||
nfolders: number;
|
||||
nsymlinks: number;
|
||||
nerrors: number;
|
||||
time: number;
|
||||
}
|
||||
|
||||
interface TimelineEntry {
|
||||
start: number;
|
||||
end: number;
|
||||
label: string;
|
||||
type: 'inference' | 'io' | 'default';
|
||||
}
|
||||
|
||||
function buildTable(type: string, data: LocationEntry[]) {
|
||||
// let html = `<h2>${type}</h2><table><thead><tr><th>Location</th><th>Size</th><th>MTime</th></tr></thead><tbody>`;
|
||||
const totalSize = data.reduce((acc, entry) => acc + entry.size, 0);
|
||||
const totalLoc = data.length;
|
||||
const totalFiles = data.reduce((acc, entry) => acc + entry.nfiles, 0);
|
||||
const totalFolders = data.reduce((acc, entry) => acc + entry.nfolders, 0);
|
||||
let title = `Locations: ${totalLoc}\nTotal Size: ${(totalSize / (1024 * 1024)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} MB\nTotal Files: ${totalFiles}\nTotal Folders: ${totalFolders}\n`;
|
||||
let html = `<h2 title="${title}">${type}</h2><table><tbody>`;
|
||||
for (const entry of data) {
|
||||
if (entry.size === 0) continue;
|
||||
const size = (entry.size / (1024 * 1024)).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' MB';
|
||||
const mtime = entry.mtime > 0 ? new Date(entry.mtime * 1000).toLocaleString() : '';
|
||||
title = `Type: ${entry.type}\nName: ${entry.name}\nSize: ${size}\nLast modified: ${mtime}\n`;
|
||||
title += `Folders: ${entry.folders.join(', ')}\nResolved paths: ${entry.paths.join(', ')}\n`;
|
||||
title += `Subfolders: ${entry.nfolders}\nFiles: ${entry.nfiles}\nSymlinks: ${entry.nsymlinks}\nErrors: ${entry.nerrors}\n`;
|
||||
title += `Time to scan: ${entry.time.toFixed(3)} seconds`;
|
||||
const perc = Math.round((entry.size / totalSize) * 100);
|
||||
const color = `rgb(${perc}, 50, 80)`;
|
||||
const css = `background: linear-gradient(to right, ${color} ${perc}%, transparent ${perc}%);`;
|
||||
html += `<tr title="${title}"><td style="${css}">${entry.name}</td><td>${size}</td><td>${mtime}</td></tr>`;
|
||||
}
|
||||
html += '</tbody></table>';
|
||||
return html;
|
||||
}
|
||||
|
||||
export async function refreshStorage(storageTypes: string[]) {
|
||||
log('refreshStorage', storageTypes);
|
||||
authFetch(`${window.api}/storage?types=${storageTypes.join(',')}`, { priority: 'low' }).then((res) => {
|
||||
if (!res) return;
|
||||
const timeline = document.getElementById('storage_timeline');
|
||||
const table = document.getElementById('storage_table');
|
||||
if (!timeline || !table) return;
|
||||
timeline.innerHTML = '';
|
||||
res.json().then((rawData) => {
|
||||
const data = rawData as LocationEntry[];
|
||||
if (!data || !data.length) {
|
||||
table.innerHTML = '<p>No storage data available.</p>';
|
||||
return;
|
||||
}
|
||||
table.innerHTML = '';
|
||||
if (storageTypes.includes('All')) storageTypes = types;
|
||||
for (const type of storageTypes) {
|
||||
const typeData = data.filter((entry) => entry.type === type);
|
||||
if (typeData.length > 0) table.innerHTML += buildTable(type, typeData);
|
||||
}
|
||||
|
||||
/*
|
||||
// build timeline
|
||||
const ts: TimelineEntry[] = [];
|
||||
for (const entry of data) {
|
||||
if (entry.op === 'begin') {
|
||||
const start = entry.timestamp;
|
||||
const endEntry = data.find((e) => (e.id === entry.id && e.op === 'end'));
|
||||
const end = endEntry?.timestamp ?? data[data.length - 1].timestamp;
|
||||
if (end - start < 0.02) continue; // skip very short entries
|
||||
if (inferenceTypes.some((type) => entry.job.toLowerCase().startsWith(type))) entry.type = 'inference';
|
||||
else if (ioTypes.some((type) => entry.job.toLowerCase().startsWith(type))) entry.type = 'io';
|
||||
else entry.type = 'default';
|
||||
if (start && end) ts.push({ start, end, label: entry.job, type: entry.type });
|
||||
}
|
||||
}
|
||||
if (!ts.length) return;
|
||||
// eslint-disable-next-line no-new
|
||||
new Timesheet(timeline, ts);
|
||||
*/
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.refreshStorage = refreshStorage;
|
||||
@@ -135,10 +135,17 @@ def initialize():
|
||||
modules.extra_networks.register_default_extra_networks()
|
||||
timer.startup.record("networks")
|
||||
|
||||
from modules.models_hf import hf_init, hf_check_cache
|
||||
from modules.models_hf import hf_init
|
||||
hf_init()
|
||||
hf_check_cache()
|
||||
timer.startup.record("huggingface")
|
||||
if shared.cmd_opts.test:
|
||||
from modules.models_hf import hf_check_cache
|
||||
hf_check_cache()
|
||||
timer.startup.record("huggingface")
|
||||
|
||||
if shared.cmd_opts.test:
|
||||
from modules.storage import check_storage
|
||||
check_storage()
|
||||
timer.startup.record("storage")
|
||||
|
||||
if shared.cmd_opts.tls_keyfile is not None and shared.cmd_opts.tls_certfile is not None:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user