implemented new feautes
+ load code with inputs and outputs + added a code save button, allowing you to save written code with inputs and outputs + example codes (js and python) add.json in the directory ./web_alekpet_nodes/lib/idenode/codes/* + visual changes and other improvements
This commit is contained in:
+3
-1
@@ -1,2 +1,4 @@
|
||||
__pycache__
|
||||
settings_nodes/
|
||||
settings_nodes/
|
||||
web_alekpet_nodes/lib/idenode/**/*.json
|
||||
!web_alekpet_nodes/lib/idenode/**/add.json
|
||||
+87
-1
@@ -1,3 +1,4 @@
|
||||
from pathlib import Path
|
||||
import types
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
@@ -5,6 +6,12 @@ import time
|
||||
import json
|
||||
import re
|
||||
|
||||
# Directory node
|
||||
DIR_IDENODE = Path(__file__)
|
||||
|
||||
# Directory with codes
|
||||
DIR_IDENODE_CODES = DIR_IDENODE.parents[1] / "web_alekpet_nodes" / "lib" / "idenode" / "codes"
|
||||
|
||||
remove_type_name = re.compile(r"(\{.*\})", re.I | re.M)
|
||||
|
||||
# Hack: string type that is always equal in not equal comparisons, thanks pythongosssss
|
||||
@@ -17,7 +24,26 @@ PY_CODE = AnyType("*")
|
||||
IDEs_DICT = {}
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/alekpet/check_js_complete")
|
||||
def isFileName(filename):
|
||||
if (
|
||||
not filename
|
||||
and filename is not None
|
||||
and (type(filename) == str and filename.strip() == "")
|
||||
):
|
||||
print("Filename is incorrect")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def getFilesInDir(folder, ext="*.json"):
|
||||
if not folder.exists():
|
||||
raise ValueError(f"[IDENode] Not found directory '{folder}'!")
|
||||
|
||||
list_files = [item.name for item in folder.rglob(ext) if not item.is_dir()]
|
||||
return list_files
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/alekpet/ide_node_check_js_complete")
|
||||
async def check_js_complete(request):
|
||||
json_data = await request.json()
|
||||
unique_id = json_data.get("unique_id", None)
|
||||
@@ -36,6 +62,66 @@ async def check_js_complete(request):
|
||||
return web.json_response({"status": "Error"})
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/alekpet/ide_node_load_codes/{lang}")
|
||||
async def load_codes(request):
|
||||
lang = request.match_info["lang"]
|
||||
list_codes = getFilesInDir(DIR_IDENODE_CODES / lang)
|
||||
|
||||
return web.json_response(
|
||||
{"codes": list_codes, "language": lang}
|
||||
)
|
||||
|
||||
@PromptServer.instance.routes.post("/alekpet/ide_node_save_code")
|
||||
async def save_code(request):
|
||||
try:
|
||||
json_data = await request.json()
|
||||
inputs = json_data.get("inputs", [])
|
||||
outputs = json_data.get("outputs", [])
|
||||
language = json_data.get("language", "python")
|
||||
code = json_data.get("code", "")
|
||||
filename = json_data.get("filename")
|
||||
|
||||
if not isFileName(filename):
|
||||
return web.json_response(
|
||||
{"message": "Filename file code is incorrect!"}, status=400
|
||||
)
|
||||
|
||||
if not language.strip():
|
||||
return web.json_response(
|
||||
{"message": "The programming language is incorrect!"}, status=400
|
||||
)
|
||||
|
||||
json_data = {
|
||||
"language": language,
|
||||
"inputs": inputs,
|
||||
"outputs": outputs,
|
||||
"code": code,
|
||||
}
|
||||
|
||||
save_path = DIR_IDENODE_CODES / language / filename
|
||||
|
||||
if not save_path.suffix.strip():
|
||||
save_path = save_path.with_suffix('.json')
|
||||
|
||||
with open(save_path, "w+", encoding="utf-8", errors="replace") as file:
|
||||
json.dump(json_data, file, ensure_ascii=False, indent=2)
|
||||
|
||||
list_codes = getFilesInDir(DIR_IDENODE_CODES / language)
|
||||
return web.json_response(
|
||||
{
|
||||
"codes": list_codes,
|
||||
"language": language,
|
||||
"message": f"Code from '{language}' save to '{save_path.name}' successfully!",
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
except OSError as e:
|
||||
return web.json_response(
|
||||
{"error": "Error: %s - %s." % (e.filename, e.strerror)}, status=500
|
||||
)
|
||||
|
||||
|
||||
def wait_js_complete(unique_id, time_out=40, poll_interval=0.2):
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < time_out:
|
||||
|
||||
+316
-48
@@ -91,17 +91,103 @@ result = runCode()
|
||||
`,
|
||||
};
|
||||
|
||||
// Default value select load code
|
||||
const defaultComboValue = "-- Select code file --";
|
||||
|
||||
// Save data to workflow forced!
|
||||
function saveValue() {
|
||||
app?.extensionManager?.workflow?.activeWorkflow?.changeTracker?.checkState();
|
||||
}
|
||||
|
||||
// Paint widget function
|
||||
function paintWidget(paramsPaint) {
|
||||
const oldDraw = this.drawWidget;
|
||||
const that = this;
|
||||
|
||||
this.draw = function () {
|
||||
// Save old params and set new
|
||||
const old_params = {};
|
||||
for (const param in paramsPaint) {
|
||||
if (!LiteGraph.hasOwnProperty(param)) continue;
|
||||
old_params[param] = LiteGraph[param];
|
||||
LiteGraph[param] = paramsPaint[param];
|
||||
}
|
||||
|
||||
oldDraw?.apply(that, arguments);
|
||||
|
||||
// Restore old params
|
||||
for (const param in old_params) {
|
||||
LiteGraph[param] = old_params[param];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Load list codes
|
||||
async function loadListCodes(language) {
|
||||
try {
|
||||
const response = await api.fetchApi(
|
||||
`/alekpet/ide_node_load_codes/${language}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
return await response.json();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { codes: [], language };
|
||||
}
|
||||
}
|
||||
|
||||
// Checks Vars
|
||||
function makeValidVariable(
|
||||
varName,
|
||||
iotype,
|
||||
textContent,
|
||||
regex = /^[a-z_][a-z0-9_]*$/i
|
||||
) {
|
||||
if (
|
||||
!varName ||
|
||||
varName.trim() === "" ||
|
||||
varName.length > MAX_CHAR_VARNAME ||
|
||||
!regex.test(varName) ||
|
||||
iotype.some((i) => i.name === varName)
|
||||
) {
|
||||
createWindowModal({
|
||||
textTitle: "WARNING",
|
||||
textBody: [
|
||||
makeElement("div", {
|
||||
style: { fontSize: "0.7rem" },
|
||||
innerHTML: textContent,
|
||||
}),
|
||||
],
|
||||
...THEMES_MODAL_WINDOW.warning,
|
||||
options: {
|
||||
auto: {
|
||||
autohide: true,
|
||||
autoremove: true,
|
||||
autoshow: true,
|
||||
timewait: 1000,
|
||||
},
|
||||
close: { showClose: false },
|
||||
overlay: { overlay_enabled: true },
|
||||
parent: widget.codeElement,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Register extensions
|
||||
app.registerExtension({
|
||||
name: "alekpet.IDENode",
|
||||
getCustomWidgets(app) {
|
||||
return {
|
||||
PYCODE: (node, inputName, inputData, app) => {
|
||||
PYCODE: async (node, inputName, inputData, app) => {
|
||||
// Wrapper and codeElement
|
||||
const codeElementWrapper = makeElement("div", {
|
||||
style: { height: "100%", marginTop: "-15px" },
|
||||
@@ -162,50 +248,14 @@ result = str(my(23, 9))`,
|
||||
}
|
||||
|
||||
widget.editor.session.setMode(`ace/mode/${v}`);
|
||||
|
||||
// Load list codes by language
|
||||
const { codes } = await loadListCodes(v);
|
||||
codeLoad.options.values = [defaultComboValue].concat(...codes);
|
||||
codeLoad.value = defaultComboValue;
|
||||
};
|
||||
}
|
||||
|
||||
// Vars
|
||||
function makeValidVariable(
|
||||
varName,
|
||||
iotype,
|
||||
textContent,
|
||||
regex = /^[a-z_][a-z0-9_]*$/i
|
||||
) {
|
||||
if (
|
||||
!varName ||
|
||||
varName.trim() === "" ||
|
||||
varName.length > MAX_CHAR_VARNAME ||
|
||||
!regex.test(varName) ||
|
||||
iotype.some((i) => i.name === varName)
|
||||
) {
|
||||
createWindowModal({
|
||||
textTitle: "WARNING",
|
||||
textBody: [
|
||||
makeElement("div", {
|
||||
style: { fontSize: "0.7rem" },
|
||||
innerHTML: textContent,
|
||||
}),
|
||||
],
|
||||
...THEMES_MODAL_WINDOW.warning,
|
||||
options: {
|
||||
auto: {
|
||||
autohide: true,
|
||||
autoremove: true,
|
||||
autoshow: true,
|
||||
timewait: 1000,
|
||||
},
|
||||
close: { showClose: false },
|
||||
overlay: { overlay_enabled: true },
|
||||
parent: widget.codeElement,
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add input vars
|
||||
node.addWidget(
|
||||
"button",
|
||||
@@ -318,11 +368,214 @@ result = str(my(23, 9))`,
|
||||
}
|
||||
);
|
||||
|
||||
// Save code
|
||||
const save_code = node.addWidget(
|
||||
"button",
|
||||
"Save code",
|
||||
"save_code",
|
||||
async () => {
|
||||
comfyuiDesktopPrompt(
|
||||
"Code filename",
|
||||
"Enter filename your code",
|
||||
codeLoad.value === defaultComboValue ? "" : codeLoad.value
|
||||
).then((filename) => {
|
||||
if (!filename || !filename.trim()) return;
|
||||
|
||||
try {
|
||||
api
|
||||
.fetchApi("/alekpet/ide_node_save_code", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
filename: filename.trim(),
|
||||
inputs: node.inputs
|
||||
.filter((i) => !["language", "pycode"].includes(i.name))
|
||||
.map((i) => ({
|
||||
name: i.name,
|
||||
type: i.type,
|
||||
})),
|
||||
outputs: node.outputs.map((o) => ({
|
||||
name: o.name,
|
||||
type: o.type,
|
||||
})),
|
||||
language: node.widgets[widgetLang_id].value,
|
||||
code: widget.editor.getValue(),
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const { codes, message, error } = data;
|
||||
|
||||
if (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
app.extensionManager.toast.add({
|
||||
severity: "success",
|
||||
summary: "Success",
|
||||
detail: message,
|
||||
life: 3000,
|
||||
});
|
||||
|
||||
codeLoad.options.values = [defaultComboValue].concat(codes);
|
||||
});
|
||||
} catch (e) {
|
||||
app.extensionManager.toast.add({
|
||||
severity: "error",
|
||||
summary: "Error",
|
||||
detail: e,
|
||||
life: 3000,
|
||||
});
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
paintWidget.apply(save_code, [
|
||||
{ WIDGET_TEXT_COLOR: "#30ce00", WIDGET_OUTLINE_COLOR: "#30ce00" },
|
||||
]); // WIDGET_BGCOLOR: "#27a700"
|
||||
|
||||
// Load code
|
||||
const codeLoad = node.addWidget(
|
||||
"COMBO",
|
||||
"Load code",
|
||||
defaultComboValue,
|
||||
async (codefile) => {
|
||||
if (codefile === defaultComboValue) return;
|
||||
|
||||
// Confirm load code?
|
||||
await app.extensionManager.dialog
|
||||
.confirm({
|
||||
title: "Confirm load code",
|
||||
message: "Are you sure you want load code?",
|
||||
type: "default",
|
||||
})
|
||||
.then((result) => {
|
||||
if (!result) codeLoad.value = defaultComboValue;
|
||||
});
|
||||
|
||||
// Load code
|
||||
fetch(
|
||||
new URL(
|
||||
`./lib/idenode/codes/${node.widgets[widgetLang_id].value}/${codefile}`,
|
||||
import.meta.url
|
||||
).toString()
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
const { inputs, outputs, code, language } = res;
|
||||
|
||||
let lang = language ?? "python";
|
||||
|
||||
// Set code language
|
||||
node.widgets[widgetLang_id].value = lang;
|
||||
widget.editor.session.setMode(`ace/mode/${lang}`);
|
||||
|
||||
const currentWidth = node.size[0];
|
||||
// Remove all input
|
||||
for (const input_idx in node.inputs) {
|
||||
const input = node.inputs[input_idx];
|
||||
|
||||
if (
|
||||
[
|
||||
"language",
|
||||
"theme_highlight",
|
||||
...inputs.map((i) => i.name),
|
||||
].includes(input.name)
|
||||
)
|
||||
continue;
|
||||
|
||||
if (input.link) {
|
||||
app.graph.removeLink(input.link);
|
||||
}
|
||||
node.removeInput(input_idx);
|
||||
}
|
||||
|
||||
// Remove all output
|
||||
for (const output_idx in node.outputs) {
|
||||
const output = node.outputs[output_idx];
|
||||
|
||||
if (
|
||||
["result", ...outputs.map((o) => o.name)].includes(
|
||||
output.name
|
||||
)
|
||||
)
|
||||
continue;
|
||||
|
||||
if (output.link) {
|
||||
app.graph.removeLink(output.link);
|
||||
}
|
||||
node.removeOutput(output_idx);
|
||||
}
|
||||
|
||||
inputs.forEach((input) => {
|
||||
const isNotInArray = !node.inputs.some(
|
||||
(i) => i.name === input.name.trim()
|
||||
);
|
||||
if (isNotInArray)
|
||||
node.addInput(input.name.trim(), input.type);
|
||||
});
|
||||
|
||||
outputs.forEach((output) => {
|
||||
const isNotInArray = !node.outputs.some(
|
||||
(o) => o.name === output.name.trim()
|
||||
);
|
||||
if (isNotInArray)
|
||||
node.addOutput(output.name.trim(), output.type);
|
||||
});
|
||||
|
||||
// Set code
|
||||
widget.editor.setValue(code);
|
||||
widget.editor.clearSelection();
|
||||
|
||||
node.setSize([currentWidth, node.size[1]]);
|
||||
node.setDirtyCanvas(true, true);
|
||||
saveValue();
|
||||
})
|
||||
.catch((e) => {
|
||||
createWindowModal({
|
||||
textTitle: "ERROR",
|
||||
textBody: [
|
||||
makeElement("div", {
|
||||
style: { fontSize: "0.7rem" },
|
||||
innerHTML: e,
|
||||
}),
|
||||
],
|
||||
...THEMES_MODAL_WINDOW.error,
|
||||
options: {
|
||||
auto: {
|
||||
autohide: true,
|
||||
autoremove: true,
|
||||
autoshow: true,
|
||||
timewait: 1500,
|
||||
},
|
||||
close: { showClose: false },
|
||||
overlay: { overlay_enabled: true },
|
||||
parent: widget.codeElement,
|
||||
},
|
||||
});
|
||||
console.error(e);
|
||||
});
|
||||
},
|
||||
{
|
||||
values: [defaultComboValue],
|
||||
default: defaultComboValue,
|
||||
}
|
||||
);
|
||||
|
||||
// Clear code button
|
||||
node.addWidget("button", "Clear", "clear_code", () => {
|
||||
widget.editor.setValue("");
|
||||
saveValue();
|
||||
});
|
||||
const clearButton = node.addWidget(
|
||||
"button",
|
||||
"Clear",
|
||||
"clear_code",
|
||||
() => {
|
||||
widget.editor.setValue("");
|
||||
saveValue();
|
||||
}
|
||||
);
|
||||
paintWidget.apply(clearButton, [
|
||||
{ WIDGET_TEXT_COLOR: "#ee0101", WIDGET_OUTLINE_COLOR: "#ee0101" },
|
||||
]);
|
||||
|
||||
node.onRemoved = function () {
|
||||
for (const w of node?.widgets) {
|
||||
@@ -380,6 +633,12 @@ result = str(my(23, 9))`,
|
||||
// });
|
||||
// }
|
||||
|
||||
// Load list codes by language
|
||||
const widgetLanguage = findWidget(this, "language", "name");
|
||||
const widgetLoadCode = findWidget(this, "Load code", "name");
|
||||
const { codes } = await loadListCodes(widgetLanguage.value);
|
||||
widgetLoadCode.options.values = [defaultComboValue].concat(codes);
|
||||
|
||||
const widgetEditor = findWidget(this, "widget_pycode", "type");
|
||||
// JS run
|
||||
api.addEventListener("alekpet_js_result", async ({ detail }) => {
|
||||
@@ -443,7 +702,7 @@ result = str(my(23, 9))`,
|
||||
res(result_run_code);
|
||||
}).then((result_code) => {
|
||||
api
|
||||
.fetchApi("/alekpet/check_js_complete", {
|
||||
.fetchApi("/alekpet/ide_node_check_js_complete", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
unique_id: this.id.toString(),
|
||||
@@ -508,7 +767,6 @@ result = str(my(23, 9))`,
|
||||
ctx.fillStyle = !colorType || colorType === "" ? "#AAA" : colorType;
|
||||
ctx.font = "12px Arial, sans-serif";
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(
|
||||
`[${type === "*" ? "any" : type.toLowerCase()}]`,
|
||||
nameSize.width + 25,
|
||||
@@ -544,6 +802,16 @@ result = str(my(23, 9))`,
|
||||
"findIndex"
|
||||
);
|
||||
|
||||
const widget_load_code_id = findWidget(
|
||||
this,
|
||||
"Load code",
|
||||
"name",
|
||||
"findIndex"
|
||||
);
|
||||
|
||||
this.widgets[widget_load_code_id].value =
|
||||
this.widgets[widget_load_code_id].options?.default;
|
||||
|
||||
const editor = this.widgets[widget_code_id]?.editor;
|
||||
|
||||
if (editor) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"language": "javascript",
|
||||
"inputs": [
|
||||
{ "name": "a", "type": "*" },
|
||||
{ "name": "b", "type": "*" }
|
||||
],
|
||||
"outputs": [{ "name": "result", "type": "*" }],
|
||||
"code": "result = a + b"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"language": "python",
|
||||
"inputs": [
|
||||
{ "name": "a", "type": "*" },
|
||||
{ "name": "b", "type": "*" }
|
||||
],
|
||||
"outputs": [{ "name": "result", "type": "*" }],
|
||||
"code": "result = a + b"
|
||||
}
|
||||
Reference in New Issue
Block a user