diff --git a/Rayko_Prompts.py b/Rayko_Prompts.py
index 92ec6b1..12c1d27 100644
--- a/Rayko_Prompts.py
+++ b/Rayko_Prompts.py
@@ -36,11 +36,12 @@ class RSPrompts:
"required": {
"clip": ("CLIP",),
"text": ("STRING", {"multiline": True, "default": "", "hidden": True}),
+ "disable_text_input": ("BOOLEAN", {"default": False, "label": "π Disable text input"}),
+ "pause_for_edit": ("BOOLEAN", {"default": False, "label": "βΈοΈ Pause for manual edit"}),
},
"optional": {
"text_input": ("STRING", {"forceInput": True}),
- "disable_text_input": ("BOOLEAN", {"default": False, "label": "π Disable text input"}),
- "pause_for_edit": ("BOOLEAN", {"default": False, "label": "βΈοΈ Pause for manual edit"}),
+ "instance_uid": ("STRING", {"default": "", "hidden": True}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
@@ -53,7 +54,9 @@ class RSPrompts:
CATEGORY = "π¦ RaykoStudio"
DESCRIPTION = "Text encoder with visual prompt controls and pause-for-edit mode."
- def encode_prompts(self, clip, disable_text_input=False, pause_for_edit=False, text="", text_input=None, unique_id=None):
+ def encode_prompts(self, clip, disable_text_input=False, pause_for_edit=False,
+ text="", text_input=None, unique_id=None, instance_uid=""):
+
if disable_text_input:
current_text = text
effective_text_input = None
@@ -61,17 +64,15 @@ class RSPrompts:
current_text = text_input if text_input is not None else text
effective_text_input = text_input
- if effective_text_input is not None and not pause_for_edit and unique_id:
+ if effective_text_input is not None and not pause_for_edit and instance_uid:
from server import PromptServer
PromptServer.instance.send_sync("rs.prompt.update", {
- "node_id": unique_id,
+ "instance_uid": instance_uid,
"prompt": current_text
})
- if pause_for_edit and effective_text_input is not None and effective_text_input and unique_id:
- print(f"[RS Prompts π¦] Pause mode enabled for node {unique_id}")
-
- PENDING_PROMPTS[unique_id] = {
+ if pause_for_edit and effective_text_input is not None and effective_text_input and instance_uid:
+ PENDING_PROMPTS[instance_uid] = {
"original": current_text,
"edited": current_text,
"status": "pending",
@@ -81,30 +82,26 @@ class RSPrompts:
from server import PromptServer
PromptServer.instance.send_sync("rs.prompt.pause", {
- "node_id": unique_id,
+ "instance_uid": instance_uid,
"prompt": current_text
})
future = Future()
- PENDING_PROMPTS[unique_id]["future"] = future
+ PENDING_PROMPTS[instance_uid]["future"] = future
try:
result = future.result(timeout=600)
if result["status"] == "approved":
final_text = result["prompt"]
- print(f"[RS Prompts π¦] Using edited prompt: {final_text[:50]}...")
else:
final_text = current_text
- print(f"[RS Prompts π¦] Using original prompt")
except TimeoutError:
- print(f"[RS Prompts π¦] Timeout! Using original prompt")
final_text = current_text
except Exception as e:
- print(f"[RS Prompts π¦] Error: {e}, using original")
final_text = current_text
finally:
- if unique_id in PENDING_PROMPTS:
- del PENDING_PROMPTS[unique_id]
+ if instance_uid in PENDING_PROMPTS:
+ del PENDING_PROMPTS[instance_uid]
current_text = final_text
@@ -162,7 +159,8 @@ async def rs_prompts_load_prompt(request):
filepath = os.path.join(PROMPTS_DIR, f"{name}.json")
if os.path.exists(filepath):
with open(filepath, 'r', encoding='utf-8') as f:
- return web.json_response(json.load(f))
+ result = json.load(f)
+ return web.json_response(result)
return web.Response(status=404, text="Prompt not found")
except Exception as e:
return web.Response(status=500, text=str(e))
@@ -187,11 +185,11 @@ async def rs_prompts_delete_prompt(request):
async def rs_prompts_approve_edit(request):
try:
data = await request.json()
- node_id = data.get("node_id")
+ instance_uid = data.get("instance_uid", "")
edited_prompt = data.get("prompt", "")
- if node_id in PENDING_PROMPTS:
- pending = PENDING_PROMPTS[node_id]
+ if instance_uid in PENDING_PROMPTS:
+ pending = PENDING_PROMPTS[instance_uid]
pending["status"] = "approved"
pending["edited"] = edited_prompt
@@ -201,13 +199,11 @@ async def rs_prompts_approve_edit(request):
"prompt": edited_prompt
})
- print(f"[RS Prompts π¦] Node {node_id} approved with new prompt")
return web.Response(status=200, text="Approved")
else:
- return web.Response(status=404, text=f"Node {node_id} not waiting")
+ return web.Response(status=404, text=f"Instance {instance_uid} not waiting")
except Exception as e:
- print(f"[RS Prompts π¦] Error in approve_edit: {e}")
return web.Response(status=500, text=str(e))
@@ -215,10 +211,10 @@ async def rs_prompts_approve_edit(request):
async def rs_prompts_reject_edit(request):
try:
data = await request.json()
- node_id = data.get("node_id")
+ instance_uid = data.get("instance_uid", "")
- if node_id in PENDING_PROMPTS:
- pending = PENDING_PROMPTS[node_id]
+ if instance_uid in PENDING_PROMPTS:
+ pending = PENDING_PROMPTS[instance_uid]
pending["status"] = "rejected"
if "future" in pending:
@@ -227,13 +223,11 @@ async def rs_prompts_reject_edit(request):
"prompt": pending["original"]
})
- print(f"[RS Prompts π¦] Node {node_id} rejected, using original")
return web.Response(status=200, text="Rejected")
else:
- return web.Response(status=404, text=f"Node {node_id} not waiting")
+ return web.Response(status=404, text=f"Instance {instance_uid} not waiting")
except Exception as e:
- print(f"[RS Prompts π¦] Error in reject_edit: {e}")
return web.Response(status=500, text=str(e))
diff --git a/__init__.py b/__init__.py
index c699036..acfe5bd 100644
--- a/__init__.py
+++ b/__init__.py
@@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'}
REQUIRED_MODULES: Set[str] = set()
PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio"
-__version__ = "0.20.6"
+__version__ = "0.21.0"
def load_modules():
print(f"π¦ {PACKAGE_DISPLAY_NAME}: Version: {__version__}")
diff --git a/package.json b/package.json
index ce976a0..cf176da 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ComfyUI_RaykoStudio",
- "version": "0.20.6",
+ "version": "0.21.0",
"description": "Set of custom nodes for ComfyUI providing additional image processing capabilities",
"author": "Raykosan",
"license": "Apache License 2.0",
diff --git a/pyproject.toml b/pyproject.toml
index 5a77c72..bc2d4a8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "ComfyUI_RaykoStudio"
description = "Set of custom nodes for ComfyUI providing additional image processing capabilities"
-version = "0.20.6"
+version = "0.21.0"
license = { file = "LICENSE" }
dependencies = ["torch>=1.7.0", "numpy>=1.19.0", "Pillow>=8.0.0", "freetype-py>=2.5.1"]
diff --git a/web/rs_prompts.js b/web/rs_prompts.js
index 3d14be0..e008799 100644
--- a/web/rs_prompts.js
+++ b/web/rs_prompts.js
@@ -10,68 +10,14 @@ styleBlock.innerHTML = `
textarea.comfy-multiline-input:disabled { opacity: 0.5 !important; color: #888 !important; }
.rs-waiting-overlay {
position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
+ top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.85);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 10000;
- border-radius: 8px;
+ display: flex; align-items: center; justify-content: center;
+ z-index: 10000; border-radius: 8px;
}
.rs-waiting-message {
- background: #2a2a2a;
- padding: 20px;
- border-radius: 8px;
- border: 1px solid #5090cc;
- text-align: center;
- }
- .rs-toggle-switch {
- position: relative;
- display: inline-flex;
- align-items: center;
- cursor: pointer;
- gap: 6px;
- }
- .rs-toggle-switch input {
- opacity: 0;
- width: 0;
- height: 0;
- position: absolute;
- }
- .rs-toggle-slider {
- position: relative;
- display: inline-block;
- width: 36px;
- height: 18px;
- background-color: #444;
- border-radius: 18px;
- transition: 0.3s;
- cursor: pointer;
- }
- .rs-toggle-slider:before {
- position: absolute;
- content: "";
- height: 14px;
- width: 14px;
- left: 2px;
- bottom: 2px;
- background-color: white;
- border-radius: 50%;
- transition: 0.3s;
- }
- input:checked + .rs-toggle-slider {
- background-color: #28a745;
- }
- input:checked + .rs-toggle-slider:before {
- transform: translateX(18px);
- }
- .rs-toggle-label {
- font-size: 11px;
- color: #ccc;
- user-select: none;
+ background: #2a2a2a; padding: 20px; border-radius: 8px;
+ border: 1px solid #fbbf24; text-align: center;
}
`;
document.head.appendChild(styleBlock);
@@ -88,11 +34,124 @@ app.registerExtension({
if (nodeData.name !== NODE_CLASS) return;
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
+ const origOnConfigure = nodeType.prototype.onConfigure;
+ const origSerialize = nodeType.prototype.serialize;
+ const origOnRemoved = nodeType.prototype.onRemoved;
+
+ nodeType.prototype.onConfigure = function(data) {
+ const result = origOnConfigure ? origOnConfigure.apply(this, arguments) : undefined;
+
+ if (this.properties?.rs_instance_uid && this.widgets) {
+ const uidWidget = this.widgets.find(w => w.name === "instance_uid");
+ if (uidWidget) {
+ uidWidget.value = this.properties.rs_instance_uid;
+ }
+ }
+
+ if (this.widgets) {
+ const pauseWidget = this.widgets.find(w => w.name === "pause_for_edit");
+ const disableWidget = this.widgets.find(w => w.name === "disable_text_input");
+
+ if (pauseWidget && this.properties?.rs_pause_state !== undefined) {
+ pauseWidget.value = this.properties.rs_pause_state;
+ }
+ if (disableWidget && this.properties?.rs_disable_state !== undefined) {
+ disableWidget.value = this.properties.rs_disable_state;
+ }
+ }
+
+ setTimeout(() => {
+ if (this.restoreFromProperties) {
+ this.restoreFromProperties();
+ }
+ }, 100);
+
+ return result;
+ };
+
+ nodeType.prototype.serialize = function() {
+ if (this.properties && this.widgets) {
+ const uidWidget = this.widgets.find(w => w.name === "instance_uid");
+ if (uidWidget && uidWidget.value) {
+ this.properties.rs_instance_uid = uidWidget.value;
+ }
+ }
+
+ if (this.widgets) {
+ const pauseWidget = this.widgets.find(w => w.name === "pause_for_edit");
+ const disableWidget = this.widgets.find(w => w.name === "disable_text_input");
+
+ if (pauseWidget && this.properties) {
+ this.properties.rs_pause_state = pauseWidget.value;
+ }
+ if (disableWidget && this.properties) {
+ this.properties.rs_disable_state = disableWidget.value;
+ }
+ }
+
+ const result = origSerialize ? origSerialize.apply(this, arguments) : {};
+ return result;
+ };
nodeType.prototype.onNodeCreated = function () {
const result = origOnNodeCreated?.apply(this, arguments);
const node = this;
+ if (!node.properties) {
+ node.properties = {};
+ }
+
+ let instanceUid = node.properties.rs_instance_uid;
+
+ if (!instanceUid) {
+ const uidWidget = node.widgets?.find(w => w.name === "instance_uid");
+ if (uidWidget && uidWidget.value) {
+ instanceUid = uidWidget.value;
+ } else {
+ instanceUid = 'rs_inst_' + crypto.randomUUID().replace(/-/g, '');
+ }
+ node.properties.rs_instance_uid = instanceUid;
+ }
+
+ if (node.properties.rs_pause_state === undefined) {
+ const pauseWidget = node.widgets?.find(w => w.name === "pause_for_edit");
+ node.properties.rs_pause_state = pauseWidget ? pauseWidget.value : false;
+ }
+ if (node.properties.rs_disable_state === undefined) {
+ const disableWidget = node.widgets?.find(w => w.name === "disable_text_input");
+ node.properties.rs_disable_state = disableWidget ? disableWidget.value : false;
+ }
+ if (node.properties.rs_is_waiting === undefined) {
+ node.properties.rs_is_waiting = false;
+ }
+ if (node.properties.rs_waiting_prompt === undefined) {
+ node.properties.rs_waiting_prompt = "";
+ }
+ if (node.properties.rs_waiting_timestamp === undefined) {
+ node.properties.rs_waiting_timestamp = 0;
+ }
+
+ const textWidget = node.widgets?.find(w => w.name === "text");
+ const pauseWidget = node.widgets?.find(w => w.name === "pause_for_edit");
+ const disableWidget = node.widgets?.find(w => w.name === "disable_text_input");
+ const uidWidget = node.widgets?.find(w => w.name === "instance_uid");
+
+ if (uidWidget) {
+ uidWidget.value = instanceUid;
+ uidWidget.hidden = true;
+ uidWidget.serializeValue = () => node.properties.rs_instance_uid;
+ }
+ if (textWidget) {
+ textWidget.hidden = true;
+ }
+
+ if (pauseWidget) {
+ pauseWidget.value = node.properties.rs_pause_state;
+ }
+ if (disableWidget) {
+ disableWidget.value = node.properties.rs_disable_state;
+ }
+
const hidePhantomSlot = () => {
if (node.inputs) {
const textInput = node.inputs.find(i => i.name === "text");
@@ -106,44 +165,8 @@ app.registerExtension({
};
setTimeout(hidePhantomSlot, 0);
- const textWidget = node.widgets?.find(w => w.name === "text");
- const pauseWidget = node.widgets?.find(w => w.name === "pause_for_edit");
- const disableWidget = node.widgets?.find(w => w.name === "disable_text_input");
-
- if (pauseWidget) {
- pauseWidget.hidden = true;
- }
-
- if (disableWidget) {
- disableWidget.hidden = true;
- }
-
- if (textWidget) textWidget.hidden = true;
-
- const enableWidgets = () => {
- if (textWidget) {
- textWidget.disabled = false;
- textWidget.serializeValue = () => textWidget.value;
- }
- };
- setTimeout(enableWidgets, 50);
-
- const origOnConnectionsChange = node.onConnectionsChange;
- node.onConnectionsChange = function(slotType, slotIndex, isConnected, link, linkInfo) {
- if (origOnConnectionsChange) origOnConnectionsChange.apply(this, arguments);
- setTimeout(enableWidgets, 50);
- setTimeout(() => updateStatusIndicator(), 10);
- setTimeout(() => {
- if (pauseModeEnabled && !canPauseBeActive()) {
- pauseModeEnabled = false;
- if (pauseWidget) pauseWidget.value = false;
- updateUIForPauseMode(false);
- }
- }, 10);
- };
-
let waitingOverlay = null;
- let pauseModeEnabled = false;
+ let enforcementInterval = null;
const removeWaitingOverlay = () => {
if (waitingOverlay && waitingOverlay.parentNode) {
@@ -157,9 +180,9 @@ app.registerExtension({
waitingOverlay = mkEl("div", "rs-waiting-overlay");
const messageDiv = mkEl("div", "rs-waiting-message");
messageDiv.innerHTML = `
-
βοΈ EDITING MODE
+ βοΈ EDITING MODE
Edit the prompt below and click APPROVE
- β³ Waiting for your decision...
+ β³ Waiting for your decision...
`;
waitingOverlay.appendChild(messageDiv);
const domWidget = node.domWidgets?.find(w => w.name === "prompt_ui");
@@ -171,73 +194,16 @@ app.registerExtension({
const root = mkEl("div", "display:flex;flex-direction:column;height:100%;padding:0;margin:0;box-sizing:border-box;overflow:hidden;position:relative;");
- const disableRow = mkEl("div", "display:flex;align-items:center;justify-content:flex-start;padding:4px 6px 2px 6px;background:#1a1a1a;");
- const disableToggleContainer = mkEl("label", "rs-toggle-switch");
- const disableToggleInput = document.createElement("input");
- disableToggleInput.type = "checkbox";
- const disableToggleSlider = mkEl("span", "rs-toggle-slider");
- const disableToggleLabel = mkEl("span", "rs-toggle-label");
- disableToggleLabel.textContent = "π Disable text input";
-
- disableToggleContainer.appendChild(disableToggleInput);
- disableToggleContainer.appendChild(disableToggleSlider);
- disableToggleContainer.appendChild(disableToggleLabel);
- disableRow.appendChild(disableToggleContainer);
-
- const pauseRow = mkEl("div", "display:flex;align-items:center;justify-content:space-between;padding:2px 6px 4px 6px;background:#1a1a1a;border-bottom:1px solid #333;gap:4px;");
-
- const toggleContainer = mkEl("label", "rs-toggle-switch");
- const toggleInput = document.createElement("input");
- toggleInput.type = "checkbox";
- const toggleSlider = mkEl("span", "rs-toggle-slider");
- const toggleLabel = mkEl("span", "rs-toggle-label");
- toggleLabel.textContent = "βΈοΈ Pause for edit";
-
- toggleContainer.appendChild(toggleInput);
- toggleContainer.appendChild(toggleSlider);
- toggleContainer.appendChild(toggleLabel);
-
- const statusIndicator = mkEl("div", "font-size:10px;padding:2px 6px;border-radius:4px;background:#2a2a2a;color:#ccc;");
- statusIndicator.textContent = "π Local prompt";
-
- pauseRow.appendChild(toggleContainer);
- pauseRow.appendChild(statusIndicator);
-
- root.appendChild(disableRow);
- root.appendChild(pauseRow);
+ const statusBar = mkEl("div", "width:100%; padding: 4px 8px; font-size: 11px; font-weight: bold; text-align: center; border-radius: 4px 4px 0 0; margin-bottom: 4px; display: flex; align-items: center; justify-content: center; gap: 6px; line-height: 1.2;");
+ root.appendChild(statusBar);
const customTextarea = document.createElement("textarea");
customTextarea.className = "rs-custom-textarea";
- customTextarea.style.cssText = "flex:1;width:100%;min-height:0;border:none;border-radius:4px;padding:8px;background:#111;color:#fff;font-family:system-ui,sans-serif;font-size:12px;resize:none;outline:none;box-sizing:border-box;";
+ customTextarea.style.cssText = "flex:1;width:100%;min-height:0;border:1px solid #444;border-radius:4px;padding:8px;background:#111;color:#fff;font-family:system-ui,sans-serif;font-size:12px;resize:none;outline:none;box-sizing:border-box;";
customTextarea.placeholder = "Enter your prompt here...";
-
- const storageKey = `rs_prompt_${node.id}`;
-
- if (textWidget) {
- const savedValue = localStorage.getItem(storageKey);
- if (savedValue && !textWidget.value) {
- textWidget.value = savedValue;
- }
- customTextarea.value = textWidget.value || "";
- textWidget.value = customTextarea.value;
- } else {
- const savedValue = localStorage.getItem(storageKey);
- if (savedValue) {
- customTextarea.value = savedValue;
- }
- }
-
- customTextarea.addEventListener("input", () => {
- if (textWidget) {
- textWidget.value = customTextarea.value;
- }
- localStorage.setItem(storageKey, customTextarea.value);
- node.graph?.setDirtyCanvas(true, true);
- });
-
root.appendChild(customTextarea);
- const buttonsWrapper = mkEl("div", "width:100%;display:flex;flex-direction:column;gap:4px;padding:4px;box-sizing:border-box;");
+ const buttonsWrapper = mkEl("div", "width:100%;display:flex;flex-direction:column;gap:4px;padding:4px;box-sizing:border-box;margin-top:4px;");
const clearRow = mkEl("div", "display:flex;gap:4px;width:100%;");
const clearBtn = mkEl("button", "flex:1;padding:6px 2px;font-size:11px;border:1px solid #99c0ee;border-radius:5px;background:#1a3a5a;color:#aadaff;cursor:pointer;");
@@ -258,13 +224,6 @@ app.registerExtension({
rejectEditBtn.textContent = "β REJECT (use original)";
actionRow.append(acceptEditBtn, rejectEditBtn);
- acceptEditBtn.disabled = true;
- acceptEditBtn.style.opacity = "0.5";
- acceptEditBtn.style.cursor = "not-allowed";
- rejectEditBtn.disabled = true;
- rejectEditBtn.style.opacity = "0.5";
- rejectEditBtn.style.cursor = "not-allowed";
-
buttonsWrapper.append(clearRow, btnRow, actionRow);
root.appendChild(buttonsWrapper);
@@ -309,161 +268,249 @@ app.registerExtension({
const canPauseBeActive = () => {
const hasConnection = hasTextInputConnection();
- const isDisabled = disableToggleInput.checked;
+ const isDisabled = node.properties.rs_disable_state;
return hasConnection && !isDisabled;
};
- const updateStatusIndicator = () => {
- if (pauseModeEnabled) {
- statusIndicator.innerHTML = `βΈοΈ WAITING FOR EDIT`;
- statusIndicator.style.background = "#1a3a1a";
- return;
- }
-
- const hasConnection = hasTextInputConnection();
- const isDisabled = disableToggleInput.checked;
-
- if (!hasConnection) {
- statusIndicator.innerHTML = `π Local prompt`;
- } else if (hasConnection && !isDisabled) {
- statusIndicator.innerHTML = `π External input`;
- } else if (hasConnection && isDisabled) {
- statusIndicator.innerHTML = `π Local prompt`;
- }
-
- statusIndicator.style.background = "#2a2a2a";
+ const getCurrentUid = () => {
+ return node.properties?.rs_instance_uid ||
+ node.widgets?.find(w => w.name === "instance_uid")?.value;
};
- const updateUIForPauseMode = (isPaused) => {
- if (isPaused) {
- clearBtn.disabled = true;
- clearBtn.style.opacity = "0.5";
- clearBtn.style.cursor = "not-allowed";
- saveBtn.disabled = true;
- saveBtn.style.opacity = "0.5";
- saveBtn.style.cursor = "not-allowed";
- selectBtn.disabled = true;
- selectBtn.style.opacity = "0.5";
- selectBtn.style.cursor = "not-allowed";
-
- acceptEditBtn.disabled = false;
- acceptEditBtn.style.opacity = "1";
- acceptEditBtn.style.cursor = "pointer";
- rejectEditBtn.disabled = false;
- rejectEditBtn.style.opacity = "1";
- rejectEditBtn.style.cursor = "pointer";
-
- customTextarea.style.border = "2px solid #28a745";
-
- updateStatusIndicator();
-
- showWaitingOverlay();
+ const saveStateToProperties = () => {
+ node.properties.rs_pause_state = pauseWidget ? pauseWidget.value : false;
+ node.properties.rs_disable_state = disableWidget ? disableWidget.value : false;
+ };
+
+ const restoreFromProperties = () => {
+ const isWaiting = node.properties.rs_is_waiting;
+ const waitingPrompt = node.properties.rs_waiting_prompt;
+
+ if (isWaiting && waitingPrompt) {
+ customTextarea.value = waitingPrompt;
+ if (textWidget) {
+ textWidget.value = waitingPrompt;
+ }
+ updateStatusAndUI();
} else {
- clearBtn.disabled = false;
- clearBtn.style.opacity = "1";
- clearBtn.style.cursor = "pointer";
- saveBtn.disabled = false;
- saveBtn.style.opacity = "1";
- saveBtn.style.cursor = "pointer";
- selectBtn.disabled = false;
- selectBtn.style.opacity = "1";
- selectBtn.style.cursor = "pointer";
-
- acceptEditBtn.disabled = true;
- acceptEditBtn.style.opacity = "0.5";
- acceptEditBtn.style.cursor = "not-allowed";
- rejectEditBtn.disabled = true;
- rejectEditBtn.style.opacity = "0.5";
- rejectEditBtn.style.cursor = "not-allowed";
-
- customTextarea.style.border = "1px solid #444";
-
- updateStatusIndicator();
-
- removeWaitingOverlay();
+ const currentUid = getCurrentUid();
+ const textKey = `rs_prompt_${currentUid}`;
+ const savedText = localStorage.getItem(textKey);
+ if (savedText !== null) {
+ customTextarea.value = savedText;
+ if (textWidget) textWidget.value = savedText;
+ }
+ updateStatusAndUI();
}
};
-
- if (disableWidget) {
- disableToggleInput.checked = disableWidget.value;
+
+ const updateStatusAndUI = () => {
+ const isWaiting = node.properties.rs_is_waiting;
+ const isDisabled = node.properties.rs_disable_state;
+ const hasConnection = hasTextInputConnection();
+
+ removeWaitingOverlay();
- if (disableToggleInput.checked) {
- disableToggleLabel.textContent = "π΄ Disable text input";
+ acceptEditBtn.disabled = true;
+ acceptEditBtn.style.opacity = "0.5";
+ acceptEditBtn.style.cursor = "not-allowed";
+ rejectEditBtn.disabled = true;
+ rejectEditBtn.style.opacity = "0.5";
+ rejectEditBtn.style.cursor = "not-allowed";
+ clearBtn.disabled = false;
+ clearBtn.style.opacity = "1";
+ clearBtn.style.cursor = "pointer";
+ saveBtn.disabled = false;
+ saveBtn.style.opacity = "1";
+ saveBtn.style.cursor = "pointer";
+ selectBtn.disabled = false;
+ selectBtn.style.opacity = "1";
+ selectBtn.style.cursor = "pointer";
+ customTextarea.style.border = "1px solid #444";
+
+ if (isWaiting && canPauseBeActive()) {
+ statusBar.style.background = "#3a2a1a";
+ statusBar.style.color = "#fbbf24";
+ statusBar.innerHTML = "π WAITING FOR EDIT - Edit prompt and click APPROVE";
+
+ acceptEditBtn.disabled = false;
+ acceptEditBtn.style.opacity = "1";
+ acceptEditBtn.style.cursor = "pointer";
+ rejectEditBtn.disabled = false;
+ rejectEditBtn.style.opacity = "1";
+ rejectEditBtn.style.cursor = "pointer";
+ clearBtn.disabled = true;
+ clearBtn.style.opacity = "0.5";
+ clearBtn.style.cursor = "not-allowed";
+ saveBtn.disabled = true;
+ saveBtn.style.opacity = "0.5";
+ saveBtn.style.cursor = "not-allowed";
+ selectBtn.disabled = true;
+ selectBtn.style.opacity = "0.5";
+ selectBtn.style.cursor = "not-allowed";
+ customTextarea.style.border = "2px solid #fbbf24";
+
+ showWaitingOverlay();
+ } else if (hasConnection && !isDisabled) {
+ statusBar.style.background = "#1a2a3a";
+ statusBar.style.color = "#60a5fa";
+ statusBar.innerHTML = "π΅ EXTERNAL INPUT";
} else {
- disableToggleLabel.textContent = "π Disable text input";
+ statusBar.style.background = "#1a3a1a";
+ statusBar.style.color = "#4ade80";
+ statusBar.innerHTML = "π’ LOCAL PROMPT";
}
- updateStatusIndicator();
+ if (node.graph) node.graph.setDirtyCanvas(true, true);
+ };
+
+ const startEnforcement = () => {
+ if (enforcementInterval) clearInterval(enforcementInterval);
+ enforcementInterval = setInterval(() => {
+ let needsRedraw = false;
+
+ if (pauseWidget && pauseWidget.value !== node.properties.rs_pause_state) {
+ pauseWidget.value = node.properties.rs_pause_state;
+ needsRedraw = true;
+ }
+ if (disableWidget && disableWidget.value !== node.properties.rs_disable_state) {
+ disableWidget.value = node.properties.rs_disable_state;
+ needsRedraw = true;
+ }
+
+ if (needsRedraw && node.graph) {
+ node.graph.setDirtyCanvas(true, true);
+ }
+ }, 200);
+ };
+
+ const stopEnforcement = () => {
+ if (enforcementInterval) {
+ clearInterval(enforcementInterval);
+ enforcementInterval = null;
+ }
+ };
+
+ node.onRemoved = function() {
+ stopEnforcement();
+ if (origOnRemoved) origOnRemoved.apply(this, arguments);
+ };
+
+ node.restoreFromProperties = restoreFromProperties;
+ node.updateStatusAndUI = updateStatusAndUI;
+ node.saveStateToProperties = saveStateToProperties;
+
+ const textKey = `rs_prompt_${instanceUid}`;
+
+ setTimeout(() => {
+ if (node.properties.rs_is_waiting && node.properties.rs_waiting_prompt) {
+ customTextarea.value = node.properties.rs_waiting_prompt;
+ if (textWidget) textWidget.value = node.properties.rs_waiting_prompt;
+ } else {
+ const savedText = localStorage.getItem(textKey);
+ if (savedText !== null && textWidget) {
+ textWidget.value = savedText;
+ customTextarea.value = savedText;
+ } else if (textWidget) {
+ const initialText = textWidget.value || "";
+ localStorage.setItem(textKey, initialText);
+ customTextarea.value = initialText;
+ }
+ }
- disableToggleInput.addEventListener("change", (e) => {
- disableWidget.value = e.target.checked;
- if (disableWidget.callback) disableWidget.callback(e.target.checked);
-
- if (e.target.checked) {
- disableToggleLabel.textContent = "π΄ Disable text input";
- } else {
- disableToggleLabel.textContent = "π Disable text input";
- }
-
- if (pauseModeEnabled && !canPauseBeActive()) {
- pauseModeEnabled = false;
- if (pauseWidget) pauseWidget.value = false;
- toggleInput.checked = false;
- updateUIForPauseMode(false);
- }
-
- updateStatusIndicator();
-
- node.graph?.setDirtyCanvas(true, true);
- });
- }
-
- if (pauseWidget) {
- toggleInput.checked = pauseWidget.value;
- }
-
- toggleInput.addEventListener("change", (e) => {
if (pauseWidget) {
- pauseWidget.value = e.target.checked;
- if (pauseWidget.callback) pauseWidget.callback(e.target.checked);
+ const originalPauseCallback = pauseWidget.callback;
+ pauseWidget.callback = function(v) {
+ node.properties.rs_pause_state = v;
+ if (!v) {
+ node.properties.rs_is_waiting = false;
+ node.properties.rs_waiting_prompt = "";
+ }
+ if (originalPauseCallback) originalPauseCallback(v);
+ updateStatusAndUI();
+ };
}
- node.graph?.setDirtyCanvas(true, true);
+
+ if (disableWidget) {
+ const originalDisableCallback = disableWidget.callback;
+ disableWidget.callback = function(v) {
+ node.properties.rs_disable_state = v;
+ if (originalDisableCallback) originalDisableCallback(v);
+ updateStatusAndUI();
+ };
+ }
+
+ startEnforcement();
+ updateStatusAndUI();
+ }, 100);
+
+ customTextarea.addEventListener("input", () => {
+ if (textWidget) {
+ textWidget.value = customTextarea.value;
+ const currentUid = getCurrentUid();
+ const currentTextKey = `rs_prompt_${currentUid}`;
+ localStorage.setItem(currentTextKey, customTextarea.value);
+ if (node.properties.rs_is_waiting) {
+ node.properties.rs_waiting_prompt = customTextarea.value;
+ }
+ }
+ if (node.graph) node.graph.setDirtyCanvas(true, true);
});
acceptEditBtn.addEventListener("click", async () => {
+ const currentUid = getCurrentUid();
+ if (!currentUid) return;
+
const currentPrompt = customTextarea.value;
+ node.properties.rs_waiting_prompt = currentPrompt;
+
await fetch("/rs_prompts/approve_edit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
- node_id: node.id.toString(),
+ instance_uid: currentUid,
prompt: currentPrompt
})
});
- pauseModeEnabled = false;
- updateUIForPauseMode(false);
- node.graph?.setDirtyCanvas(true, true);
+
+ node.properties.rs_is_waiting = false;
+ node.properties.rs_waiting_prompt = "";
+ node.properties.rs_waiting_timestamp = 0;
+ updateStatusAndUI();
});
rejectEditBtn.addEventListener("click", async () => {
+ const currentUid = getCurrentUid();
+ if (!currentUid) return;
+
await fetch("/rs_prompts/reject_edit", {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- node_id: node.id.toString()
- })
+ body: JSON.stringify({ instance_uid: currentUid })
});
- pauseModeEnabled = false;
- updateUIForPauseMode(false);
- node.graph?.setDirtyCanvas(true, true);
+
+ if (node.properties.rs_waiting_prompt) {
+ customTextarea.value = node.properties.rs_waiting_prompt;
+ if (textWidget) textWidget.value = node.properties.rs_waiting_prompt;
+ }
+
+ node.properties.rs_is_waiting = false;
+ node.properties.rs_waiting_prompt = "";
+ node.properties.rs_waiting_timestamp = 0;
+ updateStatusAndUI();
});
clearBtn.addEventListener("click", () => {
if(textWidget) {
textWidget.value = "";
customTextarea.value = "";
- localStorage.setItem(storageKey, "");
- node.graph?.setDirtyCanvas(true, true);
+ const currentUid = getCurrentUid();
+ const currentTextKey = `rs_prompt_${currentUid}`;
+ localStorage.setItem(currentTextKey, "");
+ if (node.properties.rs_is_waiting) {
+ node.properties.rs_waiting_prompt = "";
+ }
+ if (node.graph) node.graph.setDirtyCanvas(true, true);
}
});
@@ -486,13 +533,18 @@ app.registerExtension({
});
};
inputOk.addEventListener("click", performSave);
- inputCancel.addEventListener("click", () => { presetNameInput.style.display = "none"; });
+ inputCancel.addEventListener("click", () => {
+ presetNameInput.style.display = "none";
+ });
inputField.addEventListener("keydown", (e) => { if(e.key === "Enter") performSave(); if(e.key === "Escape") presetNameInput.style.display = "none"; });
selectBtn.addEventListener("click", async () => {
presetNameInput.style.display = "none";
deleteConfirmOverlay.style.display = "none";
- if (presetListOverlay.style.display === "flex") { presetListOverlay.style.display = "none"; return; }
+ if (presetListOverlay.style.display === "flex") {
+ presetListOverlay.style.display = "none";
+ return;
+ }
presetListOverlay.innerHTML = "Loading...
";
presetListOverlay.style.display = "flex";
try {
@@ -504,7 +556,8 @@ app.registerExtension({
const row = document.createElement("div");
row.style.cssText = "display:flex;align-items:center;justify-content:space-between;padding:6px 10px;border-bottom:1px solid #333;";
const nameSpan = document.createElement("span");
- nameSpan.textContent = name; nameSpan.style.cssText = "flex:1;cursor:pointer;color:#ccc;font-size:12px;";
+ nameSpan.textContent = name;
+ nameSpan.style.cssText = "flex:1;cursor:pointer;color:#ccc;font-size:12px;";
nameSpan.onmouseenter = () => nameSpan.style.background = "#3a3a3a";
nameSpan.onmouseleave = () => nameSpan.style.background = "transparent";
nameSpan.onclick = async () => {
@@ -515,9 +568,11 @@ app.registerExtension({
if(textWidget) {
textWidget.value = data.text || "";
customTextarea.value = data.text || "";
- localStorage.setItem(storageKey, data.text || "");
+ const currentUid = getCurrentUid();
+ const currentTextKey = `rs_prompt_${currentUid}`;
+ localStorage.setItem(currentTextKey, data.text || "");
}
- node.graph?.setDirtyCanvas(true, true);
+ if (node.graph) node.graph.setDirtyCanvas(true, true);
}
};
const deleteBtn = document.createElement("span");
@@ -535,7 +590,9 @@ app.registerExtension({
row.appendChild(deleteBtn);
presetListOverlay.appendChild(row);
});
- } catch(e) { presetListOverlay.textContent = "Error loading"; }
+ } catch(e) {
+ presetListOverlay.textContent = "Error loading";
+ }
});
deleteOk.addEventListener("click", async () => {
@@ -563,41 +620,50 @@ app.registerExtension({
});
api.addEventListener("rs.prompt.pause", (event) => {
- if (event.detail.node_id == node.id) {
+ const currentUid = getCurrentUid();
+ if (event.detail.instance_uid === currentUid) {
+ node.properties.rs_is_waiting = true;
+ node.properties.rs_waiting_prompt = event.detail.prompt;
+ node.properties.rs_waiting_timestamp = Date.now();
+
customTextarea.value = event.detail.prompt;
- if (textWidget) textWidget.value = event.detail.prompt;
- localStorage.setItem(storageKey, event.detail.prompt);
- if (canPauseBeActive()) {
- pauseModeEnabled = true;
- toggleInput.checked = true;
- if (pauseWidget) pauseWidget.value = true;
- updateUIForPauseMode(true);
+ if (textWidget) {
+ textWidget.value = event.detail.prompt;
+ const currentTextKey = `rs_prompt_${currentUid}`;
+ localStorage.setItem(currentTextKey, event.detail.prompt);
}
- node.graph?.setDirtyCanvas(true, true);
+ updateStatusAndUI();
}
});
api.addEventListener("rs.prompt.update", (event) => {
- if (event.detail.node_id == node.id) {
+ const currentUid = getCurrentUid();
+ if (event.detail.instance_uid === currentUid && !node.properties.rs_is_waiting) {
setTimeout(() => {
customTextarea.value = event.detail.prompt;
- if (textWidget) textWidget.value = event.detail.prompt;
- localStorage.setItem(storageKey, event.detail.prompt);
- node.graph?.setDirtyCanvas(true, true);
+ if (textWidget) {
+ textWidget.value = event.detail.prompt;
+ const currentTextKey = `rs_prompt_${currentUid}`;
+ localStorage.setItem(currentTextKey, event.detail.prompt);
+ }
+ if (node.graph) node.graph.setDirtyCanvas(true, true);
}, 10);
+ } else if (event.detail.instance_uid === currentUid && node.properties.rs_is_waiting) {
+ node.properties.rs_waiting_prompt = event.detail.prompt;
+ customTextarea.value = event.detail.prompt;
+ if (textWidget) textWidget.value = event.detail.prompt;
}
});
window.addEventListener("beforeunload", () => {
if (textWidget && textWidget.value) {
- localStorage.setItem(storageKey, textWidget.value);
- } else if (customTextarea.value) {
- localStorage.setItem(storageKey, customTextarea.value);
+ const currentUid = getCurrentUid();
+ const currentTextKey = `rs_prompt_${currentUid}`;
+ localStorage.setItem(currentTextKey, textWidget.value);
}
+ saveStateToProperties();
});
- updateUIForPauseMode(false);
-
return result;
};
}