💬 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:
@@ -46,6 +46,15 @@ tsr search -t lora -b sdxl
|
||||
|
||||
# Sort by newest, limit results
|
||||
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
|
||||
@@ -240,9 +249,10 @@ local = "http://localhost:51200"
|
||||
default_remote = "junkpile"
|
||||
```
|
||||
|
||||
Or set API key via environment variable:
|
||||
Or set API keys via environment variables:
|
||||
```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
|
||||
@@ -263,9 +273,16 @@ Data is stored in XDG-compliant paths:
|
||||
| Option | Values |
|
||||
|--------|--------|
|
||||
| `-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 |
|
||||
| `-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
|
||||
|
||||
@@ -288,9 +305,16 @@ Data is stored in XDG-compliant paths:
|
||||
|
||||
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 |
|
||||
|----------|--------|-------------|
|
||||
| `/status` | GET | Server status and active model |
|
||||
| `/status` | GET | Server status (public) |
|
||||
| `/docs` | GET | OpenAPI documentation (public) |
|
||||
| `/reload` | POST | Hot-reload with new model |
|
||||
| `/api/images` | GET | List gallery images |
|
||||
| `/api/images/{id}` | GET | Get image file |
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from enum import Enum
|
||||
from typing import Annotated, Any
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Query, Response
|
||||
@@ -17,6 +18,42 @@ logger = logging.getLogger(__name__)
|
||||
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]:
|
||||
"""Get headers for CivitAI API requests."""
|
||||
headers: dict[str, str] = {}
|
||||
@@ -27,28 +64,56 @@ def _get_headers(api_key: str | None) -> dict[str, str]:
|
||||
|
||||
@router.get("/search", response_model=None)
|
||||
async def search_models(
|
||||
query: str | None = Query(default=None, description="Search query"),
|
||||
types: str | None = Query(default=None, description="Model type (Checkpoint, LORA, LoCon, etc.)"),
|
||||
base_models: str | None = Query(default=None, alias="baseModels", description="Base model (SD 1.5, SDXL 1.0, Pony, etc.)"),
|
||||
sort: str = Query(default="Most Downloaded", description="Sort order"),
|
||||
limit: int = Query(default=20, le=100, description="Max results"),
|
||||
nsfw: bool = Query(default=True, description="Include NSFW models"),
|
||||
query: Annotated[str | None, Query(description="Search query")] = None,
|
||||
types: Annotated[str | None, Query(description="Model type (Checkpoint, LORA, etc.)")] = None,
|
||||
base_models: Annotated[str | None, Query(alias="baseModels", description="Base model")] = None,
|
||||
sort: Annotated[SortOrder, Query(description="Sort order")] = SortOrder.most_downloaded,
|
||||
limit: Annotated[int | None, Query(le=100, description="Max results (default: 25)", example=5)] = None,
|
||||
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:
|
||||
"""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()
|
||||
actual_limit = limit if limit is not None else 25
|
||||
|
||||
params: dict[str, Any] = {
|
||||
"limit": min(limit, 100),
|
||||
"nsfw": str(nsfw).lower(),
|
||||
"sort": sort,
|
||||
"limit": min(actual_limit, 100),
|
||||
"sort": sort.value,
|
||||
}
|
||||
|
||||
# Handle NSFW filtering
|
||||
if sfw:
|
||||
params["nsfw"] = "false"
|
||||
elif nsfw:
|
||||
params["nsfwLevel"] = nsfw.value
|
||||
else:
|
||||
params["nsfw"] = "true" # Default: include all
|
||||
|
||||
if query:
|
||||
params["query"] = query
|
||||
if types:
|
||||
params["types"] = types
|
||||
if 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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user