mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 11:11:26 -03:00
feat(other): answer model-versions-status read-only for unsupported types
Civitai types with no scanner at all (Wildcards, Workflows, Hypernetwork, Poses, AestheticGradient) used to get a 400 'Model type "x" is not supported', which hid the Civitai version list from clients. The handler now answers 200 with supported:false, a machine-readable reason (model_type_unsupported, or other_models_disabled when the opt-in master switch is off) and the versions marked read-only. The interactive payload gains an explicit supported:true. Legacy clients only read success/versions, so they are unaffected.
This commit is contained in:
@@ -2764,12 +2764,40 @@ class ModelLibraryHandler:
|
|||||||
|
|
||||||
normalized_type, scanner = await self._get_scanner_for_type(model_type)
|
normalized_type, scanner = await self._get_scanner_for_type(model_type)
|
||||||
if not normalized_type:
|
if not normalized_type:
|
||||||
|
# The lookup cannot be served as a fully interactive list. Two
|
||||||
|
# cases share this branch: a CivitAI type with no scanner at all
|
||||||
|
# (Wildcards, Workflows, Hypernetwork, Poses, AestheticGradient)
|
||||||
|
# and an Other-model type while the opt-in master switch is off.
|
||||||
|
# Answer 200 with the CivitAI list marked read-only plus a
|
||||||
|
# machine-readable reason, so clients can still show the
|
||||||
|
# versions and explain why the actions are missing. Legacy
|
||||||
|
# clients keep working: they only read `success`/`versions`.
|
||||||
|
reason = (
|
||||||
|
"other_models_disabled"
|
||||||
|
if self._normalize_model_type(model_type) == "other"
|
||||||
|
else "model_type_unsupported"
|
||||||
|
)
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": True,
|
||||||
"error": f'Model type "{model_type}" is not supported',
|
"modelId": model_id,
|
||||||
},
|
"modelName": model_name,
|
||||||
status=400,
|
"modelType": model_type,
|
||||||
|
"supported": False,
|
||||||
|
"reason": reason,
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"id": version.get("id"),
|
||||||
|
"name": version.get("name", ""),
|
||||||
|
"thumbnailUrl": version.get("images")[0]["url"]
|
||||||
|
if version.get("images")
|
||||||
|
else None,
|
||||||
|
"inLibrary": False,
|
||||||
|
"hasBeenDownloaded": False,
|
||||||
|
}
|
||||||
|
for version in versions
|
||||||
|
],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if not scanner:
|
if not scanner:
|
||||||
@@ -2811,6 +2839,7 @@ class ModelLibraryHandler:
|
|||||||
"modelId": model_id,
|
"modelId": model_id,
|
||||||
"modelName": model_name,
|
"modelName": model_name,
|
||||||
"modelType": model_type,
|
"modelType": model_type,
|
||||||
|
"supported": True,
|
||||||
"versions": enriched_versions,
|
"versions": enriched_versions,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2425,3 +2425,135 @@ async def test_get_init_status_reports_pending_scanners():
|
|||||||
assert "embedding" in payload["details"]
|
assert "embedding" in payload["details"]
|
||||||
assert "recipe" in payload["details"]
|
assert "recipe" in payload["details"]
|
||||||
assert "lora" not in payload["details"]
|
assert "lora" not in payload["details"]
|
||||||
|
|
||||||
|
|
||||||
|
class StaticMetadataProvider:
|
||||||
|
"""Metadata provider returning one fixed CivitAI model payload."""
|
||||||
|
|
||||||
|
def __init__(self, payload):
|
||||||
|
self.payload = payload
|
||||||
|
|
||||||
|
async def get_model_versions(self, _model_id):
|
||||||
|
return self.payload
|
||||||
|
|
||||||
|
async def get_user_models(self, _username, cursor=None):
|
||||||
|
return {"items": [], "nextCursor": None}
|
||||||
|
|
||||||
|
async def get_creator_model_count(self, _username):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _versions_status_handler(payload, *, other_scanner=None):
|
||||||
|
async def metadata_factory():
|
||||||
|
return StaticMetadataProvider(payload)
|
||||||
|
|
||||||
|
async def other_factory():
|
||||||
|
return other_scanner
|
||||||
|
|
||||||
|
return ModelLibraryHandler(
|
||||||
|
ServiceRegistryAdapter(
|
||||||
|
get_lora_scanner=fake_scanner_factory,
|
||||||
|
get_checkpoint_scanner=fake_scanner_factory,
|
||||||
|
get_embedding_scanner=fake_scanner_factory,
|
||||||
|
get_other_scanner=other_factory,
|
||||||
|
get_downloaded_version_history_service=fake_download_history_service_factory,
|
||||||
|
),
|
||||||
|
metadata_provider_factory=metadata_factory,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_model_versions_status_unsupported_type_is_read_only():
|
||||||
|
"""A type with no scanner answers 200 with a read-only list + reason."""
|
||||||
|
handler = _versions_status_handler(
|
||||||
|
{
|
||||||
|
"name": "Wildcards pack",
|
||||||
|
"type": "Wildcards",
|
||||||
|
"modelVersions": [
|
||||||
|
{"id": 11, "name": "v1", "images": [{"url": "https://img/1.png"}]},
|
||||||
|
{"id": 12, "name": "v2", "images": []},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await handler.get_model_versions_status(
|
||||||
|
FakeRequest(query={"modelId": "45448"}) # pyright: ignore[reportArgumentType]
|
||||||
|
)
|
||||||
|
payload = _json_payload(response)
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
assert payload["success"] is True
|
||||||
|
assert payload["supported"] is False
|
||||||
|
assert payload["reason"] == "model_type_unsupported"
|
||||||
|
assert payload["modelType"] == "wildcards"
|
||||||
|
assert payload["versions"] == [
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "v1",
|
||||||
|
"thumbnailUrl": "https://img/1.png",
|
||||||
|
"inLibrary": False,
|
||||||
|
"hasBeenDownloaded": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"name": "v2",
|
||||||
|
"thumbnailUrl": None,
|
||||||
|
"inLibrary": False,
|
||||||
|
"hasBeenDownloaded": False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_model_versions_status_other_disabled_is_read_only():
|
||||||
|
"""The opt-in gate keeps its own reason instead of the permanent one."""
|
||||||
|
_set_other_models_enabled(False)
|
||||||
|
handler = _versions_status_handler(
|
||||||
|
{
|
||||||
|
"name": "SDXL VAE",
|
||||||
|
"type": "VAE",
|
||||||
|
"modelVersions": [{"id": 333245, "name": "SDXL-VAE", "images": []}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await handler.get_model_versions_status(
|
||||||
|
FakeRequest(query={"modelId": "296576"}) # pyright: ignore[reportArgumentType]
|
||||||
|
)
|
||||||
|
payload = _json_payload(response)
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
assert payload["success"] is True
|
||||||
|
assert payload["supported"] is False
|
||||||
|
assert payload["reason"] == "other_models_disabled"
|
||||||
|
assert payload["modelType"] == "vae"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_model_versions_status_supported_type_stays_interactive():
|
||||||
|
"""A managed type keeps the existing enriched, fully interactive payload."""
|
||||||
|
handler = _versions_status_handler(
|
||||||
|
{
|
||||||
|
"name": "Some LoRA",
|
||||||
|
"type": "LORA",
|
||||||
|
"modelVersions": [{"id": 1, "name": "v1", "images": []}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await handler.get_model_versions_status(
|
||||||
|
FakeRequest(query={"modelId": "5"}) # pyright: ignore[reportArgumentType]
|
||||||
|
)
|
||||||
|
payload = _json_payload(response)
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
assert payload["success"] is True
|
||||||
|
assert payload["supported"] is True
|
||||||
|
assert "reason" not in payload
|
||||||
|
assert payload["versions"] == [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "v1",
|
||||||
|
"thumbnailUrl": None,
|
||||||
|
"inLibrary": False,
|
||||||
|
"hasBeenDownloaded": False,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user