diff --git a/py/routes/api_routes.py b/py/routes/api_routes.py index 9c929a0d..e6b8906b 100644 --- a/py/routes/api_routes.py +++ b/py/routes/api_routes.py @@ -1209,7 +1209,7 @@ class ApiRoutes: if primary_model: group["models"].insert(0, self._format_lora_response(primary_model)) - if group["models"]: # Only include if we found models + if len(group["models"]) > 1: # Only include if we found multiple models result.append(group) return web.json_response({ diff --git a/py/routes/checkpoints_routes.py b/py/routes/checkpoints_routes.py index 9bde3002..7e605b01 100644 --- a/py/routes/checkpoints_routes.py +++ b/py/routes/checkpoints_routes.py @@ -735,7 +735,7 @@ class CheckpointsRoutes: if primary_model: group["models"].insert(0, self._format_checkpoint_response(primary_model)) - if group["models"]: + if len(group["models"]) > 1: # Only include if we found multiple models result.append(group) return web.json_response({ diff --git a/py/services/model_hash_index.py b/py/services/model_hash_index.py index e9668b1c..1f456934 100644 --- a/py/services/model_hash_index.py +++ b/py/services/model_hash_index.py @@ -63,16 +63,16 @@ class ModelHashIndex: """Extract filename without extension from path""" return os.path.splitext(os.path.basename(file_path))[0] - def remove_by_path(self, file_path: str) -> None: + def remove_by_path(self, file_path: str, hash_val: str = None) -> None: """Remove entry by file path""" filename = self._get_filename_from_path(file_path) - hash_val = None # Find the hash for this file path - for h, p in self._hash_to_path.items(): - if p == file_path: - hash_val = h - break + if hash_val is None: + for h, p in self._hash_to_path.items(): + if p == file_path: + hash_val = h + break # If we didn't find a hash, nothing to do if not hash_val: @@ -219,16 +219,7 @@ class ModelHashIndex: return set(self._filename_to_hash.keys()) def get_duplicate_hashes(self) -> Dict[str, List[str]]: - """Get dictionary of duplicate hashes and their paths""" - # Remove entries that have only one path - hashes_to_remove = [] - for sha256, paths in self._duplicate_hashes.items(): - if len(paths) <= 1: - hashes_to_remove.append(sha256) - - for sha256 in hashes_to_remove: - del self._duplicate_hashes[sha256] - + """Get dictionary of duplicate hashes and their paths""" return self._duplicate_hashes def get_duplicate_filenames(self) -> Dict[str, List[str]]: diff --git a/py/services/model_scanner.py b/py/services/model_scanner.py index 778c16b9..24406043 100644 --- a/py/services/model_scanner.py +++ b/py/services/model_scanner.py @@ -1342,7 +1342,7 @@ class ModelScanner: hash_val = model.get('sha256', '').lower() # Remove from hash index - self._hash_index.remove_by_path(file_path) + self._hash_index.remove_by_path(file_path, hash_val) # Check and clean up duplicates self._cleanup_duplicates_after_removal(hash_val, file_name) diff --git a/static/js/api/loraApi.js b/static/js/api/loraApi.js index 42502d3f..e70aba45 100644 --- a/static/js/api/loraApi.js +++ b/static/js/api/loraApi.js @@ -132,8 +132,6 @@ export function appendLoraCards(loras) { } export async function resetAndReload(updateFolders = false) { - const pageState = getCurrentPageState(); - // Check if virtual scroller is available if (state.virtualScroller) { return resetAndReloadWithVirtualScroll({ diff --git a/static/js/components/ModelDuplicatesManager.js b/static/js/components/ModelDuplicatesManager.js index bb416b86..8317af8d 100644 --- a/static/js/components/ModelDuplicatesManager.js +++ b/static/js/components/ModelDuplicatesManager.js @@ -2,6 +2,8 @@ import { showToast } from '../utils/uiHelpers.js'; import { state, getCurrentPageState } from '../state/index.js'; import { formatDate } from '../utils/formatters.js'; +import { resetAndReload as resetAndReloadLoras } from '../api/loraApi.js'; +import { resetAndReload as resetAndReloadCheckpoints } from '../api/checkpointApi.js'; export class ModelDuplicatesManager { constructor(pageManager, modelType = 'loras') { @@ -536,11 +538,43 @@ export class ModelDuplicatesManager { showToast(`Successfully deleted ${data.total_deleted} models`, 'success'); - // Exit duplicate mode if deletions were successful + // If models were successfully deleted if (data.total_deleted > 0) { - // Check duplicates count after deletion - this.checkDuplicatesCount(); - this.exitDuplicateMode(); + // Reload model data with updated folders + if (this.modelType === 'loras') { + await resetAndReloadLoras(true); + } else { + await resetAndReloadCheckpoints(true); + } + + // Check if there are still duplicates + try { + const endpoint = `/api/${this.modelType}/find-duplicates`; + const dupResponse = await fetch(endpoint); + + if (!dupResponse.ok) { + throw new Error(`Failed to get duplicates: ${dupResponse.statusText}`); + } + + const dupData = await dupResponse.json(); + const remainingDuplicatesCount = (dupData.duplicates || []).length; + + // Update badge count + this.updateDuplicatesBadge(remainingDuplicatesCount); + + // If no more duplicates, exit duplicate mode + if (remainingDuplicatesCount === 0) { + this.exitDuplicateMode(); + } else { + // If duplicates remain, refresh duplicate groups display + this.duplicateGroups = dupData.duplicates || []; + this.selectedForDeletion.clear(); + this.renderDuplicateGroups(); + this.updateSelectedCount(); + } + } catch (error) { + console.error('Error checking remaining duplicates:', error); + } } } catch (error) {