mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-22 05:32:12 -03:00
fix(services): implement stable sorting for model and recipe caches
Add file_path as a tie-breaker for all sort modes in ModelCache, BaseModelService, LoraService, and RecipeCache to ensure deterministic ordering when primary keys are identical. Resolves issue #859.
This commit is contained in:
@@ -208,7 +208,11 @@ class BaseModelService(ABC):
|
||||
|
||||
reverse = sort_params.order == "desc"
|
||||
annotated.sort(
|
||||
key=lambda x: (x.get("usage_count", 0), x.get("model_name", "").lower()),
|
||||
key=lambda x: (
|
||||
x.get("usage_count", 0),
|
||||
x.get("model_name", "").lower(),
|
||||
x.get("file_path", "").lower()
|
||||
),
|
||||
reverse=reverse,
|
||||
)
|
||||
return annotated
|
||||
|
||||
@@ -516,12 +516,18 @@ class LoraService(BaseModelService):
|
||||
if sort_by == "model_name":
|
||||
available_loras = sorted(
|
||||
available_loras,
|
||||
key=lambda x: (x.get("model_name") or x.get("file_name", "")).lower()
|
||||
key=lambda x: (
|
||||
(x.get("model_name") or x.get("file_name", "")).lower(),
|
||||
x.get("file_path", "").lower()
|
||||
)
|
||||
)
|
||||
else: # Default to filename
|
||||
available_loras = sorted(
|
||||
available_loras,
|
||||
key=lambda x: x.get("file_name", "").lower()
|
||||
key=lambda x: (
|
||||
x.get("file_name", "").lower(),
|
||||
x.get("file_path", "").lower()
|
||||
)
|
||||
)
|
||||
|
||||
# Return minimal data needed for cycling
|
||||
|
||||
@@ -221,33 +221,45 @@ class ModelCache:
|
||||
start_time = time.perf_counter()
|
||||
reverse = (order == 'desc')
|
||||
if sort_key == 'name':
|
||||
# Natural sort by configured display name, case-insensitive
|
||||
# Natural sort by configured display name, case-insensitive, with file_path as tie-breaker
|
||||
result = natsorted(
|
||||
data,
|
||||
key=lambda x: self._get_display_name(x).lower(),
|
||||
key=lambda x: (
|
||||
self._get_display_name(x).lower(),
|
||||
x.get('file_path', '').lower()
|
||||
),
|
||||
reverse=reverse
|
||||
)
|
||||
elif sort_key == 'date':
|
||||
# Sort by modified timestamp (use .get() with default to handle missing fields)
|
||||
# Sort by modified timestamp, fallback to name and path for stability
|
||||
result = sorted(
|
||||
data,
|
||||
key=lambda x: x.get('modified', 0.0),
|
||||
key=lambda x: (
|
||||
x.get('modified', 0.0),
|
||||
self._get_display_name(x).lower(),
|
||||
x.get('file_path', '').lower()
|
||||
),
|
||||
reverse=reverse
|
||||
)
|
||||
elif sort_key == 'size':
|
||||
# Sort by file size (use .get() with default to handle missing fields)
|
||||
# Sort by file size, fallback to name and path for stability
|
||||
result = sorted(
|
||||
data,
|
||||
key=lambda x: x.get('size', 0),
|
||||
key=lambda x: (
|
||||
x.get('size', 0),
|
||||
self._get_display_name(x).lower(),
|
||||
x.get('file_path', '').lower()
|
||||
),
|
||||
reverse=reverse
|
||||
)
|
||||
elif sort_key == 'usage':
|
||||
# Sort by usage count, fallback to 0, then name for stability
|
||||
# Sort by usage count, fallback to 0, then name and path for stability
|
||||
return sorted(
|
||||
data,
|
||||
key=lambda x: (
|
||||
x.get('usage_count', 0),
|
||||
self._get_display_name(x).lower()
|
||||
self._get_display_name(x).lower(),
|
||||
x.get('file_path', '').lower()
|
||||
),
|
||||
reverse=reverse
|
||||
)
|
||||
|
||||
@@ -135,7 +135,8 @@ class RecipeCache:
|
||||
"""Sort cached views. Caller must hold ``_lock``."""
|
||||
|
||||
self.sorted_by_name = natsorted(
|
||||
self.raw_data, key=lambda x: x.get("title", "").lower()
|
||||
self.raw_data,
|
||||
key=lambda x: (x.get("title", "").lower(), x.get("file_path", "").lower()),
|
||||
)
|
||||
if not name_only:
|
||||
self.sorted_by_date = sorted(
|
||||
|
||||
Reference in New Issue
Block a user