a8d8b3792c
- quick: incremental rescan — re-walks only folders whose mtime changed
(per-dir snapshot persisted to cache/scan_snapshot.json); reuses
the cache for unchanged folders. Catches new/removed/renamed files.
- register: append specific file path(s) with NO folder walk (instant disk-wise)
- full: unchanged default (clear cache -> full re-walk)
Frontend exposes all three as Extensions-menu commands; the graph node gains a
quick/full mode widget. POST /tenaciousload/refresh now takes {mode, folder, files}.
Unit-tested: incremental scan rescans only the changed dir; register adds/skips.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
97 lines
3.5 KiB
JavaScript
97 lines
3.5 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
||
import { api } from "../../scripts/api.js";
|
||
|
||
let busy = false;
|
||
|
||
function notify(severity, detail, life = 6000) {
|
||
const toast = app.extensionManager?.toast;
|
||
if (toast?.add) {
|
||
toast.add({ severity, summary: "Tenaciousload", detail, life });
|
||
} else if (severity === "error") {
|
||
alert("Tenaciousload: " + detail);
|
||
}
|
||
console[severity === "error" ? "error" : "log"]("[Tenaciousload]", detail);
|
||
}
|
||
|
||
async function runRefresh(mode, extra) {
|
||
if (busy) {
|
||
notify("warn", "A refresh is already running…");
|
||
return;
|
||
}
|
||
busy = true;
|
||
const label =
|
||
mode === "quick" ? "Quick refresh (changed folders)" :
|
||
mode === "register" ? "Registering file(s)" : "Full refresh (rescan all)";
|
||
notify("info", `${label} — rebuilding model lists… this can take a moment.`, 10000);
|
||
try {
|
||
// 1) run the chosen refresh mode on the server
|
||
const res = await api.fetchApi("/tenaciousload/refresh", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ mode, ...(extra || {}) }),
|
||
});
|
||
const data = await res.json().catch(() => ({}));
|
||
// 2) rebuild + re-cache object_info (the slow build, if any)
|
||
await api.fetchApi("/object_info?nocache=1").then((r) => r.arrayBuffer());
|
||
// 3) update open dropdowns live if supported
|
||
try { await app.refreshComboInNodes?.(); } catch (e) { /* non-fatal */ }
|
||
|
||
let detail = "Done — new models are available (reload the page if a dropdown still looks stale).";
|
||
if (mode === "register") {
|
||
detail = `Registered ${data.added || 0} file(s)` +
|
||
(data.skipped ? `, ${data.skipped} not found on disk` : "") + ". " + detail;
|
||
} else if (mode === "quick") {
|
||
const dirs = (data.folders || []).reduce((a, f) => a + (f.scanned || 0), 0);
|
||
detail = `Quick scan: ${dirs} folder(s) rescanned. ` + detail;
|
||
}
|
||
notify("success", detail, 8000);
|
||
} catch (e) {
|
||
notify("error", "Refresh failed: " + (e?.message || e), 10000);
|
||
} finally {
|
||
busy = false;
|
||
}
|
||
}
|
||
|
||
async function doRegister() {
|
||
const folder = (prompt("Model folder type (loras, checkpoints, vae, …):", "loras") || "").trim();
|
||
if (!folder) return;
|
||
const raw = prompt(
|
||
`New file path(s) relative to the '${folder}' folder\n(comma- or newline-separated, e.g. mypack/newlora.safetensors):`,
|
||
"",
|
||
);
|
||
if (!raw) return;
|
||
const files = raw.split(/[\n,]+/).map((s) => s.trim()).filter(Boolean);
|
||
if (!files.length) return;
|
||
await runRefresh("register", { folder, files });
|
||
}
|
||
|
||
app.registerExtension({
|
||
name: "Tenaciousload.Refresh",
|
||
commands: [
|
||
{
|
||
id: "Tenaciousload.quick",
|
||
label: "⚡ Quick refresh (changed folders)",
|
||
icon: "pi pi-bolt",
|
||
function: () => runRefresh("quick"),
|
||
},
|
||
{
|
||
id: "Tenaciousload.full",
|
||
label: "🔄 Full refresh (rescan all models/LoRAs)",
|
||
icon: "pi pi-refresh",
|
||
function: () => runRefresh("full"),
|
||
},
|
||
{
|
||
id: "Tenaciousload.register",
|
||
label: "➕ Register new model file…",
|
||
icon: "pi pi-plus",
|
||
function: doRegister,
|
||
},
|
||
],
|
||
menuCommands: [
|
||
{
|
||
path: ["Extensions"],
|
||
commands: ["Tenaciousload.quick", "Tenaciousload.full", "Tenaciousload.register"],
|
||
},
|
||
],
|
||
});
|