Compare commits
10 Commits
00c8c6a790
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 690278b592 | |||
| 3ee14819b7 | |||
| d6d2c98a58 | |||
| 36dd5c91ee | |||
| 954b9ec2e6 | |||
| 1881aa727f | |||
| 78b1b85a11 | |||
| b50718f7fb | |||
| d9134b4e9b | |||
| 3fb63e44a3 |
+3
-2
@@ -47,13 +47,14 @@ class ImageGate:
|
|||||||
def run(self, image, routes, unique_id):
|
def run(self, image, routes, unique_id):
|
||||||
from comfy_execution.graph_utils import ExecutionBlocker
|
from comfy_execution.graph_utils import ExecutionBlocker
|
||||||
from . import gate_server
|
from . import gate_server
|
||||||
|
import comfy.model_management as mm
|
||||||
|
|
||||||
gate_bus.GateBus.arm(unique_id)
|
gate_bus.GateBus.arm(unique_id)
|
||||||
gate_server.send_preview(unique_id, image, routes)
|
gate_server.send_preview(unique_id, image, routes)
|
||||||
try:
|
try:
|
||||||
chosen_1 = gate_bus.GateBus.wait(unique_id)
|
chosen_1 = gate_bus.GateBus.wait(
|
||||||
|
unique_id, should_cancel=mm.processing_interrupted)
|
||||||
except gate_bus.GateCancelled:
|
except gate_bus.GateCancelled:
|
||||||
import comfy.model_management as mm
|
|
||||||
raise mm.InterruptProcessingException()
|
raise mm.InterruptProcessingException()
|
||||||
|
|
||||||
mask = mask_from_stash(gate_bus.GateBus.pop_mask(unique_id), image)
|
mask = mask_from_stash(gate_bus.GateBus.pop_mask(unique_id), image)
|
||||||
|
|||||||
+2
-2
@@ -27,10 +27,10 @@ class GateBus:
|
|||||||
cls.messages[str(node_id)] = int(message)
|
cls.messages[str(node_id)] = int(message)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def wait(cls, node_id, period=0.1):
|
def wait(cls, node_id, period=0.1, should_cancel=None):
|
||||||
sid = str(node_id)
|
sid = str(node_id)
|
||||||
while sid not in cls.messages:
|
while sid not in cls.messages:
|
||||||
if cls.cancelled:
|
if cls.cancelled or (should_cancel is not None and should_cancel()):
|
||||||
cls.cancelled = False
|
cls.cancelled = False
|
||||||
raise GateCancelled()
|
raise GateCancelled()
|
||||||
time.sleep(period)
|
time.sleep(period)
|
||||||
|
|||||||
@@ -65,3 +65,10 @@ def test_wait_payload_should_cancel_raises():
|
|||||||
gb.GateBus.arm("p")
|
gb.GateBus.arm("p")
|
||||||
with pytest.raises(gb.GateCancelled):
|
with pytest.raises(gb.GateCancelled):
|
||||||
gb.GateBus.wait_payload("p", should_cancel=lambda: True)
|
gb.GateBus.wait_payload("p", should_cancel=lambda: True)
|
||||||
|
|
||||||
|
def test_wait_should_cancel_raises():
|
||||||
|
# image gate: ComfyUI Interrupt (should_cancel) must abort the wait too
|
||||||
|
gb.GateBus.arm("7")
|
||||||
|
with pytest.raises(gb.GateCancelled):
|
||||||
|
gb.GateBus.wait("7", should_cancel=lambda: True)
|
||||||
|
assert gb.GateBus.cancelled is False
|
||||||
|
|||||||
+34
-11
@@ -32,7 +32,9 @@ const MARGIN = 10; // ComfyUI DOM-widget inset, matches the other nodes
|
|||||||
// `protected` (BOOLEAN toggle) + `stored_text` (hidden STRING) are real backend
|
// `protected` (BOOLEAN toggle) + `stored_text` (hidden STRING) are real backend
|
||||||
// widgets. When protected, the node acts as a plain text node: it outputs
|
// widgets. When protected, the node acts as a plain text node: it outputs
|
||||||
// stored_text and ignores upstream (no pause). The DOM textarea is the visible
|
// stored_text and ignores upstream (no pause). The DOM textarea is the visible
|
||||||
// editor and mirrors its value into stored_text so it persists and reaches run().
|
// editor and mirrors its value into stored_text on EVERY change (typing, upstream
|
||||||
|
// arrival, Pass) — so the editor content survives refresh / workflow reload in
|
||||||
|
// BOTH modes (stored_text also reaches run() when protected).
|
||||||
|
|
||||||
function widgetByName(node, name) {
|
function widgetByName(node, name) {
|
||||||
return node.widgets?.find((w) => w.name === name);
|
return node.widgets?.find((w) => w.name === name);
|
||||||
@@ -48,21 +50,24 @@ function syncStored(node) {
|
|||||||
if (w) w.value = node._tg?.area?.value ?? "";
|
if (w) w.value = node._tg?.area?.value ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// collapse the auto-created stored_text widget out of the layout (pool_id trick)
|
// fully hide the auto-created stored_text widget (same as the pool node's
|
||||||
|
// pool_id): getVisibleWidgets() filters on `hidden`, so it's dropped from both
|
||||||
|
// draw and layout — computeSize alone (or type="hidden") does NOT hide it.
|
||||||
|
// Serialization still iterates all widgets, so stored_text is saved/sent.
|
||||||
function hideStoredWidget(node) {
|
function hideStoredWidget(node) {
|
||||||
const w = widgetByName(node, "stored_text");
|
const w = widgetByName(node, "stored_text");
|
||||||
if (w) w.computeSize = () => [0, -4];
|
if (!w) return;
|
||||||
|
w.hidden = true;
|
||||||
|
w.computeSize = () => [0, -4];
|
||||||
}
|
}
|
||||||
|
|
||||||
// reflect the persisted protected/stored_text state into the editor + UI
|
// reflect the persisted stored_text + mode into the editor + UI. The editor text
|
||||||
|
// is restored in BOTH modes so it survives a refresh / workflow reload; the mode
|
||||||
|
// only selects the UI state (protected vs idle waiting-for-a-run).
|
||||||
function applyPersistedMode(node) {
|
function applyPersistedMode(node) {
|
||||||
if (!node._tg) return;
|
if (!node._tg) return;
|
||||||
if (isProtected(node)) {
|
|
||||||
node._tg.area.value = widgetByName(node, "stored_text")?.value ?? "";
|
node._tg.area.value = widgetByName(node, "stored_text")?.value ?? "";
|
||||||
setState(node, "protected");
|
setState(node, isProtected(node) ? "protected" : "idle");
|
||||||
} else {
|
|
||||||
setState(node, "idle");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- server call ------------------------------------------------------------
|
// ---- server call ------------------------------------------------------------
|
||||||
@@ -165,8 +170,17 @@ function setupTextGateNode(node) {
|
|||||||
const area = document.createElement("textarea");
|
const area = document.createElement("textarea");
|
||||||
area.className = "tgate-area";
|
area.className = "tgate-area";
|
||||||
area.placeholder = "waiting for a run…";
|
area.placeholder = "waiting for a run…";
|
||||||
// don't let typing/space toggle node selection or graph shortcuts
|
// Stop keys from reaching litegraph (so typing/space can't toggle node
|
||||||
area.onkeydown = (e) => e.stopPropagation();
|
// selection or fire canvas shortcuts) — EXCEPT ComfyUI's prompt-weighting
|
||||||
|
// shortcut (Ctrl/Cmd+↑/↓). That handler is a global `window` keydown listener
|
||||||
|
// that wraps the selection in (token:weight); a blanket stopPropagation here
|
||||||
|
// kept it from ever bubbling up, so weighting didn't work in this editor.
|
||||||
|
// Its execCommand edit fires our oninput, so the weighted text still syncs.
|
||||||
|
area.onkeydown = (e) => {
|
||||||
|
const isWeight = (e.ctrlKey || e.metaKey) &&
|
||||||
|
(e.key === "ArrowUp" || e.key === "ArrowDown");
|
||||||
|
if (!isWeight) e.stopPropagation();
|
||||||
|
};
|
||||||
// keep the hidden stored_text widget mirrored so edits persist + reach run()
|
// keep the hidden stored_text widget mirrored so edits persist + reach run()
|
||||||
area.oninput = () => syncStored(node);
|
area.oninput = () => syncStored(node);
|
||||||
|
|
||||||
@@ -177,6 +191,7 @@ function setupTextGateNode(node) {
|
|||||||
pass.className = "tgate-pass";
|
pass.className = "tgate-pass";
|
||||||
pass.textContent = "▶ Pass";
|
pass.textContent = "▶ Pass";
|
||||||
pass.onclick = async () => {
|
pass.onclick = async () => {
|
||||||
|
syncStored(node); // persist the passed text so a reload keeps it
|
||||||
await postPass(node, area.value);
|
await postPass(node, area.value);
|
||||||
setState(node, "passed");
|
setState(node, "passed");
|
||||||
};
|
};
|
||||||
@@ -240,11 +255,18 @@ function setupTextGateNode(node) {
|
|||||||
syncWidgetWidth(node);
|
syncWidgetWidth(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build marker — lets you confirm the browser loaded THIS build (not a cached
|
||||||
|
// old copy). If the editor comes back empty after reload but you don't see this
|
||||||
|
// line in the devtools console, your tab is running stale JS: hard-refresh
|
||||||
|
// (Ctrl/Cmd+Shift+R).
|
||||||
|
const BUILD = "2026-07-03 persist+weight";
|
||||||
|
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "datasete.gates.textgate",
|
name: "datasete.gates.textgate",
|
||||||
|
|
||||||
// one global socket listener: route the server's pause event to the node
|
// one global socket listener: route the server's pause event to the node
|
||||||
setup() {
|
setup() {
|
||||||
|
console.info(`[datasete.textgate] loaded build ${BUILD}`);
|
||||||
api.addEventListener("datasete-textgate-show", (e) => {
|
api.addEventListener("datasete-textgate-show", (e) => {
|
||||||
const d = e.detail || {};
|
const d = e.detail || {};
|
||||||
const node = app.graph?.getNodeById?.(parseInt(d.id, 10));
|
const node = app.graph?.getNodeById?.(parseInt(d.id, 10));
|
||||||
@@ -260,6 +282,7 @@ app.registerExtension({
|
|||||||
} else {
|
} else {
|
||||||
node._tg.area.value = d.text || "";
|
node._tg.area.value = d.text || "";
|
||||||
}
|
}
|
||||||
|
syncStored(node); // persist the shown text so a refresh/reload keeps it
|
||||||
setState(node, "paused");
|
setState(node, "paused");
|
||||||
try { node._tg.area.focus(); } catch (err) { /* ignore */ }
|
try { node._tg.area.focus(); } catch (err) { /* ignore */ }
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user