4 Commits

Author SHA1 Message Date
Hayden
92f2d5ab9e Fix unable to install (#119)
* fix: release without requirements.txt

* prepare release 2.3.1
2025-02-04 11:12:15 +08:00
boeto
130c75f5bf fix huggingface download with tokens (#116) 2025-02-03 20:30:07 +08:00
Hayden
921dabc057 pref: optimize scan cost (#117) 2025-02-03 20:19:02 +08:00
Hayden
ac21c8015d style: optimize toolbar layout (#115) 2025-02-03 16:52:37 +08:00
7 changed files with 73 additions and 52 deletions

View File

@@ -60,7 +60,7 @@ jobs:
run: |
pnpm install
pnpm run build
tar -czf dist.tar.gz py/ web/ __init__.py LICENSE pyproject.toml
tar -czf dist.tar.gz py/ web/ __init__.py LICENSE pyproject.toml requirements.txt
- name: Create release draft
uses: softprops/action-gh-release@v2

View File

@@ -103,6 +103,8 @@ def get_task_content(task_id: str):
if not os.path.isfile(task_file):
raise RuntimeError(f"Task {task_id} not found")
task_content = utils.load_dict_pickle_file(task_file)
if isinstance(task_content, TaskContent):
return task_content
return TaskContent(**task_content)
@@ -178,17 +180,18 @@ async def create_model_download_task(task_data: dict, request):
task_path = utils.join_path(download_path, f"{task_id}.task")
if os.path.exists(task_path):
raise RuntimeError(f"Task {task_id} already exists")
download_platform = task_data.get("downloadPlatform", None)
try:
previewFile = task_data.pop("previewFile", None)
utils.save_model_preview_image(task_path, previewFile)
preview_file = task_data.pop("previewFile", None)
utils.save_model_preview_image(task_path, preview_file, download_platform)
set_task_content(task_id, task_data)
task_status = TaskStatus(
taskId=task_id,
type=model_type,
fullname=fullname,
preview=utils.get_model_preview_name(task_path),
platform=task_data.get("downloadPlatform", None),
platform=download_platform,
totalSize=float(task_data.get("sizeBytes", 0)),
)
download_model_task_status[task_id] = task_status

View File

@@ -225,7 +225,7 @@ class HuggingfaceModelSearcher(ModelSearcher):
"pathIndex": 0,
"description": "\n".join(description_parts),
"metadata": {},
"downloadPlatform": "",
"downloadPlatform": "huggingface",
"downloadUrl": f"https://huggingface.co/{model_id}/resolve/main/{filename}?download=true",
}
models.append(model)

View File

@@ -1,6 +1,7 @@
import os
import folder_paths
from aiohttp import web
from concurrent.futures import ThreadPoolExecutor, as_completed
from . import utils
@@ -115,43 +116,57 @@ class ModelManager:
def scan_models(self, folder: str, request):
result = []
include_hidden_files = utils.get_setting_value(request, "scan.include_hidden_files", False)
folders, extensions = folder_paths.folder_names_and_paths[folder]
def get_file_info(entry: os.DirEntry[str], base_path: str, path_index: int):
fullname = entry.path.replace(base_path, "")
basename = os.path.splitext(fullname)[0]
extension = os.path.splitext(fullname)[1]
if extension not in folder_paths.supported_pt_extensions:
return None
model_preview = f"/model-manager/preview/{folder}/{path_index}/{basename}.webp"
stat = entry.stat()
return {
"fullname": fullname,
"basename": basename,
"extension": extension,
"type": folder,
"pathIndex": path_index,
"sizeBytes": stat.st_size,
"preview": model_preview,
"createdAt": round(stat.st_ctime_ns / 1000000),
"updatedAt": round(stat.st_mtime_ns / 1000000),
}
def get_all_files_entry(directory: str):
files = []
with os.scandir(directory) as it:
for entry in it:
# Skip hidden files
if not include_hidden_files:
if entry.name.startswith("."):
continue
if entry.is_dir():
files.extend(get_all_files_entry(entry.path))
elif entry.is_file():
files.append(entry)
return files
for path_index, base_path in enumerate(folders):
files = utils.recursive_search_files(base_path, request)
models = folder_paths.filter_files_extensions(files, folder_paths.supported_pt_extensions)
for fullname in models:
fullname = utils.normalize_path(fullname)
basename = os.path.splitext(fullname)[0]
extension = os.path.splitext(fullname)[1]
abs_path = utils.join_path(base_path, fullname)
file_stats = os.stat(abs_path)
# Resolve preview
image_name = utils.get_model_preview_name(abs_path)
image_name = utils.join_path(os.path.dirname(fullname), image_name)
abs_image_path = utils.join_path(base_path, image_name)
if os.path.isfile(abs_image_path):
image_state = os.stat(abs_image_path)
image_timestamp = round(image_state.st_mtime_ns / 1000000)
image_name = f"{image_name}?ts={image_timestamp}"
model_preview = f"/model-manager/preview/{folder}/{path_index}/{image_name}"
model_info = {
"fullname": fullname,
"basename": basename,
"extension": extension,
"type": folder,
"pathIndex": path_index,
"sizeBytes": file_stats.st_size,
"preview": model_preview,
"createdAt": round(file_stats.st_ctime_ns / 1000000),
"updatedAt": round(file_stats.st_mtime_ns / 1000000),
}
result.append(model_info)
if not os.path.exists(base_path):
continue
file_entries = get_all_files_entry(base_path)
with ThreadPoolExecutor() as executor:
futures = {executor.submit(get_file_info, entry, base_path, path_index): entry for entry in file_entries}
for future in as_completed(futures):
file_info = future.result()
if file_info is None:
continue
result.append(file_info)
return result

View File

@@ -277,10 +277,9 @@ def remove_model_preview_image(model_path: str):
os.remove(preview_path)
def save_model_preview_image(model_path: str, image_file_or_url: Any):
def save_model_preview_image(model_path: str, image_file_or_url: Any, platform: str | None = None):
basename = os.path.splitext(model_path)[0]
preview_path = f"{basename}.webp"
# Download image file if it is url
if type(image_file_or_url) is str:
image_url = image_file_or_url
@@ -304,8 +303,11 @@ def save_model_preview_image(model_path: str, image_file_or_url: Any):
content_type: str = image_file.content_type
if not content_type.startswith("image/"):
raise RuntimeError(f"FileTypeError: expected image, got {content_type}")
if platform == "huggingface":
# huggingface previewFile content_type='text/plain', not startswith("image/")
return
else:
raise RuntimeError(f"FileTypeError: expected image, got {content_type}")
image = Image.open(image_file.file)
image.save(preview_path, "WEBP")

View File

@@ -1,7 +1,7 @@
[project]
name = "comfyui-model-manager"
description = "Manage models: browsing, download and delete."
version = "2.3.0"
version = "2.3.1"
license = { file = "LICENSE" }
dependencies = ["markdownify"]

View File

@@ -9,18 +9,19 @@
>
<div ref="toolbarContainer" class="col-span-full">
<div :class="['flex gap-4', $toolbar_2xl('flex-row', 'flex-col')]">
<ResponseInput
v-model="searchContent"
:placeholder="$t('searchModels')"
:allow-clear="true"
suffix-icon="pi pi-search"
></ResponseInput>
<div class="flex-1">
<ResponseInput
v-model="searchContent"
:placeholder="$t('searchModels')"
:allow-clear="true"
suffix-icon="pi pi-search"
></ResponseInput>
</div>
<div class="flex items-center justify-between gap-4 overflow-hidden">
<ResponseSelect
v-model="currentType"
:items="typeOptions"
:type="isMobile ? 'drop' : 'button'"
></ResponseSelect>
<ResponseSelect
v-model="sortOrder"