feat(civitai): show search failures in the models tab

CivitSearchResponse carries the server's error text; the Gradio page
renders it, or a no-results line, in the details area instead of a
blank card list. The legacy route reads items from the response.
This commit is contained in:
CalamitousFelicitousness
2026-09-13 23:41:41 +01:00
parent ef31ce70c7
commit 229db02b14
4 changed files with 22 additions and 16 deletions
+1 -1
View File
@@ -681,7 +681,7 @@ def legacy_get_civitai(
query=query, tag=tag, types=types, sort=sort, period=period,
nsfw=nsfw, limit=limit, base=base, token=token, exact=exact,
)
return [model_to_legacy_dict(m) for m in models]
return [model_to_legacy_dict(m) for m in models.items]
return JSONResponse(content=[], status_code=200)
+4 -3
View File
@@ -141,8 +141,9 @@ class CivitaiClient:
params['favorites'] = 'true'
r = self._get('/models', params=params, token=token)
if r.status_code != 200:
log.error(f'CivitAI search: code={r.status_code} message="{response_message(r)}"')
return CivitSearchResponse()
message = response_message(r)
log.error(f'CivitAI search: code={r.status_code} message="{message}"')
return CivitSearchResponse(error=message)
data = r.json()
if 'items' not in data:
# single model by numeric query — wrap in search response
@@ -155,7 +156,7 @@ class CivitaiClient:
response = CivitSearchResponse.parse_obj(data)
except Exception as e:
log.error(f'CivitAI search parse error: {e}')
return CivitSearchResponse()
return CivitSearchResponse(error='search response could not be parsed')
# /models rejects server-side level filtering and its nsfw boolean leaks
# Mature+ content, so filter on each model's aggregate nsfwLevel here:
# nsfw on keeps every level, nsfw off/unset keeps SFW (None + Soft).
+1
View File
@@ -188,6 +188,7 @@ class CivitSearchResponse(BaseModel):
items: list[CivitModel] = Field(default_factory=list)
metadata: CivitSearchMetadata = Field(default_factory=CivitSearchMetadata)
request_url: str | None = Field(None, alias="requestUrl")
error: str | None = None # server or parse failure text; items is empty when set
class CivitTag(BaseModel):
+16 -12
View File
@@ -1,5 +1,6 @@
import re
import time
from html import escape
from installer import log
from modules.civitai.client_civitai import client
from modules.civitai.models_civitai import CivitModel, CivitSearchResponse
@@ -16,10 +17,10 @@ def search_civitai(
base: str = '',
token: str | None = None,
exact: bool = True,
) -> list[CivitModel]:
) -> CivitSearchResponse:
if not query and not tag and not sort:
log.error('CivitAI: no search criteria provided')
return []
return CivitSearchResponse(error='no search criteria provided')
t0 = time.time()
@@ -35,8 +36,8 @@ def search_civitai(
if model:
t1 = time.time()
log.info(f'CivitAI result: id={query} time={t1 - t0:.2f}')
return [model]
return []
return CivitSearchResponse(items=[model])
return CivitSearchResponse(error=f'model {query} not found')
response: CivitSearchResponse = client.search_models(
query=query,
@@ -61,17 +62,20 @@ def search_civitai(
if any(q_lower in name for name in names):
exact_models.append(model)
result = exact_models if exact_models else all_models
response.items = exact_models if exact_models else all_models
t1 = time.time()
log.info(f'CivitAI result: exact={len(exact_models)} total={len(all_models)} time={t1 - t0:.2f}')
return result
return response
def create_model_cards(all_models: list[CivitModel]) -> str:
details = """
<div id="model-details">
</div>
"""
def create_model_cards(response: CivitSearchResponse) -> str:
if response.error:
notice = f'CivitAI: {escape(response.error)}'
elif not response.items:
notice = 'No models found'
else:
notice = ''
details = f'<div id="model-details">{notice}</div>'
cards = """
<div id="model-cards" class="extra-network-cards">
{cards}
@@ -85,7 +89,7 @@ def create_model_cards(all_models: list[CivitModel]) -> str:
</div>
"""
all_cards = ''
for model in all_models:
for model in response.items:
previews = []
for version in model.versions:
for image in version.images: