feat: add model version management endpoints

- Add set_version_update_ignore endpoint to toggle ignore status for specific versions
- Add get_model_versions endpoint to retrieve version details with optional refresh
- Update serialization to include version-specific data and preview overrides
- Modify database schema to support version-level ignore tracking
- Improve error handling for rate limiting and missing models

These changes enable granular control over version updates and provide better visibility into model version status.
This commit is contained in:
Will Miao
2025-10-25 14:54:23 +08:00
parent 0594e278b6
commit 58cafdb713
6 changed files with 753 additions and 148 deletions

View File

@@ -21,6 +21,7 @@ from py.services import model_file_service
from py.services.downloader import DownloadProgress
from py.services.metadata_sync_service import MetadataSyncService
from py.services.model_file_service import AutoOrganizeResult
from py.services.model_update_service import ModelVersionRecord
from py.services.service_registry import ServiceRegistry
from py.services.websocket_manager import ws_manager
from py.utils.exif_utils import ExifUtils
@@ -42,11 +43,23 @@ class DummyRoutes(BaseModelRoutes):
class NullUpdateRecord:
model_type: str
model_id: int
largest_version_id: int | None = None
version_ids: list[int] = field(default_factory=list)
in_library_version_ids: list[int] = field(default_factory=list)
versions: list[ModelVersionRecord] = field(default_factory=list)
last_checked_at: float | None = None
should_ignore: bool = False
should_ignore_model: bool = False
@property
def largest_version_id(self) -> int | None:
if not self.versions:
return None
return max(version.version_id for version in self.versions)
@property
def version_ids(self) -> list[int]:
return [version.version_id for version in self.versions]
@property
def in_library_version_ids(self) -> list[int]:
return [version.version_id for version in self.versions if version.is_in_library]
def has_update(self) -> bool:
return False
@@ -60,10 +73,30 @@ class NullModelUpdateService:
return None
async def update_in_library_versions(self, model_type, model_id, version_ids):
return NullUpdateRecord(model_type=model_type, model_id=model_id, in_library_version_ids=list(version_ids))
versions = [
ModelVersionRecord(
version_id=version_id,
name=None,
base_model=None,
released_at=None,
size_bytes=None,
preview_url=None,
is_in_library=True,
should_ignore=False,
)
for version_id in version_ids
]
return NullUpdateRecord(model_type=model_type, model_id=model_id, versions=versions)
async def set_should_ignore(self, model_type, model_id, should_ignore):
return NullUpdateRecord(model_type=model_type, model_id=model_id, should_ignore=should_ignore)
return NullUpdateRecord(
model_type=model_type,
model_id=model_id,
should_ignore_model=should_ignore,
)
async def set_version_should_ignore(self, model_type, model_id, version_id, should_ignore):
return await self.set_should_ignore(model_type, model_id, should_ignore)
async def get_record(self, *args, **kwargs):
return None

View File

@@ -0,0 +1,57 @@
import logging
from types import SimpleNamespace
import pytest
from py.config import config
from py.routes.handlers.model_handlers import ModelUpdateHandler
from py.services.model_update_service import ModelUpdateRecord, ModelVersionRecord
class DummyScanner:
def __init__(self, cache):
self._cache = cache
async def get_cached_data(self):
return self._cache
class DummyService:
def __init__(self, cache):
self.model_type = "lora"
self.scanner = DummyScanner(cache)
@pytest.mark.asyncio
async def test_build_preview_overrides_uses_static_urls():
cache = SimpleNamespace(version_index={123: {"preview_url": "/tmp/previews/example.png"}})
service = DummyService(cache)
handler = ModelUpdateHandler(
service=service,
update_service=SimpleNamespace(),
metadata_provider_selector=lambda *_: None,
logger=logging.getLogger(__name__),
)
record = ModelUpdateRecord(
model_type="lora",
model_id=42,
versions=[
ModelVersionRecord(
version_id=123,
name=None,
base_model=None,
released_at=None,
size_bytes=None,
preview_url=None,
is_in_library=True,
should_ignore=False,
)
],
last_checked_at=None,
should_ignore_model=False,
)
overrides = await handler._build_preview_overrides(record)
expected = config.get_preview_static_url("/tmp/previews/example.png")
assert overrides == {123: expected}