- True runs remember captured text per (tab path, node) with a token. Off runs whose widget hadn't received it use the remembered text and update the embedded metadata. IS_CHANGED includes the carried token, so prompts queued ahead pick it up - Scope pushed prompt updates to the tab the run was queued from, fixing captured text overwriting a same-id node in another open workflow - Nodes fetch missed text when a workflow loads or its tab is re-opened - In-memory only; tokens are compared by equality, so restarts are safe - Saver and Passthrough get hidden stash_tab/stash_token inputs (appended last, no socket or widget, not saved in widgets_values)
107 lines
3.7 KiB
Python
107 lines
3.7 KiB
Python
"""
|
|
Backend memory of the latest input text per stash node, so it carries forward
|
|
to later runs even when the frontend never received it.
|
|
|
|
Problem: on an off run (use_input_text false) a stash node outputs its
|
|
`prompt_text` widget -- the copy captured when the prompt was queued. Text
|
|
from a True run only reaches that widget if the workflow is the active tab
|
|
when the update is pushed. Prompts queued ahead, or a tab in the background,
|
|
keep using the old text.
|
|
|
|
Fix: a True run stores its text here under (tab path, node unique_id) with a
|
|
fresh random token. Every queued prompt carries the tab path and the token of
|
|
the text its widget last took in (`stash_tab` / `stash_token`, injected by the
|
|
frontend). On an off run, if the stored token differs from the one sent, the
|
|
frontend didn't have that text yet, so the stored text is used instead.
|
|
|
|
Tokens are compared for equality, never ordered, so a server restart (which
|
|
empties this in-memory store) can never make old text look newer.
|
|
|
|
Keyed by the frontend's tab path rather than the workflow id: the id is kept
|
|
when a workflow is re-opened from an image or saved, so two open tabs can share
|
|
it; two open tabs never share a path.
|
|
"""
|
|
|
|
import threading
|
|
import uuid
|
|
|
|
from aiohttp import web
|
|
from server import PromptServer
|
|
|
|
UPDATE_EVENT = "prompt-stash-update-prompt"
|
|
|
|
_lock = threading.Lock()
|
|
_store = {} # (tab, unique_id) -> {"text": str, "token": str}
|
|
|
|
|
|
def _key(tab, unique_id):
|
|
return (str(tab), str(unique_id))
|
|
|
|
|
|
def store_text(tab, unique_id, text):
|
|
"""Record new input text for a node. Returns the new token, or "" when the
|
|
tab is unknown (no frontend, e.g. API use) and nothing was stored."""
|
|
if not tab or unique_id is None:
|
|
return ""
|
|
token = uuid.uuid4().hex
|
|
with _lock:
|
|
_store[_key(tab, unique_id)] = {"text": str(text), "token": token}
|
|
return token
|
|
|
|
|
|
def replace_text(tab, unique_id, text):
|
|
"""Update the stored text in place, keeping its token (e.g. after the user
|
|
edits it during a pause). No-op if nothing is stored for the node."""
|
|
if not tab or unique_id is None:
|
|
return
|
|
with _lock:
|
|
entry = _store.get(_key(tab, unique_id))
|
|
if entry is not None:
|
|
entry["text"] = str(text)
|
|
|
|
|
|
def get_entry(tab, unique_id):
|
|
if not tab or unique_id is None:
|
|
return None
|
|
with _lock:
|
|
entry = _store.get(_key(tab, unique_id))
|
|
return dict(entry) if entry else None
|
|
|
|
|
|
def carried_entry(tab, unique_id, sent_token):
|
|
"""The stored entry if the frontend hadn't taken it in when this prompt was
|
|
queued (its token differs from the one sent), else None."""
|
|
entry = get_entry(tab, unique_id)
|
|
if entry and entry["token"] != (sent_token or ""):
|
|
return entry
|
|
return None
|
|
|
|
|
|
def push_update(unique_id, text, tab="", token=""):
|
|
"""Tell the frontend about new text for a node. Only the node in the tab
|
|
named `tab` applies it (older frontends ignore the extra fields)."""
|
|
PromptServer.instance.send_sync(UPDATE_EVENT, {
|
|
"node_id": unique_id,
|
|
"prompt": text,
|
|
"tab": tab or "",
|
|
"token": token or "",
|
|
})
|
|
|
|
|
|
@PromptServer.instance.routes.post("/prompt_stash_saver/latest_input")
|
|
async def latest_text(request):
|
|
"""Catch-up for a tab that was in the background: return the stored text
|
|
and token for the given node ids in that tab."""
|
|
try:
|
|
data = await request.json()
|
|
except Exception:
|
|
return web.json_response({"error": "invalid JSON"}, status=400)
|
|
tab = data.get("tab") or ""
|
|
node_ids = data.get("node_ids") or []
|
|
entries = {}
|
|
for node_id in node_ids:
|
|
entry = get_entry(tab, node_id)
|
|
if entry:
|
|
entries[str(node_id)] = entry
|
|
return web.json_response({"entries": entries})
|