From 4cf734dbd6c050fda24c3238dd97a696ec5d6870 Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Fri, 11 Sep 2026 23:47:46 +0100 Subject: [PATCH] feat(civitai): add the mini version endpoint GET /sdapi/v2/civitai/version/mini/{id} exposes /model-versions/mini: the primary file flattened onto the version, every hash, and the permission flags a download needs. CivitVersionMini is its own model because its field set is per-file, not per-version. --- modules/civitai/api_civitai.py | 10 ++++++++++ modules/civitai/client_civitai.py | 13 ++++++++++++- modules/civitai/models_civitai.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/modules/civitai/api_civitai.py b/modules/civitai/api_civitai.py index b568bc31a..b1fd29cdd 100644 --- a/modules/civitai/api_civitai.py +++ b/modules/civitai/api_civitai.py @@ -158,6 +158,15 @@ def get_version_by_hash(hash_str: str, token: str | None = None): return version_to_dict(version) +def get_version_mini(version_id: int, token: str | None = None): + """Download-shaped version view carrying permission and early-access flags.""" + from modules.civitai.client_civitai import client + version = client.get_version_mini(version_id, token=token) + if version is None: + return JSONResponse(content={"error": "version not found"}, status_code=404) + return version_to_dict(version) + + def get_options(): """Get valid types, sort, period, base_models from CivitAI API discovery.""" from modules.civitai.client_civitai import client @@ -687,6 +696,7 @@ def register_api(api): api.add_api_route("/sdapi/v2/civitai/model/{model_id}", get_model, methods=["GET"], tags=["CivitAI"]) api.add_api_route("/sdapi/v2/civitai/version/{version_id}", get_version, methods=["GET"], tags=["CivitAI"]) api.add_api_route("/sdapi/v2/civitai/version/by-hash/{hash_str}", get_version_by_hash, methods=["GET"], tags=["CivitAI"]) + api.add_api_route("/sdapi/v2/civitai/version/mini/{version_id}", get_version_mini, methods=["GET"], tags=["CivitAI"]) api.add_api_route("/sdapi/v2/civitai/options", get_options, methods=["GET"], tags=["CivitAI"]) api.add_api_route("/sdapi/v2/civitai/tags", get_tags, methods=["GET"], tags=["CivitAI"]) api.add_api_route("/sdapi/v2/civitai/creators", get_creators, methods=["GET"], tags=["CivitAI"]) diff --git a/modules/civitai/client_civitai.py b/modules/civitai/client_civitai.py index e94d93bcb..e62a8eb08 100644 --- a/modules/civitai/client_civitai.py +++ b/modules/civitai/client_civitai.py @@ -1,7 +1,7 @@ import os import time from modules.logger import log -from modules.civitai.models_civitai import CivitModel, CivitVersion, CivitImage, CivitSearchResponse, CivitTagResponse, CivitCreatorResponse, CivitUserProfile +from modules.civitai.models_civitai import CivitModel, CivitVersion, CivitVersionMini, CivitImage, CivitSearchResponse, CivitTagResponse, CivitCreatorResponse, CivitUserProfile options_cache: dict = {} @@ -122,6 +122,17 @@ class CivitaiClient: log.error(f'CivitAI get version by hash parse error: hash={hash_str} {e}') return None + def get_version_mini(self, version_id: int, *, token: str | None = None) -> CivitVersionMini | None: + r = self._get(f'/model-versions/mini/{version_id}', token=token) + if r.status_code != 200: + log.error(f'CivitAI get version mini: id={version_id} code={r.status_code}') + return None + try: + return CivitVersionMini.parse_obj(r.json()) + except Exception as e: + log.error(f'CivitAI get version mini parse error: id={version_id} {e}') + return None + def get_images(self, *, model_version_id: int | None = None, limit: int | None = None, token: str | None = None) -> list[CivitImage]: params: dict = {} if model_version_id is not None: diff --git a/modules/civitai/models_civitai.py b/modules/civitai/models_civitai.py index 00c41e2a8..55b6db77a 100644 --- a/modules/civitai/models_civitai.py +++ b/modules/civitai/models_civitai.py @@ -23,6 +23,7 @@ class CivitFileHashes(BaseModel): autov3: str | None = Field(None, alias="AutoV3") crc32: str | None = Field(None, alias="CRC32") blake3: str | None = Field(None, alias="BLAKE3") + sha256_12: str | None = Field(None, alias="SHA256_12") class CivitFileMetadata(BaseModel): @@ -88,6 +89,36 @@ class CivitVersion(BaseModel): return "Unknown" if v in (None, "") else v +class CivitVersionMini(BaseModel): + # primary file flattened onto the version plus permission flags; earlyAccessEndsAt and freeTrialLimit exist only during early access + class Config: + allow_population_by_field_name = True + air: str = "" + version_name: str = Field("", alias="versionName") + model_name: str = Field("", alias="modelName") + user_id: int = Field(0, alias="userId") + base_model: str = Field("Unknown", alias="baseModel") + availability: str = "Unknown" + published_at: str | None = Field(None, alias="publishedAt") + size: float = 0 + file_type: str = Field("", alias="fileType") + file_name: str = Field("", alias="fileName") + format: str = "" + hashes: CivitFileHashes = Field(default_factory=CivitFileHashes) + download_urls: list[str] = Field(default_factory=list, alias="downloadUrls") + can_generate: bool = Field(False, alias="canGenerate") + is_featured: bool = Field(False, alias="isFeatured") + require_auth: bool = Field(False, alias="requireAuth") + check_permission: bool = Field(False, alias="checkPermission") + additional_resource_charge: bool = Field(False, alias="additionalResourceCharge") + payout_enabled: bool = Field(False, alias="payoutEnabled") + minor: bool = False + sfw_only: bool = Field(False, alias="sfwOnly") + fees: list = Field(default_factory=list) + early_access_ends_at: str | None = Field(None, alias="earlyAccessEndsAt") + free_trial_limit: int | None = Field(None, alias="freeTrialLimit") + + class CivitCreator(BaseModel): class Config: allow_population_by_field_name = True