Add accumulator preview reorder diagnostics
This commit is contained in:
@@ -522,6 +522,7 @@ if PromptServer is not None and web is not None:
|
||||
payload = await request.json()
|
||||
result = accumulator_delete_entries(
|
||||
store_key=str(payload.get("store_key") or ""),
|
||||
preview_key=str(payload.get("preview_key") or ""),
|
||||
entry_id=str(payload.get("entry_id") or ""),
|
||||
index=int(payload.get("index") or 0),
|
||||
clear=bool(payload.get("clear")),
|
||||
@@ -537,6 +538,7 @@ if PromptServer is not None and web is not None:
|
||||
payload = await request.json()
|
||||
result = accumulator_move_entry(
|
||||
store_key=str(payload.get("store_key") or ""),
|
||||
preview_key=str(payload.get("preview_key") or ""),
|
||||
entry_id=str(payload.get("entry_id") or ""),
|
||||
index=int(payload.get("index") or 0),
|
||||
direction=str(payload.get("direction") or "up"),
|
||||
|
||||
+34
-5
@@ -236,6 +236,18 @@ def _entry_infos(store: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return entries
|
||||
|
||||
|
||||
def _attach_preview_images(entries: list[dict[str, Any]], images: list[dict[str, str]]) -> None:
|
||||
by_key = {
|
||||
str(image.get("preview_key") or ""): image
|
||||
for image in images
|
||||
if str(image.get("preview_key") or "")
|
||||
}
|
||||
for entry in entries:
|
||||
image = by_key.get(str(entry.get("preview_key") or ""))
|
||||
if image:
|
||||
entry["preview_image"] = image
|
||||
|
||||
|
||||
def _accumulator_status(key: str, store: list[dict[str, Any]]) -> str:
|
||||
images = [entry.get("image") for entry in store if entry.get("image") is not None]
|
||||
shapes = []
|
||||
@@ -252,19 +264,23 @@ def accumulator_list_entries(store_key: str, preview_limit: int = 0) -> dict[str
|
||||
if not key:
|
||||
raise ValueError("store_key is required for accumulator preview actions")
|
||||
store = _ACCUMULATOR_STORES.setdefault(key, [])
|
||||
entries = _entry_infos(store)
|
||||
result = {
|
||||
"store_key": key,
|
||||
"entries": _entry_infos(store),
|
||||
"entries": entries,
|
||||
"count": len(store),
|
||||
"status": _accumulator_status(key, store),
|
||||
}
|
||||
if int(preview_limit) > 0:
|
||||
result["images"] = _preview_image_results(store, preview_limit, None, None)
|
||||
images = _preview_image_results(store, preview_limit, None, None)
|
||||
_attach_preview_images(entries, images)
|
||||
result["images"] = images
|
||||
return result
|
||||
|
||||
|
||||
def accumulator_delete_entries(
|
||||
store_key: str,
|
||||
preview_key: str = "",
|
||||
entry_id: str = "",
|
||||
index: int = 0,
|
||||
clear: bool = False,
|
||||
@@ -279,8 +295,13 @@ def accumulator_delete_entries(
|
||||
removed = len(store)
|
||||
store.clear()
|
||||
else:
|
||||
preview_key = str(preview_key or "").strip()
|
||||
entry_id = str(entry_id or "").strip()
|
||||
if entry_id:
|
||||
if preview_key:
|
||||
before = len(store)
|
||||
store[:] = [entry for entry in store if _entry_preview_key(entry) != preview_key]
|
||||
removed = before - len(store)
|
||||
elif entry_id:
|
||||
before = len(store)
|
||||
store[:] = [entry for entry in store if str(entry.get("id") or "") != entry_id]
|
||||
removed = before - len(store)
|
||||
@@ -298,6 +319,7 @@ def accumulator_delete_entries(
|
||||
|
||||
def accumulator_move_entry(
|
||||
store_key: str,
|
||||
preview_key: str = "",
|
||||
entry_id: str = "",
|
||||
index: int = 0,
|
||||
direction: str = "up",
|
||||
@@ -313,8 +335,14 @@ def accumulator_move_entry(
|
||||
result["moved"] = False
|
||||
return result
|
||||
zero_index = -1
|
||||
preview_key = str(preview_key or "").strip()
|
||||
entry_id = str(entry_id or "").strip()
|
||||
if entry_id:
|
||||
if preview_key:
|
||||
for current_index, entry in enumerate(store):
|
||||
if _entry_preview_key(entry) == preview_key:
|
||||
zero_index = current_index
|
||||
break
|
||||
elif entry_id:
|
||||
for current_index, entry in enumerate(store):
|
||||
if str(entry.get("id") or "") == entry_id:
|
||||
zero_index = current_index
|
||||
@@ -1121,8 +1149,9 @@ class SxCPAccumulatorPreview:
|
||||
images = []
|
||||
save_status += "; cleared_after_save"
|
||||
|
||||
preview_images = _preview_image_results(store, preview_limit, prompt, extra_pnginfo)
|
||||
entries = _entry_infos(store)
|
||||
preview_images = _preview_image_results(store, preview_limit, prompt, extra_pnginfo)
|
||||
_attach_preview_images(entries, preview_images)
|
||||
status = _accumulator_status(key, store)
|
||||
if removed:
|
||||
status += f"; removed={removed}"
|
||||
|
||||
+115
-3
@@ -4,6 +4,7 @@ import { api } from "../../scripts/api.js";
|
||||
const EXTENSION = "ethanfel.prompt_builder.accumulator_preview";
|
||||
const NODE_NAME = "SxCPAccumulatorPreview";
|
||||
const STYLE_ID = "sxcp-accumulator-preview-styles";
|
||||
const DEBUG_STORAGE_KEY = "sxcpAccumulatorPreviewDebug";
|
||||
|
||||
const MIN_CELL_W = 180;
|
||||
const GAP = 6;
|
||||
@@ -33,6 +34,62 @@ function getNodeById(id) {
|
||||
return app.graph?.getNodeById?.(Number(id)) || app.graph?._nodes_by_id?.[id] || app.graph?._nodes_by_id?.[Number(id)];
|
||||
}
|
||||
|
||||
function debugEnabled() {
|
||||
try {
|
||||
return window.SXCP_ACCUMULATOR_PREVIEW_DEBUG === true || localStorage.getItem(DEBUG_STORAGE_KEY) === "1";
|
||||
} catch (_err) {
|
||||
return window.SXCP_ACCUMULATOR_PREVIEW_DEBUG === true;
|
||||
}
|
||||
}
|
||||
|
||||
function debugLog(...args) {
|
||||
if (debugEnabled()) console.log(`[${EXTENSION}]`, ...args);
|
||||
}
|
||||
|
||||
function debugWarn(...args) {
|
||||
console.warn(`[${EXTENSION}]`, ...args);
|
||||
}
|
||||
|
||||
function nodeSummaries() {
|
||||
return (app.graph?._nodes || [])
|
||||
.filter(isAccumulatorPreviewNode)
|
||||
.map((node) => ({
|
||||
id: node.id,
|
||||
store_key: storeKey(node),
|
||||
entries: (node._sxapEntries || []).map((entry, index) => ({
|
||||
index: entry.index,
|
||||
id: entry.id,
|
||||
preview_key: entry.preview_key,
|
||||
has_image: entry.has_image,
|
||||
has_preview_image: !!entry.preview_image,
|
||||
image_ref: imageParamsForEntry(node, entry, index),
|
||||
})),
|
||||
images: node._sxapImages || [],
|
||||
status: node._sxapStatus || "",
|
||||
}));
|
||||
}
|
||||
|
||||
function installDebugHelpers() {
|
||||
if (window.sxcpAccumulatorPreviewDebug) return;
|
||||
window.sxcpAccumulatorPreviewDebug = {
|
||||
enable() {
|
||||
localStorage.setItem(DEBUG_STORAGE_KEY, "1");
|
||||
window.SXCP_ACCUMULATOR_PREVIEW_DEBUG = true;
|
||||
console.log(`[${EXTENSION}] debug enabled`);
|
||||
},
|
||||
disable() {
|
||||
localStorage.removeItem(DEBUG_STORAGE_KEY);
|
||||
window.SXCP_ACCUMULATOR_PREVIEW_DEBUG = false;
|
||||
console.log(`[${EXTENSION}] debug disabled`);
|
||||
},
|
||||
dump() {
|
||||
const data = nodeSummaries();
|
||||
console.log(`[${EXTENSION}] dump`, data);
|
||||
return data;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function asArray(value) {
|
||||
if (!value) return [];
|
||||
return Array.isArray(value) ? value : [value];
|
||||
@@ -93,6 +150,9 @@ function entryKey(entry) {
|
||||
function buildImageMap(entries, images, previous = new Map()) {
|
||||
const next = new Map(previous);
|
||||
const imageEntries = asArray(entries).filter((entry) => entry?.has_image);
|
||||
imageEntries.forEach((entry) => {
|
||||
if (entry.preview_image) next.set(entryKey(entry), entry.preview_image);
|
||||
});
|
||||
asArray(images).filter(Boolean).forEach((image, index) => {
|
||||
const directKey = String(image?.preview_key || "").trim();
|
||||
if (directKey) {
|
||||
@@ -111,6 +171,7 @@ function buildImageMap(entries, images, previous = new Map()) {
|
||||
}
|
||||
|
||||
function imageParamsForEntry(node, entry, fallbackIndex) {
|
||||
if (entry?.preview_image) return entry.preview_image;
|
||||
const keyed = node._sxapImageByKey?.get(entryKey(entry));
|
||||
if (keyed) return keyed;
|
||||
return (node._sxapImages || [])[fallbackIndex];
|
||||
@@ -137,12 +198,14 @@ function entryTitle(entry) {
|
||||
}
|
||||
|
||||
async function postJson(path, payload) {
|
||||
debugLog("POST", path, payload);
|
||||
const response = await api.fetchApi(path, {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await response.json();
|
||||
debugLog("POST response", path, data);
|
||||
if (!response.ok) throw new Error(data?.error || response.statusText);
|
||||
return data;
|
||||
}
|
||||
@@ -299,17 +362,37 @@ function renderCell(node, entry, imageParams, displayIndex) {
|
||||
const source = node._sxapDragEntry;
|
||||
node._sxapDragEntry = null;
|
||||
if (source.index === entry.index) return;
|
||||
debugLog("drop", {
|
||||
source_index: source.index,
|
||||
source_key: entryKey(source),
|
||||
target_index: entry.index,
|
||||
target_key: entryKey(entry),
|
||||
});
|
||||
await moveEntryToIndex(node, source, entry.index);
|
||||
};
|
||||
|
||||
const thumb = document.createElement("img");
|
||||
thumb.className = "sxap-thumb";
|
||||
thumb.style.height = `${Math.max(48, Math.round(cellW * entryAspect(entry)))}px`;
|
||||
if (imageParams) thumb.src = imageUrl(imageParams);
|
||||
if (imageParams) {
|
||||
const url = imageUrl(imageParams);
|
||||
thumb.src = url;
|
||||
thumb.onerror = () => debugWarn("image load failed", {
|
||||
entry: {index: entry.index, id: entry.id, preview_key: entry.preview_key},
|
||||
imageParams,
|
||||
url,
|
||||
});
|
||||
} else {
|
||||
debugWarn("missing image params for entry", {
|
||||
entry: {index: entry.index, id: entry.id, preview_key: entry.preview_key},
|
||||
displayIndex,
|
||||
});
|
||||
}
|
||||
thumb.draggable = true;
|
||||
thumb.onclick = () => markSelected(node, entry.index);
|
||||
thumb.ondragstart = (event) => {
|
||||
node._sxapDragEntry = entry;
|
||||
debugLog("dragstart", {index: entry.index, key: entryKey(entry), id: entry.id});
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("text/plain", String(entry.id || entry.index || ""));
|
||||
@@ -364,6 +447,12 @@ function renderGrid(node) {
|
||||
empty.textContent = storeKey(node) ? "No accumulator images." : "Run once or set an explicit store_key.";
|
||||
grid.appendChild(empty);
|
||||
} else {
|
||||
debugLog("renderGrid", entries.map((entry, index) => ({
|
||||
index: entry.index,
|
||||
key: entryKey(entry),
|
||||
has_preview_image: !!entry.preview_image,
|
||||
has_image_params: !!imageParamsForEntry(node, entry, index),
|
||||
})));
|
||||
entries.forEach((entry, index) => {
|
||||
grid.appendChild(renderCell(node, entry, imageParamsForEntry(node, entry, index), index));
|
||||
});
|
||||
@@ -428,8 +517,9 @@ async function deleteEntry(node, entry) {
|
||||
try {
|
||||
const data = await postJson("/sxcp/accumulator/delete", actionPayload(node, {
|
||||
store_key: key,
|
||||
preview_key: entry.preview_key || "",
|
||||
entry_id: entry.id || "",
|
||||
index: entry.id ? 0 : entry.index,
|
||||
index: entry.preview_key || entry.id ? 0 : entry.index,
|
||||
clear: false,
|
||||
}));
|
||||
applyData(node, data, `${data.status || ""}; deleted=${data.removed || 0}`);
|
||||
@@ -447,12 +537,33 @@ async function moveEntryToIndex(node, entry, targetIndex) {
|
||||
}
|
||||
if (!entry) return;
|
||||
try {
|
||||
debugLog("move request", {
|
||||
entry: {index: entry.index, id: entry.id, preview_key: entry.preview_key},
|
||||
targetIndex,
|
||||
});
|
||||
const data = await postJson("/sxcp/accumulator/move", actionPayload(node, {
|
||||
store_key: key,
|
||||
preview_key: entry.preview_key || "",
|
||||
entry_id: entry.id || "",
|
||||
index: entry.id ? 0 : entry.index,
|
||||
index: entry.preview_key || entry.id ? 0 : entry.index,
|
||||
target_index: targetIndex,
|
||||
}));
|
||||
debugLog("move response summary", {
|
||||
moved: data.moved,
|
||||
from_index: data.from_index,
|
||||
to_index: data.to_index,
|
||||
entries: asArray(data.entries).map((item) => ({
|
||||
index: item.index,
|
||||
id: item.id,
|
||||
preview_key: item.preview_key,
|
||||
has_preview_image: !!item.preview_image,
|
||||
})),
|
||||
images: asArray(data.images).map((item) => ({
|
||||
filename: item.filename,
|
||||
preview_key: item.preview_key,
|
||||
entry_id: item.entry_id,
|
||||
})),
|
||||
});
|
||||
applyData(node, data, `${data.status || ""}; moved=${data.moved ? "yes" : "no"}`);
|
||||
} catch (err) {
|
||||
console.error(`[${EXTENSION}] drag move failed`, err);
|
||||
@@ -599,6 +710,7 @@ app.registerExtension({
|
||||
name: EXTENSION,
|
||||
|
||||
async setup() {
|
||||
installDebugHelpers();
|
||||
api.addEventListener("executed", ({detail}) => {
|
||||
const node = getNodeById(detail?.display_node ?? detail?.node);
|
||||
if (!isAccumulatorPreviewNode(node)) return;
|
||||
|
||||
Reference in New Issue
Block a user