From c76b287aed91e4c8dae9560bbd16aa82f6303c17 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Sat, 15 Mar 2025 07:25:00 +0800 Subject: [PATCH 1/6] Normalize SHA256 hash handling by converting to lowercase in LoraScanner and LoraMetadata classes for consistency. --- py/services/lora_scanner.py | 8 ++++---- py/utils/models.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/py/services/lora_scanner.py b/py/services/lora_scanner.py index 559227cf..ca44cb19 100644 --- a/py/services/lora_scanner.py +++ b/py/services/lora_scanner.py @@ -102,7 +102,7 @@ class LoraScanner: # Build hash index and tags count for lora_data in raw_data: if 'sha256' in lora_data and 'file_path' in lora_data: - self._hash_index.add_entry(lora_data['sha256'], lora_data['file_path']) + self._hash_index.add_entry(lora_data['sha256'].lower(), lora_data['file_path']) # Count tags if 'tags' in lora_data and lora_data['tags']: @@ -649,15 +649,15 @@ class LoraScanner: # Add new methods for hash index functionality def has_lora_hash(self, sha256: str) -> bool: """Check if a LoRA with given hash exists""" - return self._hash_index.has_hash(sha256) + return self._hash_index.has_hash(sha256.lower()) def get_lora_path_by_hash(self, sha256: str) -> Optional[str]: """Get file path for a LoRA by its hash""" - return self._hash_index.get_path(sha256) + return self._hash_index.get_path(sha256.lower()) def get_lora_hash_by_path(self, file_path: str) -> Optional[str]: """Get hash for a LoRA by its file path""" - return self._hash_index.get_hash(file_path) + return self._hash_index.get_hash(file_path) # Add new method to get top tags async def get_top_tags(self, limit: int = 20) -> List[Dict[str, any]]: diff --git a/py/utils/models.py b/py/utils/models.py index 67be65f7..e543cc8c 100644 --- a/py/utils/models.py +++ b/py/utils/models.py @@ -47,7 +47,7 @@ class LoraMetadata: file_path=save_path.replace(os.sep, '/'), size=file_info.get('sizeKB', 0) * 1024, modified=datetime.now().timestamp(), - sha256=file_info['hashes'].get('SHA256', ''), + sha256=file_info['hashes'].get('SHA256', '').lower(), base_model=base_model, preview_url=None, # Will be updated after preview download preview_nsfw_level=0, # Will be updated after preview download, it is decided by the nsfw level of the preview image From 6920944724cda2dc240caa8ebfba34da7eb82f8e Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Sat, 15 Mar 2025 09:45:07 +0800 Subject: [PATCH 2/6] Refactor API and DownloadManager to utilize version-level properties for model file existence and size, improving data handling and UI responsiveness. --- py/routes/api_routes.py | 21 ++++++++++++++++----- static/js/managers/DownloadManager.js | 19 ++++++++++++------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/py/routes/api_routes.py b/py/routes/api_routes.py index 07d3471a..e080ffa4 100644 --- a/py/routes/api_routes.py +++ b/py/routes/api_routes.py @@ -529,13 +529,24 @@ class ApiRoutes: # Check local availability for each version for version in versions: - for file in version.get('files', []): - sha256 = file.get('hashes', {}).get('SHA256') + # Find the model file (type="Model") in the files list + model_file = next((file for file in version.get('files', []) + if file.get('type') == 'Model'), None) + + if model_file: + sha256 = model_file.get('hashes', {}).get('SHA256') if sha256: - file['existsLocally'] = self.scanner.has_lora_hash(sha256) - if file['existsLocally']: - file['localPath'] = self.scanner.get_lora_path_by_hash(sha256) + # Set existsLocally and localPath at the version level + version['existsLocally'] = self.scanner.has_lora_hash(sha256) + if version['existsLocally']: + version['localPath'] = self.scanner.get_lora_path_by_hash(sha256) + # Also set the model file size at the version level for easier access + version['modelSizeKB'] = model_file.get('sizeKB') + else: + # No model file found in this version + version['existsLocally'] = False + return web.json_response(versions) except Exception as e: logger.error(f"Error fetching model versions: {e}") diff --git a/static/js/managers/DownloadManager.js b/static/js/managers/DownloadManager.js index 75631bb1..e4b211ac 100644 --- a/static/js/managers/DownloadManager.js +++ b/static/js/managers/DownloadManager.js @@ -120,16 +120,21 @@ export class DownloadManager { versionList.innerHTML = this.versions.map(version => { const firstImage = version.images?.find(img => !img.url.endsWith('.mp4')); const thumbnailUrl = firstImage ? firstImage.url : '/loras_static/images/no-preview.png'; - const fileSize = (version.files[0]?.sizeKB / 1024).toFixed(2); - const existsLocally = version.files[0]?.existsLocally; - const localPath = version.files[0]?.localPath; + // Use version-level size or fallback to first file + const fileSize = version.modelSizeKB ? + (version.modelSizeKB / 1024).toFixed(2) : + (version.files[0]?.sizeKB / 1024).toFixed(2); + + // Use version-level existsLocally flag + const existsLocally = version.existsLocally; + const localPath = version.localPath; // 更新本地状态指示器为badge样式 const localStatus = existsLocally ? `
In Library -
${localPath}
+
${localPath || ''}
` : ''; return ` @@ -177,12 +182,12 @@ export class DownloadManager { this.updateNextButtonState(); } - // Add new method to update Next button state + // Update this method to use version-level existsLocally updateNextButtonState() { const nextButton = document.querySelector('#versionStep .primary-btn'); if (!nextButton) return; - const existsLocally = this.currentVersion?.files[0]?.existsLocally; + const existsLocally = this.currentVersion?.existsLocally; if (existsLocally) { nextButton.disabled = true; @@ -202,7 +207,7 @@ export class DownloadManager { } // Double-check if the version exists locally - const existsLocally = this.currentVersion.files[0]?.existsLocally; + const existsLocally = this.currentVersion.existsLocally; if (existsLocally) { showToast('This version already exists in your library', 'info'); return; From 9059795816a1c08340601c616737820637a0e84c Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Sat, 15 Mar 2025 10:16:52 +0800 Subject: [PATCH 3/6] Enhance DownloadManager to update hash index with new LoRA entries, improving file tracking during downloads. --- py/services/download_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/py/services/download_manager.py b/py/services/download_manager.py index 2dd0444f..2571ffb2 100644 --- a/py/services/download_manager.py +++ b/py/services/download_manager.py @@ -136,6 +136,9 @@ class DownloadManager: all_folders.add(relative_path) cache.folders = sorted(list(all_folders), key=lambda x: x.lower()) + # Update the hash index with the new LoRA entry + self.file_monitor.scanner._hash_index.add_entry(metadata_dict['sha256'], metadata_dict['file_path']) + # Report 100% completion if progress_callback: await progress_callback(100) From 02adced7b80daecfe4b93416e92a94e314c23019 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Sat, 15 Mar 2025 10:45:16 +0800 Subject: [PATCH 4/6] Fix path formatting in LoraStacker to ensure compatibility across different operating systems by replacing '/' with os.sep. --- py/nodes/lora_stacker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/py/nodes/lora_stacker.py b/py/nodes/lora_stacker.py index 8535ce17..7909cfbf 100644 --- a/py/nodes/lora_stacker.py +++ b/py/nodes/lora_stacker.py @@ -80,7 +80,8 @@ class LoraStacker: lora_path, trigger_words = asyncio.run(self.get_lora_info(lora_name)) # Add to stack without loading - stack.append((lora_path, model_strength, clip_strength)) + # replace '/' with os.sep to avoid different OS path format + stack.append((lora_path.replace('/', os.sep), model_strength, clip_strength)) # Add trigger words to collection all_trigger_words.extend(trigger_words) From 756ad399bfa30053440ccaa0827e8ead3ac58cf9 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Sat, 15 Mar 2025 10:45:32 +0800 Subject: [PATCH 5/6] Enhance LoraManagerLoader to include formatted loaded_loras in return values, improving data output for loaded LoRAs. --- py/nodes/lora_loader.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/py/nodes/lora_loader.py b/py/nodes/lora_loader.py index ec287721..aae8d882 100644 --- a/py/nodes/lora_loader.py +++ b/py/nodes/lora_loader.py @@ -26,8 +26,8 @@ class LoraManagerLoader: "optional": FlexibleOptionalInputType(any_type), } - RETURN_TYPES = ("MODEL", "CLIP", IO.STRING) - RETURN_NAMES = ("MODEL", "CLIP", "trigger_words") + RETURN_TYPES = ("MODEL", "CLIP", IO.STRING, IO.STRING) + RETURN_NAMES = ("MODEL", "CLIP", "trigger_words", "loaded_loras") FUNCTION = "load_loras" async def get_lora_info(self, lora_name): @@ -95,5 +95,9 @@ class LoraManagerLoader: # use ',, ' to separate trigger words for group mode trigger_words_text = ",, ".join(all_trigger_words) if all_trigger_words else "" + + # Format loaded_loras as separated by spaces + formatted_loras = " ".join([f"" + for name, strength in [item.split(':') for item in loaded_loras]]) - return (model, clip, trigger_words_text) \ No newline at end of file + return (model, clip, trigger_words_text, formatted_loras) \ No newline at end of file From 09f5e2961ecbda232d638fba00aa41aa229d9af1 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Sat, 15 Mar 2025 10:58:55 +0800 Subject: [PATCH 6/6] Bump version to 0.7.39 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 743dfd97..b93969ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-lora-manager" description = "LoRA Manager for ComfyUI - Access it at http://localhost:8188/loras for managing LoRA models with previews and metadata integration." -version = "0.7.38" +version = "0.7.39" license = {file = "LICENSE"} dependencies = [ "aiohttp",