mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-25 12:31:15 -03:00
feat(sort): enable versions_count sort in non-grouped mode
Sort by Most/Fewest versions first now works when Group by model is off. - Backend: group items by modelId (respecting version_grouping setting), count versions per group, sort groups by count, expand groups with versions sorted by version id descending - CSS: remove rule that hid the sort option in non-grouped mode - Tests: add 3 tests covering desc, asc, and same_base variants
This commit is contained in:
@@ -152,18 +152,51 @@ class BaseModelService(ABC):
|
|||||||
dedup_lost = len(sorted_data) - (len(dedup_map) + len(standalone))
|
dedup_lost = len(sorted_data) - (len(dedup_map) + len(standalone))
|
||||||
sorted_data = [entry[0] for entry in dedup_map.values()] + standalone
|
sorted_data = [entry[0] for entry in dedup_map.values()] + standalone
|
||||||
|
|
||||||
# Re-sort by version_count after dedup (only makes sense in group_by_model mode)
|
# Re-sort by version_count (grouped: after dedup; non-grouped: group internally, sort, expand)
|
||||||
is_group_by_active = kwargs.get("group_by_model") and civitai_model_id is None
|
if sort_params.key == "versions_count" and civitai_model_id is None:
|
||||||
if sort_params.key == "versions_count" and is_group_by_active:
|
|
||||||
reverse = sort_params.order == "desc"
|
reverse = sort_params.order == "desc"
|
||||||
sorted_data.sort(
|
if kwargs.get("group_by_model"):
|
||||||
key=lambda x: (
|
# Grouped mode: items are already dedup'd with version_count attached
|
||||||
x.get("version_count", 0),
|
sorted_data.sort(
|
||||||
(x.get("model_name") or x.get("file_name") or "").lower(),
|
key=lambda x: (
|
||||||
x.get("file_path", "").lower(),
|
x.get("version_count", 0),
|
||||||
),
|
(x.get("model_name") or x.get("file_name") or "").lower(),
|
||||||
reverse=reverse,
|
x.get("file_path", "").lower(),
|
||||||
)
|
),
|
||||||
|
reverse=reverse,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Non-grouped mode: group internally, sort groups by count, expand
|
||||||
|
# Respect the version_grouping setting (same logic as grouped dedup)
|
||||||
|
ufs = self.settings.get("version_grouping", "same_base")
|
||||||
|
group_by_base = ufs == "same_base"
|
||||||
|
|
||||||
|
model_groups: Dict[Any, List[Dict]] = {}
|
||||||
|
ungrouped_standalone: List[Dict] = []
|
||||||
|
for item in sorted_data:
|
||||||
|
mid = self._extract_model_id(item)
|
||||||
|
if mid is None:
|
||||||
|
ungrouped_standalone.append(item)
|
||||||
|
continue
|
||||||
|
key = (mid, item.get("base_model") or "") if group_by_base else mid
|
||||||
|
model_groups.setdefault(key, []).append(item)
|
||||||
|
# Sort versions within each group by version id descending
|
||||||
|
for items in model_groups.values():
|
||||||
|
items.sort(
|
||||||
|
key=lambda x: self._extract_version_id(x) or 0,
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
# Sort groups by version count
|
||||||
|
sorted_groups = sorted(
|
||||||
|
model_groups.values(),
|
||||||
|
key=lambda items: len(items),
|
||||||
|
reverse=reverse,
|
||||||
|
)
|
||||||
|
# Flatten: grouped items first, standalone items last
|
||||||
|
sorted_data = []
|
||||||
|
for items in sorted_groups:
|
||||||
|
sorted_data.extend(items)
|
||||||
|
sorted_data.extend(ungrouped_standalone)
|
||||||
|
|
||||||
t1 = time.perf_counter()
|
t1 = time.perf_counter()
|
||||||
if hash_filters:
|
if hash_filters:
|
||||||
|
|||||||
@@ -60,7 +60,4 @@
|
|||||||
margin-bottom: var(--space-2);
|
margin-bottom: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide versions_count sort option when group-by-model is off */
|
/* ---------- reused from shared styles ---------- */
|
||||||
body:not(.group-by-model) .sort-option-versions-count {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -809,6 +809,136 @@ async def test_get_paginated_data_group_by_model_dedup():
|
|||||||
assert response_all["total"] == 5
|
assert response_all["total"] == 5
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_paginated_data_versions_count_non_grouped_desc():
|
||||||
|
"""Non-grouped, versions_count:desc — groups by model, sorts by count desc,
|
||||||
|
within-group by version id desc, then flattens."""
|
||||||
|
items = [
|
||||||
|
# modelId=1 has 3 versions
|
||||||
|
{"model_name": "ModelA", "folder": "root", "civitai": {"modelId": 1, "id": 300}},
|
||||||
|
{"model_name": "ModelA", "folder": "root", "civitai": {"modelId": 1, "id": 200}},
|
||||||
|
{"model_name": "ModelA", "folder": "root", "civitai": {"modelId": 1, "id": 100}},
|
||||||
|
# modelId=2 has 2 versions
|
||||||
|
{"model_name": "ModelB", "folder": "root", "civitai": {"modelId": 2, "id": 99}},
|
||||||
|
{"model_name": "ModelB", "folder": "root", "civitai": {"modelId": 2, "id": 50}},
|
||||||
|
# modelId=3 has 1 version
|
||||||
|
{"model_name": "ModelC", "folder": "root", "civitai": {"modelId": 3, "id": 1}},
|
||||||
|
# standalone (no modelId)
|
||||||
|
{"model_name": "Standalone", "folder": "root"},
|
||||||
|
]
|
||||||
|
repository = StubRepository(items)
|
||||||
|
filter_set = PassThroughFilterSet()
|
||||||
|
search_strategy = NoSearchStrategy()
|
||||||
|
settings = StubSettings({})
|
||||||
|
|
||||||
|
service = DummyService(
|
||||||
|
model_type="stub",
|
||||||
|
scanner=object(),
|
||||||
|
metadata_class=BaseModelMetadata,
|
||||||
|
cache_repository=repository,
|
||||||
|
filter_set=filter_set,
|
||||||
|
search_strategy=search_strategy,
|
||||||
|
settings_provider=settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await service.get_paginated_data(
|
||||||
|
page=1, page_size=10, sort_by="versions_count:desc",
|
||||||
|
)
|
||||||
|
|
||||||
|
ids = [item["civitai"]["id"] for item in response["items"] if "civitai" in item and "id" in item["civitai"]]
|
||||||
|
# modelId=1 (3 versions): id descending → 300, 200, 100
|
||||||
|
# modelId=2 (2 versions): id descending → 99, 50
|
||||||
|
# modelId=3 (1 version) → 1
|
||||||
|
assert ids == [300, 200, 100, 99, 50, 1], f"Unexpected order: {ids}"
|
||||||
|
assert response["total"] == 7
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_paginated_data_versions_count_non_grouped_asc():
|
||||||
|
"""Non-grouped, versions_count:asc — groups by model, sorts by count asc,
|
||||||
|
then flattens."""
|
||||||
|
items = [
|
||||||
|
# modelId=1 has 3 versions
|
||||||
|
{"model_name": "ModelA", "folder": "root", "civitai": {"modelId": 1, "id": 300}},
|
||||||
|
{"model_name": "ModelA", "folder": "root", "civitai": {"modelId": 1, "id": 200}},
|
||||||
|
{"model_name": "ModelA", "folder": "root", "civitai": {"modelId": 1, "id": 100}},
|
||||||
|
# modelId=2 has 2 versions
|
||||||
|
{"model_name": "ModelB", "folder": "root", "civitai": {"modelId": 2, "id": 99}},
|
||||||
|
{"model_name": "ModelB", "folder": "root", "civitai": {"modelId": 2, "id": 50}},
|
||||||
|
# modelId=3 has 1 version
|
||||||
|
{"model_name": "ModelC", "folder": "root", "civitai": {"modelId": 3, "id": 1}},
|
||||||
|
# standalone (no modelId)
|
||||||
|
{"model_name": "Standalone", "folder": "root"},
|
||||||
|
]
|
||||||
|
repository = StubRepository(items)
|
||||||
|
filter_set = PassThroughFilterSet()
|
||||||
|
search_strategy = NoSearchStrategy()
|
||||||
|
settings = StubSettings({})
|
||||||
|
|
||||||
|
service = DummyService(
|
||||||
|
model_type="stub",
|
||||||
|
scanner=object(),
|
||||||
|
metadata_class=BaseModelMetadata,
|
||||||
|
cache_repository=repository,
|
||||||
|
filter_set=filter_set,
|
||||||
|
search_strategy=search_strategy,
|
||||||
|
settings_provider=settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await service.get_paginated_data(
|
||||||
|
page=1, page_size=10, sort_by="versions_count:asc",
|
||||||
|
)
|
||||||
|
|
||||||
|
ids = [item["civitai"]["id"] for item in response["items"] if "civitai" in item and "id" in item["civitai"]]
|
||||||
|
# modelId=3 (1 version) → 1
|
||||||
|
# modelId=2 (2 versions): id descending → 99, 50
|
||||||
|
# modelId=1 (3 versions): id descending → 300, 200, 100
|
||||||
|
assert ids == [1, 99, 50, 300, 200, 100], f"Unexpected order: {ids}"
|
||||||
|
assert response["total"] == 7
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_paginated_data_versions_count_non_grouped_same_base():
|
||||||
|
"""Non-grouped, versions_count with version_grouping=same_base —
|
||||||
|
models with same modelId but different base_model are separate groups."""
|
||||||
|
items = [
|
||||||
|
# modelId=1, base_model="sd15" — 2 versions
|
||||||
|
{"model_name": "ModelA", "folder": "root", "base_model": "sd15", "civitai": {"modelId": 1, "id": 200}},
|
||||||
|
{"model_name": "ModelA", "folder": "root", "base_model": "sd15", "civitai": {"modelId": 1, "id": 100}},
|
||||||
|
# modelId=1, base_model="sdxl" — 3 versions
|
||||||
|
{"model_name": "ModelA", "folder": "root", "base_model": "sdxl", "civitai": {"modelId": 1, "id": 30}},
|
||||||
|
{"model_name": "ModelA", "folder": "root", "base_model": "sdxl", "civitai": {"modelId": 1, "id": 20}},
|
||||||
|
{"model_name": "ModelA", "folder": "root", "base_model": "sdxl", "civitai": {"modelId": 1, "id": 10}},
|
||||||
|
# modelId=2, base_model="sd15" — 1 version
|
||||||
|
{"model_name": "ModelB", "folder": "root", "base_model": "sd15", "civitai": {"modelId": 2, "id": 1}},
|
||||||
|
]
|
||||||
|
repository = StubRepository(items)
|
||||||
|
filter_set = PassThroughFilterSet()
|
||||||
|
search_strategy = NoSearchStrategy()
|
||||||
|
settings = StubSettings({"version_grouping": "same_base"})
|
||||||
|
|
||||||
|
service = DummyService(
|
||||||
|
model_type="stub",
|
||||||
|
scanner=object(),
|
||||||
|
metadata_class=BaseModelMetadata,
|
||||||
|
cache_repository=repository,
|
||||||
|
filter_set=filter_set,
|
||||||
|
search_strategy=search_strategy,
|
||||||
|
settings_provider=settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await service.get_paginated_data(
|
||||||
|
page=1, page_size=10, sort_by="versions_count:desc",
|
||||||
|
)
|
||||||
|
|
||||||
|
ids = [item["civitai"]["id"] for item in response["items"] if "civitai" in item and "id" in item["civitai"]]
|
||||||
|
# (1, "sdxl") — 3 versions: 30, 20, 10
|
||||||
|
# (1, "sd15") — 2 versions: 200, 100
|
||||||
|
# (2, "sd15") — 1 version: 1
|
||||||
|
assert ids == [30, 20, 10, 200, 100, 1], f"Unexpected order: {ids}"
|
||||||
|
assert response["total"] == 6
|
||||||
|
|
||||||
|
|
||||||
async def test_get_paginated_data_filters_by_civitai_model_id():
|
async def test_get_paginated_data_filters_by_civitai_model_id():
|
||||||
"""civitai_model_id filter returns only items matching the given modelId,
|
"""civitai_model_id filter returns only items matching the given modelId,
|
||||||
and bypasses group_by_model dedup so all versions appear."""
|
and bypasses group_by_model dedup so all versions appear."""
|
||||||
|
|||||||
Reference in New Issue
Block a user