💬 Commit message: Update 2026-02-15 18:38:39, 2 files, 121 lines

📁 Files changed: 2
📝 Lines changed: 121

  • README.md
  • civitai_routes.py
This commit is contained in:
Adam Ladachowski
2026-02-15 18:38:39 +01:00
parent ab3660a175
commit f2df2bbefa
2 changed files with 105 additions and 16 deletions
+29 -5
View File
@@ -46,6 +46,15 @@ tsr search -t lora -b sdxl
# Sort by newest, limit results # Sort by newest, limit results
tsr search -t checkpoint -s newest -n 10 tsr search -t checkpoint -s newest -n 10
# Filter by tag and period
tsr search --tag anime -p week -b illustrious
# By creator
tsr search -u "username"
# SFW only with commercial use filter
tsr search --sfw --commercial sell
``` ```
### Get Model Info ### Get Model Info
@@ -240,9 +249,10 @@ local = "http://localhost:51200"
default_remote = "junkpile" default_remote = "junkpile"
``` ```
Or set API key via environment variable: Or set API keys via environment variables:
```bash ```bash
export CIVITAI_API_KEY="your-api-key" export CIVITAI_API_KEY="your-api-key" # For CivitAI API access
export TENSORS_API_KEY="your-server-key" # For server authentication
``` ```
## Default Paths ## Default Paths
@@ -263,9 +273,16 @@ Data is stored in XDG-compliant paths:
| Option | Values | | Option | Values |
|--------|--------| |--------|--------|
| `-t, --type` | checkpoint, lora, embedding, vae, controlnet, locon | | `-t, --type` | checkpoint, lora, embedding, vae, controlnet, locon |
| `-b, --base` | sd15, sdxl, pony, flux, illustrious | | `-b, --base` | sd14, sd15, sd2, sdxl, pony, flux, illustrious, noobai, auraflow |
| `-s, --sort` | downloads, rating, newest | | `-s, --sort` | downloads, rating, newest |
| `-n, --limit` | Number of results (default: 20) | | `-n, --limit` | Number of results (default: 25) |
| `-p, --period` | all, year, month, week, day |
| `--tag` | Filter by tag (e.g., "anime") |
| `-u, --user` | Filter by creator username |
| `--nsfw` | none, soft, mature, x |
| `--sfw` | Exclude NSFW content |
| `--commercial` | none, image, rent, sell |
| `--page` | Page number for pagination |
## Generate Options ## Generate Options
@@ -288,9 +305,16 @@ Data is stored in XDG-compliant paths:
When running `tsr serve`, the following endpoints are available: When running `tsr serve`, the following endpoints are available:
**OpenAPI Documentation:** Visit `/docs` for interactive Scalar API documentation.
**Authentication:** If `TENSORS_API_KEY` is set, all endpoints except `/status` and `/docs` require authentication via:
- Header: `X-API-Key: your-key`
- Query param: `?api_key=your-key`
| Endpoint | Method | Description | | Endpoint | Method | Description |
|----------|--------|-------------| |----------|--------|-------------|
| `/status` | GET | Server status and active model | | `/status` | GET | Server status (public) |
| `/docs` | GET | OpenAPI documentation (public) |
| `/reload` | POST | Hot-reload with new model | | `/reload` | POST | Hot-reload with new model |
| `/api/images` | GET | List gallery images | | `/api/images` | GET | List gallery images |
| `/api/images/{id}` | GET | Get image file | | `/api/images/{id}` | GET | Get image file |
+76 -11
View File
@@ -3,7 +3,8 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any from enum import Enum
from typing import Annotated, Any
import httpx import httpx
from fastapi import APIRouter, Query, Response from fastapi import APIRouter, Query, Response
@@ -17,6 +18,42 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/civitai", tags=["CivitAI"]) router = APIRouter(prefix="/api/civitai", tags=["CivitAI"])
class SortOrder(str, Enum):
"""Sort order options for CivitAI search."""
most_downloaded = "Most Downloaded"
highest_rated = "Highest Rated"
newest = "Newest"
class Period(str, Enum):
"""Time period filter options."""
all = "AllTime"
year = "Year"
month = "Month"
week = "Week"
day = "Day"
class NsfwLevel(str, Enum):
"""NSFW content filter levels."""
none = "None"
soft = "Soft"
mature = "Mature"
x = "X"
class CommercialUse(str, Enum):
"""Commercial use filter options."""
none = "None"
image = "Image"
rent = "Rent"
sell = "Sell"
def _get_headers(api_key: str | None) -> dict[str, str]: def _get_headers(api_key: str | None) -> dict[str, str]:
"""Get headers for CivitAI API requests.""" """Get headers for CivitAI API requests."""
headers: dict[str, str] = {} headers: dict[str, str] = {}
@@ -27,28 +64,56 @@ def _get_headers(api_key: str | None) -> dict[str, str]:
@router.get("/search", response_model=None) @router.get("/search", response_model=None)
async def search_models( async def search_models(
query: str | None = Query(default=None, description="Search query"), query: Annotated[str | None, Query(description="Search query")] = None,
types: str | None = Query(default=None, description="Model type (Checkpoint, LORA, LoCon, etc.)"), types: Annotated[str | None, Query(description="Model type (Checkpoint, LORA, etc.)")] = None,
base_models: str | None = Query(default=None, alias="baseModels", description="Base model (SD 1.5, SDXL 1.0, Pony, etc.)"), base_models: Annotated[str | None, Query(alias="baseModels", description="Base model")] = None,
sort: str = Query(default="Most Downloaded", description="Sort order"), sort: Annotated[SortOrder, Query(description="Sort order")] = SortOrder.most_downloaded,
limit: int = Query(default=20, le=100, description="Max results"), limit: Annotated[int | None, Query(le=100, description="Max results (default: 25)", example=5)] = None,
nsfw: bool = Query(default=True, description="Include NSFW models"), period: Annotated[Period | None, Query(description="Time period filter")] = None,
tag: Annotated[str | None, Query(description="Filter by tag")] = None,
username: Annotated[str | None, Query(description="Filter by creator username")] = None,
page: Annotated[int | None, Query(ge=1, description="Page number")] = None,
nsfw: Annotated[NsfwLevel | None, Query(description="NSFW filter level")] = None,
sfw: Annotated[bool, Query(description="Exclude NSFW content")] = False,
commercial: Annotated[CommercialUse | None, Query(description="Commercial use filter")] = None,
) -> dict[str, Any] | Response: ) -> dict[str, Any] | Response:
"""Search CivitAI models.""" """Search CivitAI models.
Supports all CivitAI search parameters including filters for type, base model,
time period, tags, creator, NSFW level, and commercial use.
"""
api_key = load_api_key() api_key = load_api_key()
actual_limit = limit if limit is not None else 25
params: dict[str, Any] = { params: dict[str, Any] = {
"limit": min(limit, 100), "limit": min(actual_limit, 100),
"nsfw": str(nsfw).lower(), "sort": sort.value,
"sort": sort,
} }
# Handle NSFW filtering
if sfw:
params["nsfw"] = "false"
elif nsfw:
params["nsfwLevel"] = nsfw.value
else:
params["nsfw"] = "true" # Default: include all
if query: if query:
params["query"] = query params["query"] = query
if types: if types:
params["types"] = types params["types"] = types
if base_models: if base_models:
params["baseModels"] = base_models params["baseModels"] = base_models
if period:
params["period"] = period.value
if tag:
params["tag"] = tag
if username:
params["username"] = username
if page:
params["page"] = page
if commercial:
params["allowCommercialUse"] = commercial.value
url = f"{CIVITAI_API_BASE}/models" url = f"{CIVITAI_API_BASE}/models"