Fix widget bugs for subgraph node connected to BV Control Center widget
This commit is contained in:
@@ -243,6 +243,7 @@ Here is an example if, for example, Only Preview is active
|
||||
|
||||
|
||||
### CHANGE LOG
|
||||
* 2026-01-02 - v0.0.17: Change toggle label system from `BV Control Center` widget.
|
||||
* 2026-01-02 - v0.0.16: Change input names to prevent possible collisions on `BV Control Center` widget.
|
||||
* 2026-01-01 - v0.0.15: Fix renaming bug for subgraph node connected to `BV Control Center` widget.
|
||||
* 2026-01-01 - v0.0.14: Fix widget bugs for subgraph node connected to `BV Control Center` widget.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
AST = "BV_AST"
|
||||
|
||||
class BVPromptEncodeNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {"prompt": "STRING", {d}}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
from typing import Dict, Any, List
|
||||
|
||||
|
||||
def parse_prompt_to_ast(text: str) -> Dict[str, Any]:
|
||||
def make_node(t: str, **kwargs):
|
||||
n = {"type": t}
|
||||
n.update(kwargs)
|
||||
return n
|
||||
|
||||
root = make_node("doc", children=[])
|
||||
current_block = make_node("block", cat="default", children=[])
|
||||
root["children"].append(current_block)
|
||||
|
||||
# Stack for nested spans: each entry is a node with children
|
||||
span_stack: List[Dict[str, Any]] = []
|
||||
|
||||
def current_container_children() -> List[Dict[str, Any]]:
|
||||
if span_stack:
|
||||
return span_stack[-1]["children"]
|
||||
return current_block["children"]
|
||||
|
||||
def emit_text(s: str):
|
||||
if not s:
|
||||
return
|
||||
children = current_container_children()
|
||||
# merge adjacent text nodes to keep AST compact
|
||||
if children and children[-1]["type"] == "text":
|
||||
children[-1]["v"] += s
|
||||
else:
|
||||
children.append(make_node("text", v=s))
|
||||
|
||||
def is_valid_cat(cat: str) -> bool:
|
||||
return bool(cat) and all(ch.isalnum() or ch in "_-" for ch in cat)
|
||||
|
||||
for line in text.splitlines(keepends=True):
|
||||
# Block directive only if standalone line starting with @@
|
||||
if line.startswith("@@"):
|
||||
raw = line[2:]
|
||||
cat_candidate = raw.strip("\r\n").strip()
|
||||
if cat_candidate == "" or is_valid_cat(cat_candidate):
|
||||
# close any open spans (fail-soft: auto-close at block boundary)
|
||||
span_stack.clear()
|
||||
current_block = make_node("block", cat=(cat_candidate or "default"), children=[])
|
||||
root["children"].append(current_block)
|
||||
continue
|
||||
|
||||
i = 0
|
||||
while i < len(line):
|
||||
ch = line[i]
|
||||
|
||||
# Try span start: {cat| ...}
|
||||
if ch == "{":
|
||||
j = i + 1
|
||||
cat_chars = []
|
||||
while j < len(line) and line[j] not in "|}":
|
||||
cat_chars.append(line[j])
|
||||
j += 1
|
||||
if j < len(line) and line[j] == "|":
|
||||
cat = "".join(cat_chars).strip()
|
||||
if is_valid_cat(cat):
|
||||
# start span
|
||||
span = make_node("span", cat=cat, children=[])
|
||||
current_container_children().append(span)
|
||||
span_stack.append(span)
|
||||
i = j + 1
|
||||
continue
|
||||
# else: treat '{' as literal
|
||||
|
||||
# Span end
|
||||
if ch == "}" and span_stack:
|
||||
span_stack.pop()
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Normal char
|
||||
emit_text(ch)
|
||||
i += 1
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def ast_to_plain_text(ast: Dict[str, Any]) -> str:
|
||||
out: List[str] = []
|
||||
|
||||
def walk(node: Dict[str, Any]):
|
||||
t = node["type"]
|
||||
if t == "text":
|
||||
out.append(node["v"])
|
||||
else:
|
||||
for c in node.get("children", []):
|
||||
walk(c)
|
||||
|
||||
walk(ast)
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def ast_to_tagged_text(ast: Dict[str, Any]) -> str:
|
||||
out: List[str] = []
|
||||
|
||||
def walk(node: Dict[str, Any]):
|
||||
t = node["type"]
|
||||
if t == "doc":
|
||||
for c in node["children"]:
|
||||
walk(c)
|
||||
|
||||
elif t == "block":
|
||||
cat = node.get("cat", "default")
|
||||
out.append(f"@@{'' if cat == 'default' else cat}\n")
|
||||
for c in node.get("children", []):
|
||||
walk(c)
|
||||
|
||||
elif t == "span":
|
||||
out.append("{%s|" % node.get("cat", ""))
|
||||
for c in node.get("children", []):
|
||||
walk(c)
|
||||
out.append("}")
|
||||
|
||||
elif t == "text":
|
||||
out.append(node["v"])
|
||||
|
||||
walk(ast)
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def extract_text_by_category(ast: Dict[str, Any], cat: str) -> str:
|
||||
out: List[str] = []
|
||||
cat = cat.strip()
|
||||
|
||||
def walk(node: Dict[str, Any], active: List[str]):
|
||||
t = node["type"]
|
||||
|
||||
if t == "block":
|
||||
active2 = active + ([node["cat"]] if node["cat"] != "default" else [])
|
||||
for c in node.get("children", []):
|
||||
walk(c, active2)
|
||||
|
||||
elif t == "span":
|
||||
active2 = active + [node["cat"]]
|
||||
for c in node.get("children", []):
|
||||
walk(c, active2)
|
||||
|
||||
elif t == "text":
|
||||
if cat in active:
|
||||
out.append(node["v"])
|
||||
|
||||
else: # doc
|
||||
for c in node.get("children", []):
|
||||
walk(c, active)
|
||||
|
||||
walk(ast, [])
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def ast_to_plain_text(ast: Dict[str, Any]) -> str:
|
||||
out: List[str] = []
|
||||
|
||||
def walk(node: Dict[str, Any]):
|
||||
t = node.get("type")
|
||||
|
||||
if t == "text":
|
||||
out.append(node.get("v", ""))
|
||||
return
|
||||
|
||||
for child in node.get("children", []) or []:
|
||||
walk(child)
|
||||
|
||||
walk(ast)
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def ast_to_tagged_text(ast: Dict[str, Any]) -> str:
|
||||
out: List[str] = []
|
||||
|
||||
def walk(node: Dict[str, Any]):
|
||||
t = node.get("type")
|
||||
|
||||
if t == "doc":
|
||||
for child in node.get("children", []) or []:
|
||||
walk(child)
|
||||
return
|
||||
|
||||
if t == "block":
|
||||
cat = node.get("cat", "default")
|
||||
out.append(f"@@{'' if cat == 'default' else cat}\n")
|
||||
for child in node.get("children", []) or []:
|
||||
walk(child)
|
||||
return
|
||||
|
||||
if t == "span":
|
||||
cat = node.get("cat", "")
|
||||
out.append("{%s|" % cat)
|
||||
for child in node.get("children", []) or []:
|
||||
walk(child)
|
||||
out.append("}")
|
||||
return
|
||||
|
||||
if t == "text":
|
||||
out.append(node.get("v", ""))
|
||||
return
|
||||
|
||||
# Unknown node types: best-effort traverse
|
||||
for child in node.get("children", []) or []:
|
||||
walk(child)
|
||||
|
||||
walk(ast)
|
||||
return "".join(out)
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "bv_nodepack"
|
||||
version = "0.0.16"
|
||||
version = "0.0.17"
|
||||
description = "A ComfyUI node pack providing advanced utility and workflow nodes, including a config-driven Control Center for muting and bypassing node groups, a flexible Pipe system, and Subgraph-friendly UI nodes (Heading, Spacer, Divider) with dynamic JavaScript rendering."
|
||||
authors = [
|
||||
{name = "BlackVortex", email = "blackvortexx@proton.me"}
|
||||
|
||||
@@ -20,8 +20,8 @@ import {
|
||||
} from "../../util/control/subgraphHandler";
|
||||
import {patchGraphAddSingletonGuard} from "../../util/control/singletonHandler";
|
||||
|
||||
const ACTIVE = "( # ACTIVE # )";
|
||||
const MUTE = "(MUTE/BYPASS)";
|
||||
const ACTIVE = "ACTIVE";
|
||||
const MUTE = "MUTE/BYPASS";
|
||||
|
||||
const comfyApp = getApp();
|
||||
|
||||
@@ -99,14 +99,8 @@ function updateSingleControlNode(node: any, config: BVControlConfig, savedValues
|
||||
// Ensure enough inputs
|
||||
while (node.inputs.length < desiredCount) {
|
||||
const i = node.inputs.length;
|
||||
const inputName = `v_${String(i).padStart(3, "0")}`;
|
||||
let label = config.rows[i].title;
|
||||
|
||||
if (getWidgetValue(node, i)) {
|
||||
label = label + " - " + ACTIVE
|
||||
} else {
|
||||
label = label + " - " + MUTE
|
||||
}
|
||||
const inputName = `bvcc_${String(i).padStart(3, "0")}`;
|
||||
const label = config.rows[i].title;
|
||||
|
||||
node.addInput(inputName, "BOOLEAN", {
|
||||
label,
|
||||
@@ -116,7 +110,8 @@ function updateSingleControlNode(node: any, config: BVControlConfig, savedValues
|
||||
});
|
||||
|
||||
const initialValue = (savedValues && savedValues[i] !== undefined) ? savedValues[i] : true;
|
||||
const w = node.addWidget("toggle", inputName, initialValue, () => node.setDirtyCanvas(true));
|
||||
// (node as LGraphNode).addWidget()
|
||||
const w = node.addWidget("toggle", inputName, initialValue, () => node.setDirtyCanvas(true), {on: ACTIVE, off: MUTE});
|
||||
w.label = label;
|
||||
w.serialize = true;
|
||||
|
||||
@@ -128,7 +123,7 @@ function updateSingleControlNode(node: any, config: BVControlConfig, savedValues
|
||||
// Remove extra inputs
|
||||
while (node.inputs.length > desiredCount) {
|
||||
const i = node.inputs.length - 1;
|
||||
const inputName = `v_${String(i).padStart(3, "0")}`;
|
||||
const inputName = `bvcc_${String(i).padStart(3, "0")}`;
|
||||
|
||||
const wi = node.widgets?.findIndex((w: any) => w.name === inputName) ?? -1;
|
||||
if (wi !== -1) node.widgets.splice(wi, 1);
|
||||
@@ -143,13 +138,7 @@ function updateSingleControlNode(node: any, config: BVControlConfig, savedValues
|
||||
for (let i = 0; i < desiredCount; i++) {
|
||||
let label = config.rows[i].title;
|
||||
|
||||
if (getWidgetValue(node, i)) {
|
||||
label = label + " - " + ACTIVE
|
||||
} else {
|
||||
label = label + " - " + MUTE
|
||||
}
|
||||
|
||||
const inputName = `v_${String(i).padStart(3, "0")}`;
|
||||
const inputName = `bvcc_${String(i).padStart(3, "0")}`;
|
||||
|
||||
if (node.inputs[i]?.label !== label) {
|
||||
node.inputs[i].label = label;
|
||||
@@ -159,17 +148,21 @@ function updateSingleControlNode(node: any, config: BVControlConfig, savedValues
|
||||
let w = node.widgets.find((ww: any) => ww.name === inputName);
|
||||
if (!w) {
|
||||
const initialValue = (savedValues && savedValues[i] !== undefined) ? savedValues[i] : true;
|
||||
w = node.addWidget("toggle", inputName, initialValue, () => node.setDirtyCanvas(true));
|
||||
w.label = label;
|
||||
w = node.addWidget("toggle", inputName, initialValue, () => node.setDirtyCanvas(true), {on: ACTIVE, off: MUTE});
|
||||
w.serialize = true;
|
||||
w.label = label;
|
||||
changed = true;
|
||||
} else {
|
||||
if (w.label !== label) {
|
||||
w.label = label;
|
||||
changed = true;
|
||||
if (w.options) {
|
||||
if (w.options.on !== ACTIVE || w.options.off !== MUTE) {
|
||||
w.options.on = ACTIVE;
|
||||
w.options.off = MUTE;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (overrideValue && savedValues && savedValues[i] !== undefined && w.value !== savedValues[i]) {
|
||||
w.value = savedValues[i];
|
||||
w.label = label;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -247,15 +240,8 @@ function updateControlNodes(config: BVControlConfig, savedValuesOverride?: any[]
|
||||
const commonCount = Math.min(node.inputs.length, config.rows.length);
|
||||
for (let i = 0; i < commonCount; i++) {
|
||||
let label = config.rows[i].title;
|
||||
const rawLabel = config.rows[i].title;
|
||||
|
||||
if (getWidgetValue(node, i)) {
|
||||
label = label + " - " + ACTIVE
|
||||
} else {
|
||||
label = label + " - " + MUTE
|
||||
}
|
||||
|
||||
const inputName = `v_${String(i).padStart(3, "0")}`;
|
||||
const inputName = `bvcc_${String(i).padStart(3, "0")}`;
|
||||
|
||||
if (node.inputs[i].label !== label) {
|
||||
node.inputs[i].label = label;
|
||||
@@ -265,8 +251,7 @@ function updateControlNodes(config: BVControlConfig, savedValuesOverride?: any[]
|
||||
let widget = node.widgets.find(w => w.name === inputName);
|
||||
if (!widget) {
|
||||
const initialValue = (savedValues && savedValues[i] !== undefined) ? savedValues[i] : true;
|
||||
widget = node.addWidget("toggle", inputName, initialValue, () => node.setDirtyCanvas(true));
|
||||
widget.label = label;
|
||||
widget = node.addWidget("toggle", inputName, initialValue, () => node.setDirtyCanvas(true), {on: ACTIVE, off: MUTE});
|
||||
widget.serialize = true;
|
||||
changed = true;
|
||||
} else {
|
||||
@@ -275,9 +260,12 @@ function updateControlNodes(config: BVControlConfig, savedValuesOverride?: any[]
|
||||
widget.value = savedValues[i];
|
||||
changed = true;
|
||||
}
|
||||
if (widget.label !== label) {
|
||||
widget.label = label;
|
||||
changed = true;
|
||||
if (widget.options) {
|
||||
if (widget.options.on !== ACTIVE || widget.options.off !== MUTE) {
|
||||
widget.options.on = ACTIVE;
|
||||
widget.options.off = MUTE;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +278,7 @@ function updateControlNodes(config: BVControlConfig, savedValuesOverride?: any[]
|
||||
|
||||
if (node.inputs.length > config.rows.length) {
|
||||
for (let i = node.inputs.length - 1; i >= config.rows.length; i--) {
|
||||
const inputName = `v_${String(i).padStart(3, "0")}`;
|
||||
const inputName = `bvcc_${String(i).padStart(3, "0")}`;
|
||||
const widgetIndex = node.widgets?.findIndex(w => w.name === inputName) ?? -1;
|
||||
if (widgetIndex !== -1) node.widgets.splice(widgetIndex, 1);
|
||||
if (node.isInputConnected(i)) node.disconnectInput(i);
|
||||
@@ -300,12 +288,7 @@ function updateControlNodes(config: BVControlConfig, savedValuesOverride?: any[]
|
||||
} else if (node.inputs.length < config.rows.length) {
|
||||
for (let i = node.inputs.length; i < config.rows.length; i++) {
|
||||
let label = config.rows[i].title;
|
||||
if (getWidgetValue(node, i)) {
|
||||
label = label + " - " + ACTIVE
|
||||
} else {
|
||||
label = label + " - " + MUTE
|
||||
}
|
||||
const inputName = `v_${String(i).padStart(3, "0")}`;
|
||||
const inputName = `bvcc_${String(i).padStart(3, "0")}`;
|
||||
|
||||
node.addInput(inputName, "BOOLEAN", {
|
||||
label,
|
||||
@@ -315,9 +298,7 @@ function updateControlNodes(config: BVControlConfig, savedValuesOverride?: any[]
|
||||
});
|
||||
|
||||
const initialValue = (savedValues && savedValues[i] !== undefined) ? savedValues[i] : true;
|
||||
const widget = node.addWidget("toggle", inputName, initialValue, () => node.setDirtyCanvas(true));
|
||||
// @ts-ignore
|
||||
widget.label = label;
|
||||
const widget = node.addWidget("toggle", inputName, initialValue, () => node.setDirtyCanvas(true), {on: ACTIVE, off: MUTE});
|
||||
widget.serialize = true;
|
||||
|
||||
// @ts-ignore
|
||||
@@ -350,11 +331,11 @@ function hookWidgetChanged(node: any) {
|
||||
|
||||
const config = readConfig();
|
||||
if (config) {
|
||||
// const title = widget.label?.split(" - ")[0]
|
||||
const title = widget.label
|
||||
const index = node.widgets.indexOf(widget);
|
||||
const title = (index !== -1 && node.inputs[index]) ? node.inputs[index].label : widget.label;
|
||||
if (!title) return;
|
||||
executeChange(title, value);
|
||||
updateSingleControlNode(node, config, undefined, false);
|
||||
// updateSingleControlNode(node, config, undefined, false);
|
||||
}
|
||||
|
||||
return r
|
||||
@@ -392,7 +373,7 @@ function executeChange(title: string, value: boolean): void {
|
||||
|
||||
const applyDisable = !value;
|
||||
|
||||
const foundRow = config.rows.find(row => row.title.split(" - ")[0] === title.split(" - ")[0]);
|
||||
const foundRow = config.rows.find(row => row.title === title);
|
||||
if (!foundRow) return;
|
||||
for (const entry of foundRow.entries) {
|
||||
const groupTitle = normalizeGroupTitle(entry);
|
||||
@@ -425,7 +406,10 @@ comfyApp.registerExtension({
|
||||
|
||||
const cfg = readConfig();
|
||||
if (cfg) {
|
||||
updateSingleControlNode(this, cfg);
|
||||
setTimeout(() => {
|
||||
|
||||
updateSingleControlNode(this, cfg);
|
||||
}, 50)
|
||||
}
|
||||
|
||||
hookWidgetChanged(this)
|
||||
@@ -539,11 +523,6 @@ comfyApp.registerExtension({
|
||||
}
|
||||
|
||||
setWidgetValue(sNode, sWidget.name, value)
|
||||
|
||||
const config = readConfig()
|
||||
if(config){
|
||||
updateSingleControlNode(ccNode, config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +544,6 @@ function patchAllSubgraphs(): number {
|
||||
|
||||
let patched = 0;
|
||||
for (const s of subs) {
|
||||
console.log("patching", s.title)
|
||||
const didPatch = patchSubgraphContainerPrototype(s, hookSubgraphWidgetChanged, executeChange);
|
||||
if (didPatch) patched++;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ export function patchSubgraphContainerPrototype(subgraphNode: any, hookSubgraphW
|
||||
if (node && iNode) {
|
||||
console.debug("Subgraph container widget changed", name, value, old_value, widget, node, widget.label)
|
||||
// hookSubgraphWidgetChanged(node, name)
|
||||
widget.label = widget.label.split(" - ")[0] + " - " + (value ? ACTIVE : MUTE)
|
||||
// widget.label = widget.label.split(" - ")[0] + " - " + (value ? ACTIVE : MUTE)
|
||||
|
||||
iNode.widgets?.forEach((w) => {
|
||||
if(w.name == sWidget.name){
|
||||
|
||||
Reference in New Issue
Block a user