fix: handle civitai not found responses

This commit is contained in:
pixelpaws
2025-10-28 21:47:30 +08:00
parent ce3adaf831
commit dc1f7ab6fe
7 changed files with 194 additions and 10 deletions

View File

@@ -27,6 +27,7 @@ from ...services.service_registry import ServiceRegistry
from ...services.settings_manager import get_settings_manager
from ...services.websocket_manager import ws_manager
from ...services.downloader import get_downloader
from ...services.errors import ResourceNotFoundError
from ...utils.constants import (
CIVITAI_USER_MODEL_TYPES,
DEFAULT_NODE_COLOR,
@@ -618,7 +619,10 @@ class ModelLibraryHandler:
if not metadata_provider:
return web.json_response({"success": False, "error": "Metadata provider not available"}, status=503)
response = await metadata_provider.get_model_versions(model_id)
try:
response = await metadata_provider.get_model_versions(model_id)
except ResourceNotFoundError:
return web.json_response({"success": False, "error": "Model not found"}, status=404)
if not response or not response.get("modelVersions"):
return web.json_response({"success": False, "error": "Model not found"}, status=404)

View File

@@ -29,7 +29,7 @@ from ...services.use_cases import (
)
from ...services.websocket_manager import WebSocketManager
from ...services.websocket_progress_callback import WebSocketProgressCallback
from ...services.errors import RateLimitError
from ...services.errors import RateLimitError, ResourceNotFoundError
from ...utils.file_utils import calculate_sha256
from ...utils.metadata_manager import MetadataManager
@@ -863,7 +863,10 @@ class ModelCivitaiHandler:
try:
model_id = request.match_info["model_id"]
metadata_provider = await self._metadata_provider_factory()
response = await metadata_provider.get_model_versions(model_id)
try:
response = await metadata_provider.get_model_versions(model_id)
except ResourceNotFoundError:
return web.Response(status=404, text="Model not found")
if not response or not response.get("modelVersions"):
return web.Response(status=404, text="Model not found")

View File

@@ -2,10 +2,10 @@ import asyncio
import copy
import logging
import os
from typing import Optional, Dict, Tuple, List, Sequence
from typing import Any, Optional, Dict, Tuple, List, Sequence
from .model_metadata_provider import CivitaiModelMetadataProvider, ModelMetadataProviderManager
from .downloader import get_downloader
from .errors import RateLimitError
from .errors import RateLimitError, ResourceNotFoundError
logger = logging.getLogger(__name__)
@@ -160,7 +160,29 @@ class CivitaiClient:
logger.error(f"Download Error: {str(e)}")
return False
async def get_model_versions(self, model_id: str) -> List[Dict]:
@staticmethod
def _extract_error_message(payload: Any) -> str:
"""Return a human-readable error message from an API payload."""
def _from_value(value: Any) -> str:
if isinstance(value, str):
return value
if isinstance(value, dict):
for key in ("message", "error", "detail", "details"):
if key in value:
candidate = _from_value(value[key])
if candidate:
return candidate
if isinstance(value, list):
for item in value:
candidate = _from_value(item)
if candidate:
return candidate
return ""
return _from_value(payload)
async def get_model_versions(self, model_id: str) -> Optional[Dict]:
"""Get all versions of a model with local availability info"""
try:
success, result = await self._make_request(
@@ -175,12 +197,20 @@ class CivitaiClient:
'type': result.get('type', ''),
'name': result.get('name', '')
}
message = self._extract_error_message(result)
if message and 'not found' in message.lower():
raise ResourceNotFoundError(f"Resource not found for model {model_id}")
if message:
raise RuntimeError(message)
return None
except RateLimitError:
raise
except ResourceNotFoundError as exc:
logger.info("Model %s is no longer available on Civitai: %s", model_id, exc)
raise
except Exception as e:
logger.error(f"Error fetching model versions: {e}")
return None
logger.error("Error fetching model versions: %s", e, exc_info=True)
raise
async def get_model_versions_bulk(
self, model_ids: Sequence[int]

View File

@@ -19,3 +19,9 @@ class RateLimitError(RuntimeError):
self.retry_after = retry_after
self.provider = provider
class ResourceNotFoundError(RuntimeError):
"""Raised when a remote resource is permanently missing."""
pass

View File

@@ -9,7 +9,7 @@ import time
from dataclasses import dataclass, replace
from typing import Dict, Iterable, List, Mapping, Optional, Sequence
from .errors import RateLimitError
from .errors import RateLimitError, ResourceNotFoundError
from .settings_manager import get_settings_manager
from ..utils.civitai_utils import rewrite_preview_url
from ..utils.preview_selection import select_preview_media
@@ -459,14 +459,21 @@ class ModelUpdateService:
# release lock during network request
fetched_versions: List[ModelVersionRecord] | None = None
refresh_succeeded = False
fallback_attempted = False
fallback_error_message: Optional[str] = None
mark_model_as_ignored = False
response: Optional[Mapping] = None
if metadata_provider and should_fetch:
response = prefetched_response
if response is None:
fallback_attempted = True
try:
response = await metadata_provider.get_model_versions(model_id)
except RateLimitError:
raise
except ResourceNotFoundError as exc:
fallback_error_message = str(exc) or "resource not found"
mark_model_as_ignored = True
except Exception as exc: # pragma: no cover - defensive log
logger.error(
"Failed to fetch versions for model %s (%s): %s",
@@ -475,11 +482,39 @@ class ModelUpdateService:
exc,
exc_info=True,
)
fallback_error_message = str(exc)
if response is not None:
extracted = self._extract_versions(response)
if extracted is not None:
fetched_versions = extracted
refresh_succeeded = True
elif fallback_attempted and fallback_error_message is None:
fallback_error_message = "no versions returned"
elif fallback_attempted and fallback_error_message is None:
fallback_error_message = "no response"
if fallback_attempted:
if refresh_succeeded and isinstance(fetched_versions, list):
logger.info(
"Fetched metadata via single lookup for model %s (%s); received %d versions",
model_id,
model_type,
len(fetched_versions),
)
elif mark_model_as_ignored:
logger.info(
"Single lookup for model %s (%s) reported missing remote resource: %s",
model_id,
model_type,
fallback_error_message or "resource not found",
)
else:
logger.warning(
"Single lookup for model %s (%s) failed: %s",
model_id,
model_type,
fallback_error_message or "unknown error",
)
async with self._lock:
existing = self._get_record(model_type, model_id)
@@ -491,6 +526,23 @@ class ModelUpdateService:
self._upsert_record(record)
return record
if mark_model_as_ignored:
record = self._merge_with_local_versions(
existing,
normalized_local,
model_type=model_type,
model_id=model_id,
last_checked_at=now,
)
record = replace(record, should_ignore_model=True)
self._upsert_record(record)
logger.info(
"Marked model %s (%s) as ignored after remote resource was not found",
model_id,
model_type,
)
return record
if refresh_succeeded and isinstance(fetched_versions, list):
record = self._build_record_from_remote(
model_type,