feat(model-manager): flag Early Access models in CivitAI browse
CivitAI Early Access versions require purchased access and otherwise fail with 401. Surface version `availability` from the API as an `early_access` flag (per card and per version), show an amber "EARLY ACCESS" tag on the card, label such entries in the version dropdown, and warn before attempting to download one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -240,7 +240,8 @@ SparkyUI includes a **StabilityMatrix-style Model Manager** - a lightweight Fast
|
||||
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.
|
||||
Multi-version models get a version picker on the card. **Early Access** versions are
|
||||
flagged (they require purchased access on CivitAI and otherwise fail with 401).
|
||||
- **Installed Models** - browse what's on disk, grouped by type, with size and delete actions
|
||||
- **Add / Download** - paste a download URL and pick a type; live progress bars
|
||||
- **Direct URLs** - any direct download link
|
||||
|
||||
@@ -176,6 +176,12 @@ def _pick_thumbnail(version: dict) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
|
||||
def _is_early_access(version: dict) -> bool:
|
||||
"""A version is early-access (download requires purchase/login) when its
|
||||
availability is anything other than Public."""
|
||||
return (version.get("availability") or "Public") != "Public"
|
||||
|
||||
|
||||
def _to_card(item: dict) -> dict:
|
||||
versions = item.get("modelVersions") or []
|
||||
v0 = versions[0] if versions else {}
|
||||
@@ -192,8 +198,14 @@ def _to_card(item: dict) -> dict:
|
||||
"thumbnail": thumb.get("url") if thumb else None,
|
||||
"thumbnail_type": thumb.get("type") if thumb else None,
|
||||
"primary_version_id": v0.get("id"),
|
||||
"early_access": _is_early_access(v0),
|
||||
"versions": [
|
||||
{"id": v.get("id"), "name": v.get("name")} for v in versions
|
||||
{
|
||||
"id": v.get("id"),
|
||||
"name": v.get("name"),
|
||||
"early_access": _is_early_access(v),
|
||||
}
|
||||
for v in versions
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -477,6 +477,12 @@ function renderBrowseCard(m) {
|
||||
n.textContent = "NSFW";
|
||||
thumb.appendChild(n);
|
||||
}
|
||||
if (m.early_access) {
|
||||
const ea = document.createElement("span");
|
||||
ea.className = "ea-tag";
|
||||
ea.textContent = "EARLY ACCESS";
|
||||
thumb.appendChild(ea);
|
||||
}
|
||||
|
||||
const body = document.createElement("div");
|
||||
body.className = "body";
|
||||
@@ -497,7 +503,8 @@ function renderBrowseCard(m) {
|
||||
for (const v of m.versions) {
|
||||
const o = document.createElement("option");
|
||||
o.value = v.id;
|
||||
o.textContent = v.name || `v${v.id}`;
|
||||
o.textContent = (v.name || `v${v.id}`) + (v.early_access ? " — Early Access" : "");
|
||||
o.dataset.ea = v.early_access ? "1" : "";
|
||||
versionSel.appendChild(o);
|
||||
}
|
||||
foot.appendChild(versionSel);
|
||||
@@ -507,8 +514,15 @@ function renderBrowseCard(m) {
|
||||
btn.className = "btn primary";
|
||||
btn.textContent = "Download";
|
||||
btn.addEventListener("click", () => {
|
||||
const vid = versionSel ? Number(versionSel.value) : m.primary_version_id;
|
||||
downloadVersion(vid, btn);
|
||||
let vid, isEa;
|
||||
if (versionSel) {
|
||||
vid = Number(versionSel.value);
|
||||
isEa = versionSel.selectedOptions[0]?.dataset.ea === "1";
|
||||
} else {
|
||||
vid = m.primary_version_id;
|
||||
isEa = m.early_access;
|
||||
}
|
||||
downloadVersion(vid, btn, isEa);
|
||||
});
|
||||
foot.appendChild(btn);
|
||||
|
||||
@@ -517,8 +531,15 @@ function renderBrowseCard(m) {
|
||||
return card;
|
||||
}
|
||||
|
||||
async function downloadVersion(versionId, btn) {
|
||||
async function downloadVersion(versionId, btn, isEarlyAccess) {
|
||||
if (!versionId) { toast($("#browseToast"), "No version available", true); return; }
|
||||
if (isEarlyAccess) {
|
||||
const ok = await confirmDialog(
|
||||
"This is an Early Access version. CivitAI only allows downloading it if " +
|
||||
"your account has purchased/unlocked access — otherwise it will fail with " +
|
||||
"401. Try anyway?", "Download");
|
||||
if (!ok) return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
const original = btn.textContent;
|
||||
btn.textContent = "Queued ✓";
|
||||
|
||||
@@ -242,6 +242,11 @@ code { background: var(--bg-3); padding: 1px 5px; border-radius: 4px; font-size:
|
||||
background: rgba(239,93,107,.85); color: #fff;
|
||||
font-size: 10px; padding: 2px 7px; border-radius: 8px; font-weight: 600;
|
||||
}
|
||||
.browse-card .thumb .ea-tag {
|
||||
position: absolute; bottom: 8px; left: 8px;
|
||||
background: rgba(240,180,0,.92); color: #1a1400;
|
||||
font-size: 10px; padding: 2px 7px; border-radius: 8px; font-weight: 700;
|
||||
}
|
||||
.browse-card .body { padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
|
||||
.browse-card .b-name { font-weight: 600; font-size: 13px; line-height: 1.25;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
|
||||
Reference in New Issue
Block a user