From 90986bd7958efa4e1d303b22d5fd499c6cb5e9c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 17 Mar 2026 01:32:48 +0000 Subject: [PATCH 1/2] feat: add case-insensitive webp support for lora cover photos Make preview file discovery case-insensitive so files with uppercase extensions like .WEBP are found on case-sensitive filesystems. Also explicitly list image/webp in the file picker accept attribute for broader browser compatibility. https://claude.ai/code/session_01SgT2pkisi27bEQELX5EeXZ --- py/services/model_scanner.py | 16 +++++----- py/utils/file_utils.py | 58 +++++++++++++++-------------------- static/js/api/baseModelApi.js | 2 +- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/py/services/model_scanner.py b/py/services/model_scanner.py index 3c427790..8244bc40 100644 --- a/py/services/model_scanner.py +++ b/py/services/model_scanner.py @@ -14,7 +14,6 @@ from ..utils.metadata_manager import MetadataManager from ..utils.civitai_utils import resolve_license_info from .model_cache import ModelCache from .model_hash_index import ModelHashIndex -from ..utils.constants import PREVIEW_EXTENSIONS from .model_lifecycle_service import delete_model_artifacts from .service_registry import ServiceRegistry from .websocket_manager import ws_manager @@ -1442,14 +1441,13 @@ class ModelScanner: file_path = self._hash_index.get_path(sha256.lower()) if not file_path: return None - - base_name = os.path.splitext(file_path)[0] - - for ext in PREVIEW_EXTENSIONS: - preview_path = f"{base_name}{ext}" - if os.path.exists(preview_path): - return config.get_preview_static_url(preview_path) - + + dir_path = os.path.dirname(file_path) + base_name = os.path.splitext(os.path.basename(file_path))[0] + preview_path = find_preview_file(base_name, dir_path) + if preview_path: + return config.get_preview_static_url(preview_path) + return None async def get_top_tags(self, limit: int = 20) -> List[Dict[str, any]]: diff --git a/py/utils/file_utils.py b/py/utils/file_utils.py index f0532fb6..4c6d4317 100644 --- a/py/utils/file_utils.py +++ b/py/utils/file_utils.py @@ -40,49 +40,39 @@ async def calculate_sha256(file_path: str) -> str: return sha256_hash.hexdigest() def find_preview_file(base_name: str, dir_path: str) -> str: - """Find preview file for given base name in directory""" - + """Find preview file for given base name in directory. + + Performs an exact-case check first (fast path), then falls back to a + case-insensitive scan so that files like ``model.WEBP`` or ``model.Png`` + are discovered on case-sensitive filesystems. + """ + temp_extensions = PREVIEW_EXTENSIONS.copy() # Add example extension for compatibility # https://github.com/willmiao/ComfyUI-Lora-Manager/issues/225 # The preview image will be optimized to lora-name.webp, so it won't affect other logic temp_extensions.append(".example.0.jpeg") + + # Fast path: exact-case match for ext in temp_extensions: full_pattern = os.path.join(dir_path, f"{base_name}{ext}") if os.path.exists(full_pattern): - # Check if this is an image and not already webp - # TODO: disable the optimization for now, maybe add a config option later - # if ext.lower().endswith(('.jpg', '.jpeg', '.png')) and not ext.lower().endswith('.webp'): - # try: - # # Optimize the image to webp format - # webp_path = os.path.join(dir_path, f"{base_name}.webp") - - # # Use ExifUtils to optimize the image - # with open(full_pattern, 'rb') as f: - # image_data = f.read() - - # optimized_data, _ = ExifUtils.optimize_image( - # image_data=image_data, - # target_width=CARD_PREVIEW_WIDTH, - # format='webp', - # quality=85, - # preserve_metadata=False - # ) - - # # Save the optimized webp file - # with open(webp_path, 'wb') as f: - # f.write(optimized_data) - - # logger.debug(f"Optimized preview image from {full_pattern} to {webp_path}") - # return webp_path.replace(os.sep, "/") - # except Exception as e: - # logger.error(f"Error optimizing preview image {full_pattern}: {e}") - # # Fall back to original file if optimization fails - # return full_pattern.replace(os.sep, "/") - - # Return the original path for webp images or non-image files return full_pattern.replace(os.sep, "/") - + + # Slow path: case-insensitive match for systems with mixed-case extensions + # (e.g. .WEBP, .Png, .JPG placed manually or by external tools) + try: + dir_entries = os.listdir(dir_path) + except OSError: + return "" + + base_lower = base_name.lower() + for ext in temp_extensions: + target = f"{base_lower}{ext}" # ext is already lowercase + for entry in dir_entries: + if entry.lower() == target: + return os.path.join(dir_path, entry).replace(os.sep, "/") + return "" def get_preview_extension(preview_path: str) -> str: diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index 115c6d1e..44d604e3 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -251,7 +251,7 @@ export class BaseModelApiClient { replaceModelPreview(filePath) { const input = document.createElement('input'); input.type = 'file'; - input.accept = 'image/*,video/mp4'; + input.accept = 'image/*,image/webp,video/mp4'; input.onchange = async () => { if (!input.files || !input.files[0]) return; From 05ebd7493d2bab5a54a5c86346beac2c8ab30206 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 17 Mar 2026 01:33:34 +0000 Subject: [PATCH 2/2] chore: update package-lock.json after npm install https://claude.ai/code/session_01SgT2pkisi27bEQELX5EeXZ --- package-lock.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62fa3fe9..875f7d6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -114,7 +114,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -138,7 +137,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1613,7 +1611,6 @@ "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.0.1", "data-urls": "^5.0.0",