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()
|
payload = await request.json()
|
||||||
result = accumulator_delete_entries(
|
result = accumulator_delete_entries(
|
||||||
store_key=str(payload.get("store_key") or ""),
|
store_key=str(payload.get("store_key") or ""),
|
||||||
|
preview_key=str(payload.get("preview_key") or ""),
|
||||||
entry_id=str(payload.get("entry_id") or ""),
|
entry_id=str(payload.get("entry_id") or ""),
|
||||||
index=int(payload.get("index") or 0),
|
index=int(payload.get("index") or 0),
|
||||||
clear=bool(payload.get("clear")),
|
clear=bool(payload.get("clear")),
|
||||||
@@ -537,6 +538,7 @@ if PromptServer is not None and web is not None:
|
|||||||
payload = await request.json()
|
payload = await request.json()
|
||||||
result = accumulator_move_entry(
|
result = accumulator_move_entry(
|
||||||
store_key=str(payload.get("store_key") or ""),
|
store_key=str(payload.get("store_key") or ""),
|
||||||
|
preview_key=str(payload.get("preview_key") or ""),
|
||||||
entry_id=str(payload.get("entry_id") or ""),
|
entry_id=str(payload.get("entry_id") or ""),
|
||||||
index=int(payload.get("index") or 0),
|
index=int(payload.get("index") or 0),
|
||||||
direction=str(payload.get("direction") or "up"),
|
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
|
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:
|
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]
|
images = [entry.get("image") for entry in store if entry.get("image") is not None]
|
||||||
shapes = []
|
shapes = []
|
||||||
@@ -252,19 +264,23 @@ def accumulator_list_entries(store_key: str, preview_limit: int = 0) -> dict[str
|
|||||||
if not key:
|
if not key:
|
||||||
raise ValueError("store_key is required for accumulator preview actions")
|
raise ValueError("store_key is required for accumulator preview actions")
|
||||||
store = _ACCUMULATOR_STORES.setdefault(key, [])
|
store = _ACCUMULATOR_STORES.setdefault(key, [])
|
||||||
|
entries = _entry_infos(store)
|
||||||
result = {
|
result = {
|
||||||
"store_key": key,
|
"store_key": key,
|
||||||
"entries": _entry_infos(store),
|
"entries": entries,
|
||||||
"count": len(store),
|
"count": len(store),
|
||||||
"status": _accumulator_status(key, store),
|
"status": _accumulator_status(key, store),
|
||||||
}
|
}
|
||||||
if int(preview_limit) > 0:
|
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
|
return result
|
||||||
|
|
||||||
|
|
||||||
def accumulator_delete_entries(
|
def accumulator_delete_entries(
|
||||||
store_key: str,
|
store_key: str,
|
||||||
|
preview_key: str = "",
|
||||||
entry_id: str = "",
|
entry_id: str = "",
|
||||||
index: int = 0,
|
index: int = 0,
|
||||||
clear: bool = False,
|
clear: bool = False,
|
||||||
@@ -279,8 +295,13 @@ def accumulator_delete_entries(
|
|||||||
removed = len(store)
|
removed = len(store)
|
||||||
store.clear()
|
store.clear()
|
||||||
else:
|
else:
|
||||||
|
preview_key = str(preview_key or "").strip()
|
||||||
entry_id = str(entry_id 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)
|
before = len(store)
|
||||||
store[:] = [entry for entry in store if str(entry.get("id") or "") != entry_id]
|
store[:] = [entry for entry in store if str(entry.get("id") or "") != entry_id]
|
||||||
removed = before - len(store)
|
removed = before - len(store)
|
||||||
@@ -298,6 +319,7 @@ def accumulator_delete_entries(
|
|||||||
|
|
||||||
def accumulator_move_entry(
|
def accumulator_move_entry(
|
||||||
store_key: str,
|
store_key: str,
|
||||||
|
preview_key: str = "",
|
||||||
entry_id: str = "",
|
entry_id: str = "",
|
||||||
index: int = 0,
|
index: int = 0,
|
||||||
direction: str = "up",
|
direction: str = "up",
|
||||||
@@ -313,8 +335,14 @@ def accumulator_move_entry(
|
|||||||
result["moved"] = False
|
result["moved"] = False
|
||||||
return result
|
return result
|
||||||
zero_index = -1
|
zero_index = -1
|
||||||
|
preview_key = str(preview_key or "").strip()
|
||||||
entry_id = str(entry_id 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):
|
for current_index, entry in enumerate(store):
|
||||||
if str(entry.get("id") or "") == entry_id:
|
if str(entry.get("id") or "") == entry_id:
|
||||||
zero_index = current_index
|
zero_index = current_index
|
||||||
@@ -1121,8 +1149,9 @@ class SxCPAccumulatorPreview:
|
|||||||
images = []
|
images = []
|
||||||
save_status += "; cleared_after_save"
|
save_status += "; cleared_after_save"
|
||||||
|
|
||||||
preview_images = _preview_image_results(store, preview_limit, prompt, extra_pnginfo)
|
|
||||||
entries = _entry_infos(store)
|
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)
|
status = _accumulator_status(key, store)
|
||||||
if removed:
|
if removed:
|
||||||
status += f"; removed={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 EXTENSION = "ethanfel.prompt_builder.accumulator_preview";
|
||||||
const NODE_NAME = "SxCPAccumulatorPreview";
|
const NODE_NAME = "SxCPAccumulatorPreview";
|
||||||
const STYLE_ID = "sxcp-accumulator-preview-styles";
|
const STYLE_ID = "sxcp-accumulator-preview-styles";
|
||||||
|
const DEBUG_STORAGE_KEY = "sxcpAccumulatorPreviewDebug";
|
||||||
|
|
||||||
const MIN_CELL_W = 180;
|
const MIN_CELL_W = 180;
|
||||||
const GAP = 6;
|
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)];
|
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) {
|
function asArray(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
return Array.isArray(value) ? value : [value];
|
return Array.isArray(value) ? value : [value];
|
||||||
@@ -93,6 +150,9 @@ function entryKey(entry) {
|
|||||||
function buildImageMap(entries, images, previous = new Map()) {
|
function buildImageMap(entries, images, previous = new Map()) {
|
||||||
const next = new Map(previous);
|
const next = new Map(previous);
|
||||||
const imageEntries = asArray(entries).filter((entry) => entry?.has_image);
|
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) => {
|
asArray(images).filter(Boolean).forEach((image, index) => {
|
||||||
const directKey = String(image?.preview_key || "").trim();
|
const directKey = String(image?.preview_key || "").trim();
|
||||||
if (directKey) {
|
if (directKey) {
|
||||||
@@ -111,6 +171,7 @@ function buildImageMap(entries, images, previous = new Map()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function imageParamsForEntry(node, entry, fallbackIndex) {
|
function imageParamsForEntry(node, entry, fallbackIndex) {
|
||||||
|
if (entry?.preview_image) return entry.preview_image;
|
||||||
const keyed = node._sxapImageByKey?.get(entryKey(entry));
|
const keyed = node._sxapImageByKey?.get(entryKey(entry));
|
||||||
if (keyed) return keyed;
|
if (keyed) return keyed;
|
||||||
return (node._sxapImages || [])[fallbackIndex];
|
return (node._sxapImages || [])[fallbackIndex];
|
||||||
@@ -137,12 +198,14 @@ function entryTitle(entry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function postJson(path, payload) {
|
async function postJson(path, payload) {
|
||||||
|
debugLog("POST", path, payload);
|
||||||
const response = await api.fetchApi(path, {
|
const response = await api.fetchApi(path, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: {"Content-Type": "application/json"},
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
debugLog("POST response", path, data);
|
||||||
if (!response.ok) throw new Error(data?.error || response.statusText);
|
if (!response.ok) throw new Error(data?.error || response.statusText);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -299,17 +362,37 @@ function renderCell(node, entry, imageParams, displayIndex) {
|
|||||||
const source = node._sxapDragEntry;
|
const source = node._sxapDragEntry;
|
||||||
node._sxapDragEntry = null;
|
node._sxapDragEntry = null;
|
||||||
if (source.index === entry.index) return;
|
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);
|
await moveEntryToIndex(node, source, entry.index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const thumb = document.createElement("img");
|
const thumb = document.createElement("img");
|
||||||
thumb.className = "sxap-thumb";
|
thumb.className = "sxap-thumb";
|
||||||
thumb.style.height = `${Math.max(48, Math.round(cellW * entryAspect(entry)))}px`;
|
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.draggable = true;
|
||||||
thumb.onclick = () => markSelected(node, entry.index);
|
thumb.onclick = () => markSelected(node, entry.index);
|
||||||
thumb.ondragstart = (event) => {
|
thumb.ondragstart = (event) => {
|
||||||
node._sxapDragEntry = entry;
|
node._sxapDragEntry = entry;
|
||||||
|
debugLog("dragstart", {index: entry.index, key: entryKey(entry), id: entry.id});
|
||||||
if (event.dataTransfer) {
|
if (event.dataTransfer) {
|
||||||
event.dataTransfer.effectAllowed = "move";
|
event.dataTransfer.effectAllowed = "move";
|
||||||
event.dataTransfer.setData("text/plain", String(entry.id || entry.index || ""));
|
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.";
|
empty.textContent = storeKey(node) ? "No accumulator images." : "Run once or set an explicit store_key.";
|
||||||
grid.appendChild(empty);
|
grid.appendChild(empty);
|
||||||
} else {
|
} 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) => {
|
entries.forEach((entry, index) => {
|
||||||
grid.appendChild(renderCell(node, entry, imageParamsForEntry(node, entry, index), index));
|
grid.appendChild(renderCell(node, entry, imageParamsForEntry(node, entry, index), index));
|
||||||
});
|
});
|
||||||
@@ -428,8 +517,9 @@ async function deleteEntry(node, entry) {
|
|||||||
try {
|
try {
|
||||||
const data = await postJson("/sxcp/accumulator/delete", actionPayload(node, {
|
const data = await postJson("/sxcp/accumulator/delete", actionPayload(node, {
|
||||||
store_key: key,
|
store_key: key,
|
||||||
|
preview_key: entry.preview_key || "",
|
||||||
entry_id: entry.id || "",
|
entry_id: entry.id || "",
|
||||||
index: entry.id ? 0 : entry.index,
|
index: entry.preview_key || entry.id ? 0 : entry.index,
|
||||||
clear: false,
|
clear: false,
|
||||||
}));
|
}));
|
||||||
applyData(node, data, `${data.status || ""}; deleted=${data.removed || 0}`);
|
applyData(node, data, `${data.status || ""}; deleted=${data.removed || 0}`);
|
||||||
@@ -447,12 +537,33 @@ async function moveEntryToIndex(node, entry, targetIndex) {
|
|||||||
}
|
}
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
try {
|
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, {
|
const data = await postJson("/sxcp/accumulator/move", actionPayload(node, {
|
||||||
store_key: key,
|
store_key: key,
|
||||||
|
preview_key: entry.preview_key || "",
|
||||||
entry_id: entry.id || "",
|
entry_id: entry.id || "",
|
||||||
index: entry.id ? 0 : entry.index,
|
index: entry.preview_key || entry.id ? 0 : entry.index,
|
||||||
target_index: targetIndex,
|
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"}`);
|
applyData(node, data, `${data.status || ""}; moved=${data.moved ? "yes" : "no"}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[${EXTENSION}] drag move failed`, err);
|
console.error(`[${EXTENSION}] drag move failed`, err);
|
||||||
@@ -599,6 +710,7 @@ app.registerExtension({
|
|||||||
name: EXTENSION,
|
name: EXTENSION,
|
||||||
|
|
||||||
async setup() {
|
async setup() {
|
||||||
|
installDebugHelpers();
|
||||||
api.addEventListener("executed", ({detail}) => {
|
api.addEventListener("executed", ({detail}) => {
|
||||||
const node = getNodeById(detail?.display_node ?? detail?.node);
|
const node = getNodeById(detail?.display_node ?? detail?.node);
|
||||||
if (!isAccumulatorPreviewNode(node)) return;
|
if (!isAccumulatorPreviewNode(node)) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user