diff --git a/__init__.py b/__init__.py index 9c8a94c..7c4cc95 100644 --- a/__init__.py +++ b/__init__.py @@ -394,6 +394,7 @@ try: accumulator_delete_entries, accumulator_list_entries, accumulator_move_entry, + accumulator_save_entries, ) from .prompt_builder import ( build_camera_config_json, @@ -471,6 +472,7 @@ except ImportError: accumulator_delete_entries, accumulator_list_entries, accumulator_move_entry, + accumulator_save_entries, ) from prompt_builder import ( build_camera_config_json, @@ -583,6 +585,21 @@ if PromptServer is not None and web is not None: except Exception as exc: return web.json_response({"error": str(exc)}, status=400) + @PromptServer.instance.routes.post("/sxcp/accumulator/save") + async def sxcp_accumulator_save(request): + try: + payload = await request.json() + result = accumulator_save_entries( + store_key=str(payload.get("store_key") or ""), + save_path=str(payload.get("save_path") or "sxcp_accumulator"), + filename_prefix=str(payload.get("filename_prefix") or "sxcp_accum"), + clear_after_save=bool(payload.get("clear_after_save")), + preview_limit=int(payload.get("preview_limit") or 0), + ) + return web.json_response(result) + except Exception as exc: + return web.json_response({"error": str(exc)}, status=400) + @PromptServer.instance.routes.post("/sxcp/accumulator/move") async def sxcp_accumulator_move(request): try: diff --git a/loop_nodes.py b/loop_nodes.py index b6cef0f..f4ccd73 100644 --- a/loop_nodes.py +++ b/loop_nodes.py @@ -317,6 +317,31 @@ def accumulator_delete_entries( return result +def accumulator_save_entries( + store_key: str, + save_path: str = "sxcp_accumulator", + filename_prefix: str = "sxcp_accum", + clear_after_save: bool = False, + preview_limit: int = 0, +) -> dict[str, Any]: + key = str(store_key or "").strip() + if not key: + raise ValueError("store_key is required for accumulator save") + store = _ACCUMULATOR_STORES.setdefault(key, []) + saved_paths = _save_images_to_folder(store, save_path, filename_prefix, None, None) + if saved_paths and bool(clear_after_save): + store.clear() + result = accumulator_list_entries(key, preview_limit=preview_limit) + result["saved_paths"] = saved_paths + result["saved"] = len(saved_paths) + result["cleared_after_save"] = bool(saved_paths and clear_after_save) + result["status"] = ( + f"{result.get('status', '')}; saved={len(saved_paths)}" + f"{'; cleared_after_save' if saved_paths and clear_after_save else ''}" + ) + return result + + def accumulator_move_entry( store_key: str, preview_key: str = "", diff --git a/web/accumulator_preview.js b/web/accumulator_preview.js index aa170ff..99cc777 100644 --- a/web/accumulator_preview.js +++ b/web/accumulator_preview.js @@ -594,26 +594,22 @@ async function clearStore(node) { } async function saveBatch(node) { - const saveWidget = widget(node, "save_batch"); - if (!saveWidget) { - alert("Missing save_batch widget."); + const key = storeKey(node); + if (!key) { + alert("Set the same explicit store_key on the Accumulator and Accumulator Preview first."); return; } - setWidgetValue(node, "finished", true); - setWidgetValue(node, "save_batch", true); - node.setDirtyCanvas?.(true, true); try { - try { - await app.queuePrompt(0, 1); - } catch (_err) { - await app.queuePrompt(0); - } + const data = await postJson("/sxcp/accumulator/save", actionPayload(node, { + store_key: key, + save_path: widget(node, "save_path")?.value || "sxcp_accumulator", + filename_prefix: widget(node, "filename_prefix")?.value || "sxcp_accum", + clear_after_save: !!widget(node, "clear_after_save")?.value, + })); + applyData(node, data, data.status || `saved=${data.saved || 0}`); } catch (err) { - console.error(`[${EXTENSION}] save queue failed`, err); + console.error(`[${EXTENSION}] direct save failed`, err); alert(`Save failed: ${err}`); - } finally { - setWidgetValue(node, "save_batch", false); - node.setDirtyCanvas?.(true, true); } }