mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-23 14:12:11 -03:00
feat: add update availability filter to model list
Add a new filter option to show only models with available updates across all supported languages. This includes: - Adding "updates" filter translations in all locale files (de, en, es, fr, he, ja, ko) - Extending BaseModelApiClient to handle update_available_only query parameter - Implementing update filter button in PageControls component with event listeners - Adding corresponding CSS styles for active filter state The feature allows users to quickly identify and focus on models that have updates available, improving the update management workflow.
This commit is contained in:
@@ -135,6 +135,9 @@ function renderControlsDom(pageKey) {
|
||||
<div class="control-group">
|
||||
<button id="favoriteFilterBtn" class="favorite-filter"></button>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<button id="updateFilterBtn" class="update-filter"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -309,6 +312,84 @@ describe('PageControls favorites, sorting, and duplicates scenarios', () => {
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('persists update-available toggle for LoRAs and triggers reload', async () => {
|
||||
renderControlsDom('loras');
|
||||
const stateModule = await import('../../../static/js/state/index.js');
|
||||
stateModule.initPageState('loras');
|
||||
const { LorasControls } = await import('../../../static/js/components/controls/LorasControls.js');
|
||||
|
||||
const controls = new LorasControls();
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(sessionStorage.getItem('lora_manager_show_update_available_only_loras')).toBe('true');
|
||||
expect(stateModule.getCurrentPageState().showUpdateAvailableOnly).toBe(true);
|
||||
expect(document.getElementById('updateFilterBtn').classList.contains('active')).toBe(true);
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
|
||||
resetAndReloadMock.mockClear();
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(sessionStorage.getItem('lora_manager_show_update_available_only_loras')).toBe('false');
|
||||
expect(stateModule.getCurrentPageState().showUpdateAvailableOnly).toBe(false);
|
||||
expect(document.getElementById('updateFilterBtn').classList.contains('active')).toBe(false);
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('does not change filter badge or button when toggling update availability', async () => {
|
||||
renderControlsDom('loras');
|
||||
const stateModule = await import('../../../static/js/state/index.js');
|
||||
stateModule.initPageState('loras');
|
||||
const { LorasControls } = await import('../../../static/js/components/controls/LorasControls.js');
|
||||
|
||||
const controls = new LorasControls();
|
||||
|
||||
const filterBadge = document.getElementById('activeFiltersCount');
|
||||
const filterButton = document.getElementById('filterButton');
|
||||
|
||||
expect(filterBadge.style.display).toBe('none');
|
||||
expect(filterBadge.textContent).toBe('0');
|
||||
expect(filterButton.classList.contains('active')).toBe(false);
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(filterBadge.style.display).toBe('none');
|
||||
expect(filterBadge.textContent).toBe('0');
|
||||
expect(filterButton.classList.contains('active')).toBe(false);
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(filterBadge.style.display).toBe('none');
|
||||
expect(filterBadge.textContent).toBe('0');
|
||||
expect(filterButton.classList.contains('active')).toBe(false);
|
||||
});
|
||||
|
||||
it('persists update-available toggle for checkpoints and triggers reload', async () => {
|
||||
renderControlsDom('checkpoints');
|
||||
const stateModule = await import('../../../static/js/state/index.js');
|
||||
stateModule.initPageState('checkpoints');
|
||||
const { CheckpointsControls } = await import('../../../static/js/components/controls/CheckpointsControls.js');
|
||||
|
||||
const controls = new CheckpointsControls();
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(sessionStorage.getItem('lora_manager_show_update_available_only_checkpoints')).toBe('true');
|
||||
expect(stateModule.getCurrentPageState().showUpdateAvailableOnly).toBe(true);
|
||||
expect(document.getElementById('updateFilterBtn').classList.contains('active')).toBe(true);
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
|
||||
resetAndReloadMock.mockClear();
|
||||
|
||||
await controls.toggleUpdateAvailableOnly();
|
||||
|
||||
expect(sessionStorage.getItem('lora_manager_show_update_available_only_checkpoints')).toBe('false');
|
||||
expect(stateModule.getCurrentPageState().showUpdateAvailableOnly).toBe(false);
|
||||
expect(document.getElementById('updateFilterBtn').classList.contains('active')).toBe(false);
|
||||
expect(resetAndReloadMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('saves sort selection and reloads models', async () => {
|
||||
renderControlsDom('loras');
|
||||
const stateModule = await import('../../../static/js/state/index.js');
|
||||
|
||||
@@ -341,7 +341,7 @@ async def test_get_paginated_data_filters_by_update_status():
|
||||
page=1,
|
||||
page_size=5,
|
||||
sort_by="name:asc",
|
||||
has_update=True,
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert update_service.bulk_calls == [("stub", [1, 2, 3])]
|
||||
@@ -379,7 +379,7 @@ async def test_get_paginated_data_has_update_without_service_returns_empty():
|
||||
page=1,
|
||||
page_size=10,
|
||||
sort_by="name:asc",
|
||||
has_update=True,
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert response["items"] == []
|
||||
@@ -414,7 +414,7 @@ async def test_get_paginated_data_skips_items_when_update_check_fails():
|
||||
page=1,
|
||||
page_size=10,
|
||||
sort_by="name:asc",
|
||||
has_update=True,
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert update_service.bulk_calls == [("stub", [1, 2])]
|
||||
@@ -460,6 +460,79 @@ async def test_get_paginated_data_annotates_update_flags_with_bulk_dedup():
|
||||
assert response["total_pages"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_paginated_data_filters_update_available_only():
|
||||
items = [
|
||||
{"model_name": "Alpha", "civitai": {"modelId": 101}},
|
||||
{"model_name": "Beta", "civitai": {"modelId": 102}},
|
||||
{"model_name": "Gamma", "civitai": {"modelId": 103}},
|
||||
]
|
||||
repository = StubRepository(items)
|
||||
filter_set = PassThroughFilterSet()
|
||||
search_strategy = NoSearchStrategy()
|
||||
update_service = StubUpdateService({101: True, 102: False, 103: True})
|
||||
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,
|
||||
update_service=update_service,
|
||||
)
|
||||
|
||||
response = await service.get_paginated_data(
|
||||
page=1,
|
||||
page_size=5,
|
||||
sort_by="name:asc",
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert update_service.bulk_calls == [("stub", [101, 102, 103])]
|
||||
assert update_service.calls == []
|
||||
assert [item["model_name"] for item in response["items"]] == ["Alpha", "Gamma"]
|
||||
assert all(item["update_available"] is True for item in response["items"])
|
||||
assert response["total"] == 2
|
||||
assert response["total_pages"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_paginated_data_update_available_only_without_update_service():
|
||||
items = [
|
||||
{"model_name": "Alpha", "civitai": {"modelId": 201}},
|
||||
{"model_name": "Beta", "civitai": {"modelId": 202}},
|
||||
]
|
||||
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,
|
||||
update_service=None,
|
||||
)
|
||||
|
||||
response = await service.get_paginated_data(
|
||||
page=1,
|
||||
page_size=10,
|
||||
sort_by="name:asc",
|
||||
update_available_only=True,
|
||||
)
|
||||
|
||||
assert response["items"] == []
|
||||
assert response["total"] == 0
|
||||
assert response["total_pages"] == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"service_cls, extra_fields",
|
||||
|
||||
Reference in New Issue
Block a user