|
|
import { app } from "../../scripts/app.js"; |
|
|
import { api } from "../../scripts/api.js"; |
|
|
import "./lib/idenode/ace-builds/src-min-noconflict/ace.js"; |
|
|
import { |
|
|
createWindowModal, |
|
|
makeElement, |
|
|
findWidget, |
|
|
THEMES_MODAL_WINDOW, |
|
|
} from "./utils.js"; |
|
|
|
|
|
ace.config.set( |
|
|
"basePath", |
|
|
new URL( |
|
|
"./lib/idenode/ace-builds/src-min-noconflict/", |
|
|
import.meta.url |
|
|
).toString() |
|
|
); |
|
|
|
|
|
|
|
|
const MAX_CHAR_VARNAME = 20; |
|
|
const LIST_THEMES = [ |
|
|
"ambiance", |
|
|
"chaos", |
|
|
"chrome", |
|
|
"cloud9_day", |
|
|
"cloud9_night", |
|
|
"cloud9_night_low_color", |
|
|
"clouds", |
|
|
"clouds_midnight", |
|
|
"cloud_editor", |
|
|
"cloud_editor_dark", |
|
|
"cobalt", |
|
|
"crimson_editor", |
|
|
"dawn", |
|
|
"dracula", |
|
|
"dreamweaver", |
|
|
"eclipse", |
|
|
"github", |
|
|
"github_dark", |
|
|
"github_light_default", |
|
|
"gob", |
|
|
"gruvbox", |
|
|
"gruvbox_dark_hard", |
|
|
"gruvbox_light_hard", |
|
|
"idle_fingers", |
|
|
"iplastic", |
|
|
"katzenmilch", |
|
|
"kr_theme", |
|
|
"kuroir", |
|
|
"merbivore", |
|
|
"merbivore_soft", |
|
|
"monokai", |
|
|
"mono_industrial", |
|
|
"nord_dark", |
|
|
"one_dark", |
|
|
"pastel_on_dark", |
|
|
"solarized_dark", |
|
|
"solarized_light", |
|
|
"sqlserver", |
|
|
"terminal", |
|
|
"textmate", |
|
|
"tomorrow", |
|
|
"tomorrow_night", |
|
|
"tomorrow_night_blue", |
|
|
"tomorrow_night_bright", |
|
|
"tomorrow_night_eighties", |
|
|
"twilight", |
|
|
"vibrant_ink", |
|
|
"xcode", |
|
|
]; |
|
|
|
|
|
const DEFAULT_TEMPLATES = { |
|
|
js: `// !!! Attention, do not insert unverified code !!! |
|
|
// ---- Example code ---- |
|
|
// Globals inputs variables: var1, var2, var3, user variables ... |
|
|
const runCode = () => { |
|
|
const date = new Date().toJSON().replace("T"," "); |
|
|
return date.slice(0,date.indexOf(".")) |
|
|
} |
|
|
result = runCode();`, |
|
|
py: `# !!! Attention, do not insert unverified code !!! |
|
|
# ---- Example code ---- |
|
|
# Globals inputs variables: var1, var2, var3, user variables ... |
|
|
from time import strftime |
|
|
def runCode(): |
|
|
nowDataTime = strftime("%Y-%m-%d %H:%M:%S") |
|
|
return f"Hello ComfyUI with us today {nowDataTime}!" |
|
|
result = runCode() |
|
|
`, |
|
|
}; |
|
|
|
|
|
function getPostition(ctx, w_width, y, n_height) { |
|
|
const MARGIN = 5; |
|
|
|
|
|
const rect = ctx.canvas.getBoundingClientRect(); |
|
|
const transform = new DOMMatrix() |
|
|
.scaleSelf(rect.width / ctx.canvas.width, rect.height / ctx.canvas.height) |
|
|
.multiplySelf(ctx.getTransform()) |
|
|
.translateSelf(MARGIN, MARGIN + y); |
|
|
|
|
|
return { |
|
|
transformOrigin: "0 0", |
|
|
transform: transform, |
|
|
left: `0px`, |
|
|
top: `0px`, |
|
|
position: "absolute", |
|
|
maxWidth: `${w_width - MARGIN * 2}px`, |
|
|
maxHeight: `${n_height - MARGIN * 2 - y - 15}px`, |
|
|
width: "100%", |
|
|
height: "90%", |
|
|
scrollbarColor: "var(--descrip-text) var(--bg-color)", |
|
|
scrollbarWidth: "thin", |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
function codeEditor(node, inputName, inputData) { |
|
|
const widget = { |
|
|
type: "pycode", |
|
|
name: inputName, |
|
|
value: |
|
|
inputData[1]?.default || |
|
|
`def my(a, b=1): |
|
|
return a * b<br> |
|
|
|
|
|
result = str(my(23, 9))`, |
|
|
size: [500, 350], |
|
|
draw(ctx, node, widget_width, y, widget_height) { |
|
|
Object.assign( |
|
|
this.codeElement.style, |
|
|
getPostition(ctx, widget_width, y, node.size[1]) |
|
|
); |
|
|
}, |
|
|
computeSize(...args) { |
|
|
return [500, 350]; |
|
|
}, |
|
|
}; |
|
|
|
|
|
widget.codeElement = makeElement("pre", { innerHTML: widget.value }); |
|
|
widget.editor = ace.edit(widget.codeElement); |
|
|
widget.editor.setTheme("ace/theme/monokai"); |
|
|
widget.editor.session.setMode("ace/mode/python"); |
|
|
|
|
|
document.body.appendChild(widget.codeElement); |
|
|
|
|
|
return widget; |
|
|
} |
|
|
|
|
|
|
|
|
app.registerExtension({ |
|
|
name: "Comfy.ExperimentalNodesAlekPet", |
|
|
getCustomWidgets(app) { |
|
|
return { |
|
|
PYCODE: (node, inputName, inputData, app) => { |
|
|
const widget = codeEditor(node, inputName, inputData); |
|
|
|
|
|
widget.editor.getSession().on("change", function (e) { |
|
|
widget.value = widget.editor.getValue(); |
|
|
}); |
|
|
|
|
|
const themeList = node.addWidget( |
|
|
"combo", |
|
|
"theme_highlight", |
|
|
"monokai", |
|
|
(v) => { |
|
|
widget.editor.setTheme(`ace/theme/${themeList.value}`); |
|
|
}, |
|
|
{ |
|
|
values: LIST_THEMES, |
|
|
serialize: false, |
|
|
} |
|
|
); |
|
|
|
|
|
const widgetLang_id = findWidget(node, "language", "name", "findIndex"); |
|
|
if (widgetLang_id !== -1) { |
|
|
node.widgets[widgetLang_id].callback = (v) => { |
|
|
widget.editor.setTheme(`ace/theme/${themeList.value}`); |
|
|
confirm("Clear code?") && widget.editor.setValue(""); |
|
|
if (confirm("Paste template?")) { |
|
|
let defaultCode = null; |
|
|
if (v === "javascript") { |
|
|
defaultCode = DEFAULT_TEMPLATES.js; |
|
|
} else if (v === "python") { |
|
|
defaultCode = DEFAULT_TEMPLATES.py; |
|
|
} |
|
|
|
|
|
if (defaultCode) { |
|
|
widget.editor.setValue(defaultCode); |
|
|
widget.editor.clearSelection(); |
|
|
} |
|
|
} |
|
|
|
|
|
widget.editor.session.setMode(`ace/mode/${v}`); |
|
|
}; |
|
|
} |
|
|
|
|
|
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); |
|
|
|
|
|
if ( |
|
|
!varName || |
|
|
varName.trim() === "" || |
|
|
varName.length > MAX_CHAR_VARNAME || |
|
|
!/^[a-z_][a-z0-9_]*$/i.test(varName) |
|
|
) { |
|
|
const windowError = 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>`, |
|
|
}), |
|
|
], |
|
|
...THEMES_MODAL_WINDOW.warning, |
|
|
options: { |
|
|
auto: { |
|
|
autohide: true, |
|
|
autoremove: true, |
|
|
autoshow: true, |
|
|
timewait: 2000, |
|
|
}, |
|
|
parent: widget.codeElement, |
|
|
}, |
|
|
}); |
|
|
|
|
|
return; |
|
|
} |
|
|
const currentWidth = node.size[0]; |
|
|
node.addInput(varName, "*"); |
|
|
node.setSize([currentWidth, node.size[1]]); |
|
|
}); |
|
|
|
|
|
node.addWidget("button", "Clear", "clear_code", () => { |
|
|
widget.editor.setValue(""); |
|
|
}); |
|
|
|
|
|
node.onRemoved = function () { |
|
|
for (const w of node?.widgets) { |
|
|
if (w?.codeElement) w.codeElement.remove(); |
|
|
} |
|
|
}; |
|
|
|
|
|
node.addCustomWidget(widget); |
|
|
|
|
|
return widget; |
|
|
}, |
|
|
}; |
|
|
}, |
|
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) { |
|
|
|
|
|
if (nodeData.name === "IDENode") { |
|
|
|
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated; |
|
|
nodeType.prototype.onNodeCreated = async function () { |
|
|
const ret = onNodeCreated |
|
|
? onNodeCreated.apply(this, arguments) |
|
|
: undefined; |
|
|
|
|
|
const node_title = await this.getTitle(); |
|
|
const nodeName = `${nodeData.name}_${this.id}`; |
|
|
|
|
|
console.log(`Create ${nodeData.name}: ${nodeName}`); |
|
|
this.name = nodeName; |
|
|
|
|
|
|
|
|
if (!this?.inputs) { |
|
|
["var1", "var2", "var3"].forEach((inputName) => { |
|
|
const currentWidth = this.size[0]; |
|
|
this.addInput(inputName, "*"); |
|
|
this.setSize([currentWidth, this.size[1]]); |
|
|
}); |
|
|
} |
|
|
|
|
|
const widgetEditor = findWidget(this, "pycode", "type"); |
|
|
|
|
|
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`; |
|
|
for (let [k, v] of Object.entries(edit_vars)) { |
|
|
|
|
|
if (v instanceof String || typeof v === "string") { |
|
|
v = `"${v}"`; |
|
|
} else if (v instanceof Object || typeof v === "object") { |
|
|
v = JSON.stringify(v); |
|
|
} |
|
|
code_run += `let ${k} = ${v};\n`; |
|
|
} |
|
|
|
|
|
code_run += `${widgetEditor.editor.getValue()}\nreturn result\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}`); |
|
|
} |
|
|
|
|
|
res(result_run_code); |
|
|
}).then((result_code) => { |
|
|
api |
|
|
.fetchApi("/alekpet/check_js_complete", { |
|
|
method: "POST", |
|
|
body: JSON.stringify({ |
|
|
unique_id: this.id.toString(), |
|
|
result_code: JSON.stringify(result_code), |
|
|
}), |
|
|
}) |
|
|
.then((res) => res.json()) |
|
|
.then((res) => |
|
|
res?.status === "Ok" |
|
|
? console.log( |
|
|
`%cJS complete ok: ${this.name}: ${res.status}`, |
|
|
"color: green; font-weight: 600;" |
|
|
) |
|
|
: console.error(`Error JS complete: ${res.status}`) |
|
|
) |
|
|
.catch((err) => console.error(`Error JS complete: ${err}`)); |
|
|
}); |
|
|
}); |
|
|
|
|
|
this.setSize([500, 350]); |
|
|
|
|
|
return ret; |
|
|
}; |
|
|
|
|
|
|
|
|
const onConfigure = nodeType.prototype.onConfigure; |
|
|
nodeType.prototype.onConfigure = function (node) { |
|
|
onConfigure?.apply(this, arguments); |
|
|
if (node?.widgets_values?.length) { |
|
|
const widget_code_id = findWidget( |
|
|
this, |
|
|
"pycode", |
|
|
"type", |
|
|
"findIndex" |
|
|
); |
|
|
const widget_theme_id = findWidget( |
|
|
this, |
|
|
"theme_highlight", |
|
|
"name", |
|
|
"findIndex" |
|
|
); |
|
|
const widget_language_id = findWidget( |
|
|
this, |
|
|
"language", |
|
|
"name", |
|
|
"findIndex" |
|
|
); |
|
|
|
|
|
const editor = this.widgets[widget_code_id]?.editor; |
|
|
|
|
|
if (editor) { |
|
|
editor.setTheme( |
|
|
`ace/theme/${this.widgets_values[widget_theme_id]}` |
|
|
); |
|
|
editor.session.setMode( |
|
|
`ace/mode/${this.widgets_values[widget_language_id]}` |
|
|
); |
|
|
editor.setValue(this.widgets_values[widget_code_id]); |
|
|
editor.clearSelection(); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; |
|
|
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]; |
|
|
|
|
|
|
|
|
if (!!past) { |
|
|
for (const input_idx in this.inputs) { |
|
|
const input = this.inputs[input_idx]; |
|
|
|
|
|
if (["language", "theme_highlight"].includes(input.name)) continue; |
|
|
|
|
|
options.splice(past_index + 1, 0, { |
|
|
content: `Remove Input ${input.name}`, |
|
|
callback: (e) => { |
|
|
const currentWidth = this.size[0]; |
|
|
if (input.link) { |
|
|
app.graph.removeLink(input.link); |
|
|
} |
|
|
this.removeInput(input_idx); |
|
|
this.setSize([currentWidth, this.size[1]]); |
|
|
}, |
|
|
}); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
} |
|
|
}, |
|
|
}); |
|
|
|