Files
chrmc40 4a3eb29968 Per-image modal setup persistence (Re-pose / Inpaint / Upscale)
Render modals now remember and restore the user's last setup per image,
surviving close/reopen and restart, via a server-side sidecar keyed by image
content hash.

- New core/modal_setup_api.py sidecar (sibling of edit-docs): POST(merge per
  kind)/GET/DELETE, manifest + binary planes, LRU budget, tombstone-aware.
- Re-pose: AUTO-restores recipe/model/prompt/strength and the full 3D pose on
  open (seed-before-mount), lineage-aware so a re-posed result/edit resumes its
  source's setup. Plus a Re-Apply button.
- Inpaint: restores engine/mode/dials, the PuLID/Style-Reference cluster, the
  prompt, and the painted mask (alpha plane).
- Upscale: restores dials + prompt via an opt-in chip, doc-dims keyed.
- Also: before/after Compare wipe on Upscale + Re-pose; Fit/Compare controls
  relocated with click-fix + double-click-to-fit.
2026-06-18 16:14:29 -07:00

52 lines
2.1 KiB
JavaScript

// Client for the per-image modal-setup sidecar (core/modal_setup_api.py).
// Keyed by the viewer's displayedHash (== the editor's editDocHash). Best-effort:
// every call swallows its own errors and returns a safe value, so persistence
// can never block or break a render. See dev-promptchain/docs/plans/modal-setup-persistence.md.
const isHash = (h) => /^[0-9a-f]{64}$/.test(h || "");
// Returns the whole manifest { version, dims:{w,h}, kinds:{upscale,inpaint,repose} }
// or null when there's nothing saved / no usable key.
export async function loadModalSetup(fetchApi, hash) {
if (!fetchApi || !isHash(hash)) return null;
try {
const res = await fetchApi(`/promptchain/modal-setup/${hash}`);
if (!res?.ok) return null; // 404 = no sidecar yet
const doc = await res.json();
return doc && typeof doc === "object" && doc.kinds ? doc : null;
} catch {
return null;
}
}
// Merge ONE tool's setup into the image's sidecar (server preserves the others).
// `data` is the kind's plain object; `dims` = {w,h} for the restore-time guard;
// `planes` maps "<kind>__<name>.png" -> Blob (e.g. the inpaint mask). Returns ok.
export async function saveModalSetup(fetchApi, hash, kind, data, dims = null, planes = {}) {
if (!fetchApi || !isHash(hash) || !kind) return false;
try {
const fd = new FormData();
fd.append("kind", kind);
fd.append("data", JSON.stringify(data ?? {}));
if (dims) fd.append("dims", JSON.stringify(dims));
for (const [name, blob] of Object.entries(planes)) fd.append(name, blob, name);
const res = await fetchApi(`/promptchain/modal-setup/${hash}`, { method: "POST", body: fd });
return !!res?.ok;
} catch {
return false;
}
}
// Drop one kind (kind set) or the whole image's sidecar (kind null). Used to
// invalidate a stale setup when content is unchanged (same hash).
export async function deleteModalSetup(fetchApi, hash, kind = null) {
if (!fetchApi || !isHash(hash)) return false;
try {
const q = kind ? `?kind=${encodeURIComponent(kind)}` : "";
const res = await fetchApi(`/promptchain/modal-setup/${hash}${q}`, { method: "DELETE" });
return !!res?.ok;
} catch {
return false;
}
}