IdeNode Implemented add output variables and types
This commit is contained in:
@@ -4,6 +4,10 @@
|
||||
|
||||
**IDE Node** is an experimental node that allows you to run code written in **Python** or **Javascript** directly in the node, it is also possible to receive input data, which is stored in variables, process and output the result.
|
||||
|
||||
# Changelog:
|
||||
|
||||
- 2024.08.28: Implemented add output variables and types
|
||||
|
||||
# Features:
|
||||
|
||||
- Selecting a syntax highlighting theme
|
||||
|
||||
+46
-12
@@ -3,7 +3,9 @@ from server import PromptServer
|
||||
from aiohttp import web
|
||||
from asyncio import sleep, run
|
||||
import json
|
||||
import re
|
||||
|
||||
remove_type_name = re.compile(r"(\{.*\})", re.I | re.M)
|
||||
|
||||
# Hack: string type that is always equal in not equal comparisons, thanks pythongosssss
|
||||
class AnyType(str):
|
||||
@@ -49,6 +51,24 @@ async def wait_js_complete(unique_id, time_out=40):
|
||||
return False
|
||||
|
||||
|
||||
# - Thank you very much for the class -> Trung0246 -
|
||||
# - https://github.com/Trung0246/ComfyUI-0246/blob/main/utils.py#L51
|
||||
class TautologyStr(str):
|
||||
def __ne__(self, other):
|
||||
return False
|
||||
|
||||
|
||||
class ByPassTypeTuple(tuple):
|
||||
def __getitem__(self, index):
|
||||
if index > 0:
|
||||
index = 0
|
||||
item = super().__getitem__(index)
|
||||
if isinstance(item, str):
|
||||
return TautologyStr(item)
|
||||
return item
|
||||
# ---------------------------
|
||||
|
||||
|
||||
class IDENode:
|
||||
def __init__(self):
|
||||
self.js_complete = False
|
||||
@@ -78,43 +98,57 @@ result = runCode()"""
|
||||
},
|
||||
),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"},
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (PY_CODE,)
|
||||
RETURN_NAMES = ("any",)
|
||||
RETURN_TYPES = ByPassTypeTuple((PY_CODE,))
|
||||
RETURN_NAMES = ("result{ANY}",)
|
||||
FUNCTION = "exec_py"
|
||||
|
||||
CATEGORY = "AlekPet Nodes/experiments"
|
||||
|
||||
def exec_py(self, pycode, language, unique_id, **kwargs):
|
||||
def exec_py(self, pycode, language, unique_id, extra_pnginfo, **kwargs):
|
||||
if unique_id not in IDEs_DICT:
|
||||
IDEs_DICT[unique_id] = self
|
||||
|
||||
if language == "python":
|
||||
my_namespace = types.SimpleNamespace()
|
||||
my_namespace.__dict__.update(kwargs)
|
||||
my_namespace.__dict__.setdefault("result", "The result variable is not assigned")
|
||||
|
||||
outputs = {}
|
||||
|
||||
for node in extra_pnginfo['workflow']['nodes']:
|
||||
if node['id'] == int(unique_id):
|
||||
outputs_valid = [ouput for ouput in node.get('outputs', []) if ouput.get('name','') != '' and ouput.get('type','') != '']
|
||||
outputs = {re.sub(remove_type_name, "", ouput['name']): None for ouput in outputs_valid}
|
||||
self.RETURN_TYPES = ByPassTypeTuple(out["type"] for out in outputs_valid)
|
||||
self.RETURN_NAMES = tuple(name for name in outputs.keys())
|
||||
|
||||
my_namespace = types.SimpleNamespace()
|
||||
my_namespace.__dict__.update(outputs)
|
||||
my_namespace.__dict__.update({re.sub(remove_type_name, "", prop): kwargs[prop] for prop in kwargs})
|
||||
my_namespace.__dict__.setdefault("result", "The result variable is not assigned")
|
||||
|
||||
if language == "python":
|
||||
try:
|
||||
exec(pycode, my_namespace.__dict__)
|
||||
except Exception as e:
|
||||
my_namespace.result = f"Error in python code: {e}"
|
||||
|
||||
return (my_namespace.result,)
|
||||
new_dict = {key: my_namespace.__dict__[key] for key in my_namespace.__dict__ if key not in ['__builtins__', *kwargs.keys()] and not callable(my_namespace.__dict__[key])}
|
||||
|
||||
return (*new_dict.values(),)
|
||||
|
||||
else:
|
||||
IDEs_DICT[unique_id].js_complete = False
|
||||
IDEs_DICT[unique_id].js_result = None
|
||||
|
||||
new_dict = {key: my_namespace.__dict__[key] for key in my_namespace.__dict__ if key not in ['__builtins__', *kwargs.keys()] and not callable(my_namespace.__dict__[key])}
|
||||
|
||||
PromptServer.instance.send_sync(
|
||||
"alekpet_js_result",
|
||||
{"unique_id": unique_id, "vars": json.dumps(kwargs)},
|
||||
{"unique_id": unique_id, "vars": json.dumps(new_dict)},
|
||||
)
|
||||
if not run(wait_js_complete(unique_id)):
|
||||
print(f"IDENode_{unique_id}: Failed to get data!")
|
||||
else:
|
||||
print(f"IDENode_{unique_id}: Data received successful!")
|
||||
|
||||
print(IDEs_DICT[unique_id].js_result)
|
||||
return (IDEs_DICT[unique_id].js_result,)
|
||||
return (*IDEs_DICT[unique_id].js_result,)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 243 KiB |
+158
-24
@@ -17,7 +17,7 @@ ace.config.set(
|
||||
);
|
||||
|
||||
// Constants
|
||||
const MAX_CHAR_VARNAME = 20;
|
||||
const MAX_CHAR_VARNAME = 50;
|
||||
const LIST_THEMES = [
|
||||
"ambiance",
|
||||
"chaos",
|
||||
@@ -77,7 +77,7 @@ const runCode = () => {
|
||||
const date = new Date().toJSON().replace("T"," ");
|
||||
return date.slice(0,date.indexOf("."))
|
||||
}
|
||||
result = runCode();`,
|
||||
result = \`Hello ComfyUI with us today \${runCode()}!\``,
|
||||
py: `# !!! Attention, do not insert unverified code !!!
|
||||
# ---- Example code ----
|
||||
# Globals inputs variables: var1, var2, var3, user variables ...
|
||||
@@ -227,23 +227,23 @@ app.registerExtension({
|
||||
};
|
||||
}
|
||||
|
||||
node.addWidget("button", "Add Variable", "add_variable", () => {
|
||||
const nameInput = node?.inputs?.length
|
||||
? `var${node.inputs.length + 1}`
|
||||
: "var1";
|
||||
const varName = prompt("Enter variable name:", nameInput);
|
||||
|
||||
function makeValidVariable(
|
||||
varName,
|
||||
textContent,
|
||||
regex = /^[a-z_][a-z0-9_]*$/i
|
||||
) {
|
||||
if (
|
||||
!varName ||
|
||||
varName.trim() === "" ||
|
||||
varName.length > MAX_CHAR_VARNAME ||
|
||||
!/^[a-z_][a-z0-9_]*$/i.test(varName)
|
||||
!regex.test(varName)
|
||||
) {
|
||||
const windowError = createWindowModal({
|
||||
createWindowModal({
|
||||
textTitle: "WARNING",
|
||||
textBody: [
|
||||
makeElement("div", {
|
||||
innerHTML: `<h3>Variable name is incorrect!</h3><ul style="text-align:left;padding: 2px;"><li>starts with a number</li><li>has spaces or tabs</li><li>is empty</li><li>variable name is greater ${MAX_CHAR_VARNAME}</li></ul>`,
|
||||
style: { fontSize: "0.7rem" },
|
||||
innerHTML: textContent,
|
||||
}),
|
||||
],
|
||||
...THEMES_MODAL_WINDOW.warning,
|
||||
@@ -258,12 +258,105 @@ app.registerExtension({
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const currentWidth = node.size[0];
|
||||
node.addInput(varName, "*");
|
||||
node.setSize([currentWidth, node.size[1]]);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
node.addWidget(
|
||||
"button",
|
||||
"Add Input variable",
|
||||
"add_input_variable",
|
||||
() => {
|
||||
// Input name variable and check
|
||||
const nameInput = node?.inputs?.length
|
||||
? `var${node.inputs.length + 1}`
|
||||
: "var1";
|
||||
|
||||
const varName = prompt(
|
||||
"Enter input variable name:",
|
||||
nameInput
|
||||
).trim();
|
||||
|
||||
if (
|
||||
!makeValidVariable(
|
||||
varName,
|
||||
`<h3>Variable for <span style="color: limegreen">Input</span> name is incorrect!</h3><ul style="text-align:left;padding: 2px;margin-left: 5%;"><li>starts with a number</li><li>has spaces or tabs</li><li>is empty</li><li>variable name is greater ${MAX_CHAR_VARNAME}</li></ul>`
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
||||
// Type variable and check
|
||||
let type = prompt(
|
||||
"Enter type data output (default: *):",
|
||||
"*"
|
||||
).trim();
|
||||
|
||||
if (
|
||||
!makeValidVariable(
|
||||
type,
|
||||
`<h3>Type value is incorrect!</h3><ul style="text-align:left;padding: 2px;"><li>has spaces or tabs</li><li>is empty</li><li>type value length is greater ${MAX_CHAR_VARNAME}</li></ul>`,
|
||||
/^[*a-z_][a-z0-9_]*$/i
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
||||
const currentWidth = node.size[0];
|
||||
node.addInput(
|
||||
`${varName}{${type === "*" ? "ANY" : type.toUpperCase()}}`,
|
||||
type
|
||||
);
|
||||
node.setSize([currentWidth, node.size[1]]);
|
||||
}
|
||||
);
|
||||
|
||||
node.addWidget(
|
||||
"button",
|
||||
"Add Output variable",
|
||||
"add_output_variable",
|
||||
() => {
|
||||
const currentWidth = node.size[0];
|
||||
|
||||
// Output name variable
|
||||
const nameOutput = node?.outputs?.length
|
||||
? `result${node.outputs.length + 1}`
|
||||
: "result1";
|
||||
const varName = prompt(
|
||||
"Enter output variable name:",
|
||||
nameOutput
|
||||
).trim();
|
||||
|
||||
// Check output variable name
|
||||
if (
|
||||
!makeValidVariable(
|
||||
varName,
|
||||
`<h3>Variable for <span style="color: pink">Output</span> name is incorrect!</h3><ul style="text-align:left;padding: 2px;margin-left: 5%;"><li>starts with a number</li><li>has spaces or tabs</li><li>is empty</li><li>variable name is greater ${MAX_CHAR_VARNAME}</li></ul>`
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
||||
// Type variable and check
|
||||
let type = prompt(
|
||||
"Enter type data output (default: *):",
|
||||
"*"
|
||||
).trim();
|
||||
|
||||
if (
|
||||
!makeValidVariable(
|
||||
type,
|
||||
`<h3>Type value is incorrect!</h3><ul style="text-align:left;padding: 2px;"><li>has spaces or tabs</li><li>is empty</li><li>type value length is greater ${MAX_CHAR_VARNAME}</li></ul>`,
|
||||
/^[*a-z_][a-z0-9_]*$/i
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
||||
node.addOutput(
|
||||
`${varName}{${type === "*" ? "ANY" : type.toUpperCase()}}`,
|
||||
type
|
||||
);
|
||||
node.setSize([currentWidth, node.size[1]]);
|
||||
}
|
||||
);
|
||||
|
||||
node.addWidget("button", "Clear", "clear_code", () => {
|
||||
widget.editor.setValue("");
|
||||
@@ -300,7 +393,7 @@ app.registerExtension({
|
||||
|
||||
// Create default inputs, when first create node
|
||||
if (!this?.inputs) {
|
||||
["var1", "var2", "var3"].forEach((inputName) => {
|
||||
["var1{ANY}", "var2{ANY}", "var3{ANY}"].forEach((inputName) => {
|
||||
const currentWidth = this.size[0];
|
||||
this.addInput(inputName, "*");
|
||||
this.setSize([currentWidth, this.size[1]]);
|
||||
@@ -311,13 +404,14 @@ app.registerExtension({
|
||||
// JS run
|
||||
api.addEventListener("alekpet_js_result", async ({ detail }) => {
|
||||
const { vars, unique_id } = detail;
|
||||
|
||||
if ((vars && !Object.keys(vars).length) || +unique_id !== this.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise((res) => {
|
||||
const edit_vars = JSON.parse(vars);
|
||||
let code_run = `\nlet result = null;\n`;
|
||||
let code_run = "";
|
||||
for (let [k, v] of Object.entries(edit_vars)) {
|
||||
// Check type
|
||||
if (v instanceof String || typeof v === "string") {
|
||||
@@ -328,15 +422,36 @@ app.registerExtension({
|
||||
code_run += `let ${k} = ${v};\n`;
|
||||
}
|
||||
|
||||
code_run += `${widgetEditor.editor.getValue()}\nreturn result\n`;
|
||||
code_run += `${widgetEditor.editor.getValue()}\nreturn [${Object.keys(
|
||||
edit_vars
|
||||
)},]\n`;
|
||||
let result_run_code = null;
|
||||
try {
|
||||
result_run_code = eval(`(function(){${code_run}}())`);
|
||||
} catch (e) {
|
||||
result_run_code = `Error in javascript code: ${e}`;
|
||||
console.error(`<${this.name}> ${result_run_code}`);
|
||||
createWindowModal({
|
||||
textTitle: "Error in javascript code",
|
||||
textBody: [
|
||||
makeElement("div", {
|
||||
style: { fontSize: "0.7rem" },
|
||||
textContent: e,
|
||||
}),
|
||||
],
|
||||
...THEMES_MODAL_WINDOW.error,
|
||||
options: {
|
||||
auto: {
|
||||
autohide: true,
|
||||
autoremove: true,
|
||||
autoshow: true,
|
||||
timewait: 2000,
|
||||
},
|
||||
parent: widgetEditor.codeElement,
|
||||
},
|
||||
});
|
||||
res(Object.keys(edit_vars).map((v) => e.message));
|
||||
}
|
||||
|
||||
res(result_run_code);
|
||||
}).then((result_code) => {
|
||||
api
|
||||
@@ -344,7 +459,7 @@ app.registerExtension({
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
unique_id: this.id.toString(),
|
||||
result_code: JSON.stringify(result_code),
|
||||
result_code,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
@@ -360,7 +475,7 @@ app.registerExtension({
|
||||
});
|
||||
});
|
||||
|
||||
this.setSize([500, 350]);
|
||||
this.setSize([530, this.size[1]]);
|
||||
|
||||
return ret;
|
||||
};
|
||||
@@ -409,12 +524,11 @@ app.registerExtension({
|
||||
nodeType.prototype.getExtraMenuOptions = function (_, options) {
|
||||
getExtraMenuOptions?.apply(this, arguments);
|
||||
|
||||
const lastSepId = options.lastIndexOf(null);
|
||||
const past_index = options.length - 1;
|
||||
const past = options[past_index];
|
||||
|
||||
// options.splice(lastSepId, 0, null);
|
||||
if (!!past) {
|
||||
// Inputs remove
|
||||
for (const input_idx in this.inputs) {
|
||||
const input = this.inputs[input_idx];
|
||||
|
||||
@@ -432,6 +546,26 @@ app.registerExtension({
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Output remove
|
||||
for (const output_idx in this.outputs) {
|
||||
const output = this.outputs[output_idx];
|
||||
|
||||
if (["result{ANY}"].includes(output.name)) continue;
|
||||
|
||||
options.splice(past_index + 1, 0, {
|
||||
content: `Remove Output ${output.name}`,
|
||||
callback: (e) => {
|
||||
const currentWidth = this.size[0];
|
||||
console.log(output);
|
||||
if (output.link) {
|
||||
app.graph.removeLink(output.link);
|
||||
}
|
||||
this.removeOutput(output_idx);
|
||||
this.setSize([currentWidth, this.size[1]]);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
// end - ExtraMenuOptions
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui_custom_nodes_alekpet"
|
||||
description = "Nodes: PoseNode, PainterNode, TranslateTextNode, TranslateCLIPTextEncodeNode, DeepTranslatorTextNode, DeepTranslatorCLIPTextEncodeNode, ArgosTranslateTextNode, ArgosTranslateCLIPTextEncodeNode, PreviewTextNode, HexToHueNode, ColorsCorrectNode, IDENode."
|
||||
version = "1.0.20"
|
||||
version = "1.0.21"
|
||||
license = { file = "LICENSE" }
|
||||
|
||||
[project.urls]
|
||||
|
||||
Reference in New Issue
Block a user