mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -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"
|
reverse = sort_params.order == "desc"
|
||||||
annotated.sort(
|
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,
|
reverse=reverse,
|
||||||
)
|
)
|
||||||
return annotated
|
return annotated
|
||||||
|
|||||||
@@ -516,12 +516,18 @@ class LoraService(BaseModelService):
|
|||||||
if sort_by == "model_name":
|
if sort_by == "model_name":
|
||||||
available_loras = sorted(
|
available_loras = sorted(
|
||||||
available_loras,
|
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
|
else: # Default to filename
|
||||||
available_loras = sorted(
|
available_loras = sorted(
|
||||||
available_loras,
|
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
|
# Return minimal data needed for cycling
|
||||||
|
|||||||
@@ -221,33 +221,45 @@ class ModelCache:
|
|||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
reverse = (order == 'desc')
|
reverse = (order == 'desc')
|
||||||
if sort_key == 'name':
|
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(
|
result = natsorted(
|
||||||
data,
|
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
|
reverse=reverse
|
||||||
)
|
)
|
||||||
elif sort_key == 'date':
|
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(
|
result = sorted(
|
||||||
data,
|
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
|
reverse=reverse
|
||||||
)
|
)
|
||||||
elif sort_key == 'size':
|
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(
|
result = sorted(
|
||||||
data,
|
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
|
reverse=reverse
|
||||||
)
|
)
|
||||||
elif sort_key == 'usage':
|
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(
|
return sorted(
|
||||||
data,
|
data,
|
||||||
key=lambda x: (
|
key=lambda x: (
|
||||||
x.get('usage_count', 0),
|
x.get('usage_count', 0),
|
||||||
self._get_display_name(x).lower()
|
self._get_display_name(x).lower(),
|
||||||
|
x.get('file_path', '').lower()
|
||||||
),
|
),
|
||||||
reverse=reverse
|
reverse=reverse
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -135,7 +135,8 @@ class RecipeCache:
|
|||||||
"""Sort cached views. Caller must hold ``_lock``."""
|
"""Sort cached views. Caller must hold ``_lock``."""
|
||||||
|
|
||||||
self.sorted_by_name = natsorted(
|
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:
|
if not name_only:
|
||||||
self.sorted_by_date = sorted(
|
self.sorted_by_date = sorted(
|
||||||
|
|||||||
Reference in New Issue
Block a user