From 6871a6d4607ed3453ebbafc0f2eb4220d8bd1dd7 Mon Sep 17 00:00:00 2001 From: TBNilles Date: Sun, 7 Jun 2026 16:19:48 -0400 Subject: [PATCH] feat(model-manager): add "Delete all" gallery button + clearer 401 errors - Gallery: DELETE /api/gallery/all removes every image under output/; "Delete all" button with in-app confirm and a deleted/failed count. - Downloads: surface a clear, actionable message when CivitAI/HuggingFace returns 401/403 (model requires login/early-access, or the key/token lacks access) instead of a bare error, both at resolve time and during the download stream. Co-Authored-By: Claude Opus 4.8 --- README.md | 3 ++- model-manager/app/downloader.py | 7 +++++-- model-manager/app/main.py | 26 ++++++++++++++++++++++++++ model-manager/app/static/app.js | 21 +++++++++++++++++++++ model-manager/app/static/index.html | 1 + 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index afd8b63..77164e1 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,8 @@ SparkyUI includes a **StabilityMatrix-style Model Manager** - a lightweight Fast **Features:** - **Gallery** - browse generated photos from ComfyUI's `output/` in a large desktop grid, - click for a full-size lightbox view, and **permanently delete** photos (with confirm). + click for a full-size lightbox view, and **permanently delete** photos one at a time or + all at once (with confirm). - **Browse CivitAI** - search the CivitAI catalog in a thumbnail grid (filter by type, sort, period, NSFW toggle) and **click a model to download it** - no URL pasting needed. Multi-version models get a version picker on the card. diff --git a/model-manager/app/downloader.py b/model-manager/app/downloader.py index f721cc8..beb0140 100644 --- a/model-manager/app/downloader.py +++ b/model-manager/app/downloader.py @@ -111,8 +111,11 @@ async def run_download(download_id: int, url: str, headers: dict[str, str], db.update_download(download_id, status="canceled", error="Canceled by user") _cleanup(part_path) except httpx.HTTPStatusError as exc: - db.update_download(download_id, status="failed", - error=f"HTTP {exc.response.status_code}") + code = exc.response.status_code + msg = f"HTTP {code}" + if code in (401, 403): + msg += " — requires login/early-access or invalid API key" + db.update_download(download_id, status="failed", error=msg) _cleanup(part_path) except Exception as exc: # noqa: BLE001 - surface any failure to the UI db.update_download(download_id, status="failed", error=str(exc)) diff --git a/model-manager/app/main.py b/model-manager/app/main.py index e80de5d..de6f3cf 100644 --- a/model-manager/app/main.py +++ b/model-manager/app/main.py @@ -6,6 +6,7 @@ import os from pathlib import Path from typing import Optional +import httpx from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles @@ -141,6 +142,15 @@ async def start_download(body: DownloadIn) -> dict: try: resolved = await registries.resolve(url) + except httpx.HTTPStatusError as exc: + code = exc.response.status_code + if code in (401, 403): + raise HTTPException( + 400, + f"The source returned {code} for this model. It likely requires " + "being logged in / early access on that site, or your API key/token " + "doesn't have access to it. (Public models download fine.)") + raise HTTPException(400, f"Could not resolve URL (HTTP {code})") except Exception as exc: # noqa: BLE001 - report resolution failure to the user raise HTTPException(400, f"Could not resolve URL: {exc}") @@ -246,6 +256,22 @@ def list_gallery(limit: int = 60, offset: int = 0) -> dict: "offset": offset, "returned": len(page)} +@app.delete("/api/gallery/all") +def delete_all_photos() -> dict: + """Permanently delete every image under OUTPUT_DIR.""" + deleted = 0 + failed = 0 + if OUTPUT_DIR.is_dir(): + for path in OUTPUT_DIR.rglob("*"): + if path.is_file() and path.suffix.lower() in IMAGE_EXTS: + try: + path.unlink() + deleted += 1 + except OSError: + failed += 1 + return {"deleted": deleted, "failed": failed} + + @app.get("/gallery/file") def gallery_file(path: str): try: diff --git a/model-manager/app/static/app.js b/model-manager/app/static/app.js index 8c93017..4932826 100644 --- a/model-manager/app/static/app.js +++ b/model-manager/app/static/app.js @@ -376,7 +376,28 @@ document.addEventListener("keydown", (e) => { $("#lbDelete").addEventListener("click", () => { if (lbCurrent) deletePhoto(lbCurrent.p, lbCurrent.card); }); +async function deleteAllPhotos() { + if (!galleryState.total) { + toast($("#galleryToast"), "No photos to delete", false); + return; + } + const ok = await confirmDialog( + `Permanently delete ALL ${galleryState.total} photo(s)? This cannot be undone.`, + "Delete all"); + if (!ok) return; + try { + const res = await api("/api/gallery/all", { method: "DELETE" }); + let msg = `Deleted ${res.deleted} photo(s)`; + if (res.failed) msg += ` (${res.failed} could not be removed — permissions)`; + toast($("#galleryToast"), msg, res.failed > 0); + loadGallery(true); + } catch (err) { + toast($("#galleryToast"), err.message, true); + } +} + $("#refreshGallery").addEventListener("click", () => loadGallery(true)); +$("#deleteAllGallery").addEventListener("click", deleteAllPhotos); $("#galleryMore").addEventListener("click", () => loadGallery(false)); // ---- browse civitai ------------------------------------------------------- diff --git a/model-manager/app/static/index.html b/model-manager/app/static/index.html index 9d990b8..e5970f8 100644 --- a/model-manager/app/static/index.html +++ b/model-manager/app/static/index.html @@ -58,6 +58,7 @@
+