From 24e39ed225bf4086ff9021216c521a8db721a9b7 Mon Sep 17 00:00:00 2001 From: Adam Ladachowski Date: Sat, 14 Feb 2026 03:24:57 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=AC=20Commit=20message:=20Update=20202?= =?UTF-8?q?6-02-14=2003:24:57,=204=20files,=20682=20lines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📁 Files changed: 4 📝 Lines changed: 682 • .coverage • __init__.py • civitai_routes.py • index.html --- .coverage | Bin 151552 -> 151552 bytes tensors/server/__init__.py | 2 + tensors/server/civitai_routes.py | 88 +++++ tensors/server/static/index.html | 592 +++++++++++++++++++++++++++++++ 4 files changed, 682 insertions(+) create mode 100644 tensors/server/civitai_routes.py diff --git a/.coverage b/.coverage index 50b4dd7665f8b6f24177d433e6982e8c3badf0bd..deeb7962f761b0b966bf3ded4589628729d1892a 100644 GIT binary patch delta 21188 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 Cvl!d} delta 20360 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 FastAPI: async def gallery_ui() -> FileResponse: return FileResponse(static_dir / "index.html") + app.include_router(create_civitai_router()) # Must be before catch-all proxy app.include_router(create_db_router()) # Must be before catch-all proxy app.include_router(create_gallery_router()) # Must be before catch-all proxy app.include_router(create_models_router(pm)) # Must be before catch-all proxy diff --git a/tensors/server/civitai_routes.py b/tensors/server/civitai_routes.py new file mode 100644 index 0000000..ab20a1b --- /dev/null +++ b/tensors/server/civitai_routes.py @@ -0,0 +1,88 @@ +"""FastAPI route handlers for CivitAI API endpoints.""" + +from __future__ import annotations + +import logging +from typing import Any + +import httpx +from fastapi import APIRouter, Query, Response +from fastapi.responses import JSONResponse + +from tensors.config import CIVITAI_API_BASE, load_api_key + +logger = logging.getLogger(__name__) + +router = APIRouter(prefix="/api/civitai", tags=["civitai"]) + + +def _get_headers(api_key: str | None) -> dict[str, str]: + """Get headers for CivitAI API requests.""" + headers: dict[str, str] = {} + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + return headers + + +@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"), +) -> dict[str, Any] | Response: + """Search CivitAI models.""" + api_key = load_api_key() + + params: dict[str, Any] = { + "limit": min(limit, 100), + "nsfw": str(nsfw).lower(), + "sort": sort, + } + + if query: + params["query"] = query + if types: + params["types"] = types + if base_models: + params["baseModels"] = base_models + + url = f"{CIVITAI_API_BASE}/models" + + try: + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get(url, params=params, headers=_get_headers(api_key)) + response.raise_for_status() + result: dict[str, Any] = response.json() + return result + except httpx.HTTPStatusError as e: + logger.error("CivitAI API error: %s", e.response.status_code) + return JSONResponse({"error": f"API error: {e.response.status_code}"}, status_code=e.response.status_code) + except httpx.RequestError as e: + logger.error("CivitAI request error: %s", e) + return JSONResponse({"error": f"Request error: {e}"}, status_code=500) + + +@router.get("/model/{model_id}", response_model=None) +async def get_model(model_id: int) -> dict[str, Any] | Response: + """Get model details from CivitAI.""" + api_key = load_api_key() + url = f"{CIVITAI_API_BASE}/models/{model_id}" + + try: + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get(url, headers=_get_headers(api_key)) + response.raise_for_status() + result: dict[str, Any] = response.json() + return result + except httpx.HTTPStatusError: + return JSONResponse({"error": "Model not found"}, status_code=404) + except httpx.RequestError as e: + return JSONResponse({"error": f"Request error: {e}"}, status_code=500) + + +def create_civitai_router() -> APIRouter: + """Return the CivitAI API router.""" + return router diff --git a/tensors/server/static/index.html b/tensors/server/static/index.html index 0b994a2..e0db58a 100644 --- a/tensors/server/static/index.html +++ b/tensors/server/static/index.html @@ -210,6 +210,296 @@ .empty { text-align: center; padding: 4rem 1rem; color: #666; } .error { color: #dc2626; } + + /* Search view */ + #search-view { + display: flex; + flex-direction: column; + } + .search-header { + padding: 1rem; + background: #0f0f0f; + border-bottom: 1px solid #333; + } + @media (min-width: 768px) { + .search-header { padding: 1.5rem; } + } + .search-row { + display: flex; + gap: 10px; + max-width: 1000px; + margin: 0 auto; + flex-wrap: wrap; + } + .search-row input[type="text"] { + flex: 1; + min-width: 200px; + padding: 0.75rem 1rem; + border: 1px solid #333; + border-radius: 8px; + background: #1a1a1a; + color: #e5e5e5; + font-size: 1rem; + } + .search-row input[type="text"]::placeholder { color: #666; } + .search-row input[type="text"]:focus { + outline: none; + border-color: #4ade80; + } + .search-row select { + padding: 0.75rem 1rem; + border: 1px solid #333; + border-radius: 8px; + background: #1a1a1a; + color: #e5e5e5; + font-size: 0.9rem; + cursor: pointer; + } + .search-row select:focus { + outline: none; + border-color: #4ade80; + } + .search-row button { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 8px; + background: #4ade80; + color: #0f0f0f; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; + } + .search-row button:hover { background: #22c55e; } + .search-row button:disabled { background: #333; color: #666; cursor: not-allowed; } + + .search-results { + flex: 1; + overflow-y: auto; + padding: 1rem; + } + @media (min-width: 768px) { + .search-results { padding: 1.5rem; } + } + + /* Model cards */ + .model-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1rem; + } + @media (min-width: 768px) { + .model-grid { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1.5rem; } + } + .model-card { + background: #1a1a1a; + border: 1px solid #333; + border-radius: 12px; + overflow: hidden; + transition: border-color 0.2s, transform 0.2s; + } + .model-card:hover { + border-color: #4ade80; + transform: translateY(-2px); + } + .model-card-image { + width: 100%; + aspect-ratio: 16/10; + object-fit: cover; + background: #0f0f0f; + } + .model-card-body { + padding: 1rem; + } + .model-card-title { + font-size: 1rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: #e5e5e5; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + .model-card-meta { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 0.75rem; + } + .model-tag { + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 500; + } + .model-tag.type { + background: #4ade80; + color: #0f0f0f; + } + .model-tag.base { + background: #333; + color: #e5e5e5; + } + .model-tag.nsfw { + background: #dc2626; + color: #fff; + } + .model-card-stats { + display: flex; + gap: 1rem; + font-size: 0.8rem; + color: #888; + } + .model-card-stats span { + display: flex; + align-items: center; + gap: 0.25rem; + } + .model-card-stats svg { + width: 14px; + height: 14px; + } + .model-card-footer { + padding: 0.75rem 1rem; + border-top: 1px solid #333; + display: flex; + justify-content: space-between; + align-items: center; + } + .model-card-creator { + font-size: 0.8rem; + color: #888; + } + .model-card-actions { + display: flex; + gap: 0.5rem; + } + .model-card-actions button { + padding: 0.5rem 0.75rem; + border: none; + border-radius: 6px; + font-size: 0.8rem; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; + } + .btn-view { + background: #333; + color: #e5e5e5; + } + .btn-view:hover { background: #444; } + .btn-download { + background: #4ade80; + color: #0f0f0f; + } + .btn-download:hover { background: #22c55e; } + + /* Modal */ + .modal-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.8); + z-index: 1000; + align-items: center; + justify-content: center; + padding: 1rem; + } + .modal-overlay.active { display: flex; } + .modal { + background: #1a1a1a; + border: 1px solid #333; + border-radius: 12px; + max-width: 600px; + width: 100%; + max-height: 90vh; + overflow-y: auto; + } + .modal-header { + padding: 1rem; + border-bottom: 1px solid #333; + display: flex; + justify-content: space-between; + align-items: center; + } + .modal-header h3 { + font-size: 1.1rem; + font-weight: 600; + } + .modal-close { + background: none; + border: none; + color: #888; + cursor: pointer; + padding: 0.25rem; + } + .modal-close:hover { color: #e5e5e5; } + .modal-body { + padding: 1rem; + } + .modal-images { + display: flex; + gap: 0.5rem; + overflow-x: auto; + padding-bottom: 0.5rem; + margin-bottom: 1rem; + } + .modal-images img { + width: 120px; + height: 120px; + object-fit: cover; + border-radius: 8px; + cursor: pointer; + border: 2px solid transparent; + } + .modal-images img:hover { border-color: #4ade80; } + .modal-info { + display: grid; + gap: 0.75rem; + } + .modal-info-row { + display: flex; + justify-content: space-between; + font-size: 0.9rem; + } + .modal-info-row label { + color: #888; + } + .modal-info-row span { + color: #e5e5e5; + } + .modal-versions { + margin-top: 1rem; + } + .modal-versions h4 { + font-size: 0.9rem; + color: #888; + margin-bottom: 0.5rem; + } + .version-list { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + .version-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem; + background: #0f0f0f; + border-radius: 8px; + } + .version-name { + font-size: 0.9rem; + } + .version-base { + font-size: 0.75rem; + color: #888; + } @@ -219,6 +509,11 @@ + + + +
+
Search for models on CivitAI
+
+ + + + +