diff --git a/comfy_extras/eval_web/ace_utils.js b/comfy_extras/eval_web/ace_utils.js
deleted file mode 100644
index 78c00d809..000000000
--- a/comfy_extras/eval_web/ace_utils.js
+++ /dev/null
@@ -1,769 +0,0 @@
-/**
- * Uses code adapted from https://github.com/yorkane/ComfyUI-KYNode
- *
- * MIT License
- *
- * Copyright (c) 2024 Kevin Yuan
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-// Make modal window
-function makeModal({ title = "Message", text = "No text", type = "info", parent = null, stylePos = "fixed", classes = [] } = {}) {
- const overlay = document.createElement("div");
- Object.assign(overlay.style, {
- display: "none",
- position: stylePos,
- background: "rgba(0 0 0 / 0.8)",
- opacity: 0,
- top: "0",
- left: "0",
- right: "0",
- bottom: "0",
- zIndex: "500",
- transition: "all .8s",
- cursor: "pointer",
- });
-
- const boxModal = document.createElement("div");
- Object.assign(boxModal.style, {
- transition: "all 0.5s",
- opacity: 0,
- display: "none",
- position: stylePos,
- top: "50%",
- left: "50%",
- transform: "translate(-50%,-50%)",
- background: "#525252",
- minWidth: "300px",
- fontFamily: "sans-serif",
- zIndex: "501",
- border: "1px solid rgb(255 255 255 / 45%)",
- });
-
- boxModal.className = "alekpet_modal_window";
- boxModal.classList.add(...classes);
-
- const boxModalBody = document.createElement("div");
- Object.assign(boxModalBody.style, {
- display: "flex",
- flexDirection: "column",
- textAlign: "center",
- });
-
- boxModalBody.className = "alekpet_modal_body";
-
- const boxModalHtml = `
-
- ${text}
`;
- boxModalBody.innerHTML = boxModalHtml;
-
- const alekpet_modal_header = boxModalBody.querySelector(".alekpet_modal_header");
- Object.assign(alekpet_modal_header.style, {
- display: "flex",
- alignItems: "center",
- });
-
- const close = boxModalBody.querySelector(".alekpet_modal_close");
- Object.assign(close.style, {
- cursor: "pointer",
- });
-
- let parentElement = document.body;
- if (parent && parent.nodeType === 1) {
- parentElement = parent;
- }
-
- boxModal.append(boxModalBody);
- parentElement.append(overlay, boxModal);
-
- const removeEvent = new Event("removeElements");
- const remove = () => {
- animateTransitionProps(boxModal, { opacity: 0 }).then(() =>
- animateTransitionProps(overlay, { opacity: 0 }).then(() => {
- parentElement.removeChild(boxModal);
- parentElement.removeChild(overlay);
- }),
- );
- };
-
- boxModal.addEventListener("removeElements", remove);
- overlay.addEventListener("removeElements", remove);
-
- animateTransitionProps(overlay)
- .then(() => {
- overlay.addEventListener("click", () => {
- overlay.dispatchEvent(removeEvent);
- });
- animateTransitionProps(boxModal);
- })
- .then(() => boxModal.querySelector(".alekpet_modal_close").addEventListener("click", () => boxModal.dispatchEvent(removeEvent)));
-}
-
-function findWidget(node, value, attr = "name", func = "find") {
- return node?.widgets ? node.widgets[func]((w) => (Array.isArray(value) ? value.includes(w[attr]) : w[attr] === value)) : null;
-}
-
-function animateTransitionProps(el, props = { opacity: 1 }, preStyles = { display: "block" }) {
- Object.assign(el.style, preStyles);
-
- el.style.transition = !el.style.transition || !window.getComputedStyle(el).getPropertyValue("transition") ? "all .8s" : el.style.transition;
-
- return new Promise((res) => {
- setTimeout(() => {
- Object.assign(el.style, props);
-
- const transstart = () => (el.isAnimating = true);
- const transchancel = () => (el.isAnimating = false);
- el.addEventListener("transitionstart", transstart);
- el.addEventListener("transitioncancel", transchancel);
-
- el.addEventListener("transitionend", function transend() {
- el.isAnimating = false;
- el.removeEventListener("transitionend", transend);
- el.removeEventListener("transitionend", transchancel);
- el.removeEventListener("transitionend", transstart);
- res(el);
- });
- }, 100);
- });
-}
-
-function animateClick(target, params = {}) {
- const { opacityVal = 0.9, callback = () => {} } = params;
- if (target?.isAnimating) return;
-
- const hide = +target.style.opacity === 0;
- return animateTransitionProps(target, {
- opacity: hide ? opacityVal : 0,
- }).then((el) => {
- const isHide = hide || el.style.display === "none";
- showHide({ elements: [target], hide: !hide });
- callback();
- return isHide;
- });
-}
-
-function showHide({ elements = [], hide = null, displayProp = "block" } = {}) {
- Array.from(elements).forEach((el) => {
- if (hide !== null) {
- el.style.display = !hide ? displayProp : "none";
- } else {
- el.style.display = !el.style.display || el.style.display === "none" ? displayProp : "none";
- }
- });
-}
-
-function isEmptyObject(obj) {
- if (!obj) return true;
- return Object.keys(obj).length === 0 && obj.constructor === Object;
-}
-
-function makeElement(tag, attrs = {}) {
- if (!tag) tag = "div";
- const element = document.createElement(tag);
- Object.keys(attrs).forEach((key) => {
- const currValue = attrs[key];
- if (key === "class") {
- if (Array.isArray(currValue)) {
- element.classList.add(...currValue);
- } else if (currValue instanceof String || typeof currValue === "string") {
- element.className = currValue;
- }
- } else if (key === "dataset") {
- try {
- if (Array.isArray(currValue)) {
- currValue.forEach((datasetArr) => {
- const [prop, propval] = Object.entries(datasetArr)[0];
- element.dataset[prop] = propval;
- });
- } else {
- Object.entries(currValue).forEach((datasetArr) => {
- const [prop, propval] = datasetArr;
- element.dataset[prop] = propval;
- });
- }
- } catch (err) {
- console.log(err);
- }
- } else if (key === "style") {
- if (typeof currValue === "object" && !Array.isArray(currValue) && Object.keys(currValue).length) {
- Object.assign(element[key], currValue);
- } else if (typeof currValue === "object" && Array.isArray(currValue) && currValue.length) {
- element[key] = [...currValue];
- } else if (currValue instanceof String || typeof currValue === "string") {
- element[key] = currValue;
- }
- } else if (["for"].includes(key)) {
- element.setAttribute(key, currValue);
- } else if (key === "children") {
- element.append(...(currValue instanceof Array ? currValue : [currValue]));
- } else if (key === "parent") {
- currValue.append(element);
- } else {
- element[key] = currValue;
- }
- });
- return element;
-}
-
-function isValidStyle(opt, strColor) {
- let op = new Option().style;
- if (!op.hasOwnProperty(opt)) return { result: false, color: "", color_hex: "" };
-
- op[opt] = strColor;
-
- return {
- result: op[opt] !== "",
- color_rgb: op[opt],
- color_hex: rgbToHex(op[opt]),
- };
-}
-
-function rgbToHex(rgb) {
- const regEx = new RegExp(/\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);
- if (regEx.test(rgb)) {
- let [, r, g, b] = regEx.exec(rgb);
- r = parseInt(r).toString(16);
- g = parseInt(g).toString(16);
- b = parseInt(b).toString(16);
-
- r = r.length === 1 ? r + "0" : r;
- g = g.length === 1 ? g + "0" : g;
- b = b.length === 1 ? b + "0" : b;
-
- return `#${r}${g}${b}`;
- }
-}
-
-async function getDataJSON(url) {
- try {
- const response = await fetch(url);
- const jsonData = await response.json();
- return jsonData;
- } catch (err) {
- return new Error(err);
- }
-}
-
-function deepMerge(target, source) {
- if (source?.nodeType) return;
- for (let key in source) {
- if (source[key] instanceof Object && key in target) {
- Object.assign(source[key], deepMerge(target[key], source[key]));
- }
- }
-
- Object.assign(target || {}, source);
- return target;
-}
-
-const THEME_MODAL_WINDOW_BASE = {
- stylesTitle: {
- background: "auto",
- padding: "5px",
- borderRadius: "6px",
- marginBottom: "5px",
- alignSelf: "stretch",
- },
- stylesWrapper: {
- display: "none",
- opacity: 0,
- minWidth: "220px",
- position: "absolute",
- left: "50%",
- top: "50%",
- transform: "translate(-50%, -50%)",
- transition: "all .8s",
- fontFamily: "monospace",
- zIndex: 99999,
- },
- stylesBox: {
- display: "flex",
- flexDirection: "column",
- background: "#0e0e0e",
- padding: "6px",
- justifyContent: "center",
- alignItems: "center",
- gap: "3px",
- textAlign: "center",
- borderRadius: "6px",
- color: "white",
- border: "2px solid silver",
- boxShadow: "2px 2px 4px silver",
- maxWidth: "300px",
- },
- stylesClose: {
- position: "absolute",
- top: "-10px",
- right: "-10px",
- background: "silver",
- borderRadius: "50%",
- width: "20px",
- height: "20px",
- cursor: "pointer",
- display: "flex",
- justifyContent: "center",
- alignItems: "center",
- fontSize: "0.8rem",
- },
-};
-
-const THEMES_MODAL_WINDOW = {
- error: {
- stylesTitle: {
- ...THEME_MODAL_WINDOW_BASE.stylesTitle,
- background: "#8f210f",
- },
- stylesBox: {
- ...THEME_MODAL_WINDOW_BASE.stylesBox,
- background: "#3b2222",
- boxShadow: "3px 3px 6px #141414",
- border: "1px solid #f91b1b",
- },
- stylesWrapper: { ...THEME_MODAL_WINDOW_BASE.stylesWrapper },
- stylesClose: {
- ...THEME_MODAL_WINDOW_BASE.stylesClose,
- background: "#3b2222",
- },
- },
- warning: {
- stylesTitle: {
- ...THEME_MODAL_WINDOW_BASE.stylesTitle,
- background: "#e99818",
- },
- stylesBox: {
- ...THEME_MODAL_WINDOW_BASE.stylesBox,
- background: "#594e32",
- boxShadow: "3px 3px 6px #141414",
- border: "1px solid #e99818",
- },
- stylesWrapper: { ...THEME_MODAL_WINDOW_BASE.stylesWrapper },
- stylesClose: {
- ...THEME_MODAL_WINDOW_BASE.stylesClose,
- background: "#594e32",
- },
- },
- normal: {
- stylesTitle: {
- ...THEME_MODAL_WINDOW_BASE.stylesTitle,
- background: "#108f0f",
- },
- stylesBox: {
- ...THEME_MODAL_WINDOW_BASE.stylesBox,
- background: "#223b2a",
- boxShadow: "3px 3px 6px #141414",
- border: "1px solid #108f0f",
- },
- stylesWrapper: { ...THEME_MODAL_WINDOW_BASE.stylesWrapper },
- stylesClose: {
- ...THEME_MODAL_WINDOW_BASE.stylesClose,
- background: "#223b2a",
- },
- },
-};
-
-const defaultOptions = {
- auto: {
- autohide: false,
- autoshow: false,
- autoremove: false,
- propStyles: { opacity: 0 },
- propPreStyles: {},
- timewait: 2000,
- },
- overlay: {
- overlay_enabled: false,
- overlayClasses: [],
- overlayStyles: {},
- },
- close: { closeRemove: false, showClose: true },
- parent: null,
-};
-
-function createWindowModal({ textTitle = "Message", textBody = "Hello world!", textFooter = null, classesWrapper = [], stylesWrapper = {}, classesBox = [], stylesBox = {}, classesTitle = [], stylesTitle = {}, classesBody = [], stylesBody = {}, classesClose = [], stylesClose = {}, classesFooter = [], stylesFooter = {}, options = defaultOptions } = {}) {
- // Check all options exist
- const _options = deepMerge(JSON.parse(JSON.stringify(defaultOptions)), options);
-
- const {
- parent,
- overlay: { overlay_enabled, overlayClasses, overlayStyles },
- close: { closeRemove, showClose },
- auto: { autohide, autoshow, autoremove, timewait, propStyles, propPreStyles },
- } = _options;
-
- // Function past text(html)
- function addText(text, parent) {
- if (!parent) return;
-
- switch (typeof text) {
- case "string":
- if (/^\<.*\/?\>$/.test(text)) {
- parent.innerHTML = text;
- } else {
- parent.textContent = text;
- }
- break;
- case "object":
- default:
- if (Array.isArray(text)) {
- text.forEach((element) => (element.nodeType === 1 || element.nodeType === 3) && parent.append(element));
- } else if (text.nodeType === 1 || text.nodeType === 3) parent.append(text);
- }
- }
-
- // Overlay
- let overlayElement = null;
- if (overlay_enabled) {
- overlayElement = makeElement("div", {
- class: [...overlayClasses],
- style: {
- display: "none",
- position: "fixed",
- background: "rgba(0 0 0 / 0.8)",
- opacity: 0,
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- zIndex: 99999,
- transition: "all .8s",
- cursor: "pointer",
- ...overlayStyles,
- },
- });
- }
-
- // Wrapper
- const wrapper_settings = makeElement("div", {
- class: ["alekpet__wrapper__window", ...classesWrapper],
- });
-
- Object.assign(wrapper_settings.style, {
- ...THEME_MODAL_WINDOW_BASE.stylesWrapper,
- ...stylesWrapper,
- });
-
- // Box
- const box__settings = makeElement("div", {
- class: ["alekpet__window__box", ...classesBox],
- });
- Object.assign(box__settings.style, {
- ...THEME_MODAL_WINDOW_BASE.stylesBox,
- ...stylesBox,
- });
-
- // Title
- let box_settings_title = "";
- if (textTitle) {
- box_settings_title = makeElement("div", {
- class: ["alekpet__window__title", ...classesTitle],
- });
-
- Object.assign(box_settings_title.style, {
- ...THEME_MODAL_WINDOW_BASE.stylesTitle,
- ...stylesTitle,
- });
-
- // Add text (html) to title
- addText(textTitle, box_settings_title);
- }
- // Body
- let box_settings_body = "";
- if (textBody) {
- box_settings_body = makeElement("div", {
- class: ["alekpet__window__body", ...classesBody],
- });
-
- Object.assign(box_settings_body.style, {
- display: "flex",
- flexDirection: "column",
- alignItems: "flex-end",
- gap: "5px",
- textWrap: "wrap",
- ...stylesBody,
- });
-
- // Add text (html) to body
- addText(textBody, box_settings_body);
- }
-
- // Close button
- const close__box__button = makeElement("div", {
- class: ["close__box__button", ...classesClose],
- textContent: "✖",
- });
-
- Object.assign(close__box__button.style, {
- ...THEME_MODAL_WINDOW_BASE.stylesClose,
- ...stylesClose,
- });
-
- if (!showClose) close__box__button.style.display = "none";
-
- const closeEvent = new Event("closeModal");
- const closeModalWindow = function () {
- overlay_enabled
- ? animateTransitionProps(overlayElement, {
- opacity: 0,
- })
- .then(() =>
- animateTransitionProps(wrapper_settings, {
- opacity: 0,
- }),
- )
- .then(() => {
- if (closeRemove) {
- parent.removeChild(wrapper_settings);
- parent.removeChild(overlayElement);
- } else {
- showHide({ elements: [wrapper_settings, overlayElement] });
- }
- })
- : animateTransitionProps(wrapper_settings, {
- opacity: 0,
- }).then(() => {
- showHide({ elements: [wrapper_settings] });
- });
- };
-
- close__box__button.addEventListener("closeModal", closeModalWindow);
-
- close__box__button.addEventListener("click", () => close__box__button.dispatchEvent(closeEvent));
-
- close__box__button.onmouseenter = () => {
- close__box__button.style.opacity = 0.8;
- };
-
- close__box__button.onmouseleave = () => {
- close__box__button.style.opacity = 1;
- };
-
- box__settings.append(box_settings_title, box_settings_body);
-
- // Footer
- if (textFooter) {
- const box_settings_footer = makeElement("div", {
- class: [...classesFooter],
- });
- Object.assign(box_settings_footer.style, {
- ...stylesFooter,
- });
-
- // Add text (html) to body
- addText(textFooter, box_settings_footer);
-
- box__settings.append(box_settings_footer);
- }
-
- wrapper_settings.append(close__box__button, box__settings);
-
- if (parent && parent.nodeType === 1) {
- if (overlay_enabled) parent.append(overlayElement);
- parent.append(wrapper_settings);
-
- if (autoshow) {
- overlay_enabled
- ? animateClick(overlayElement).then(() =>
- animateClick(wrapper_settings).then(
- () =>
- autohide &&
- setTimeout(
- () =>
- animateTransitionProps(wrapper_settings, { ...propStyles }, { ...propPreStyles })
- .then(() => animateTransitionProps(overlayElement, { ...propStyles }, { ...propPreStyles }))
- .then(() => {
- if (autoremove) {
- parent.removeChild(wrapper_settings);
- parent.removeChild(overlayElement);
- }
- }),
- timewait,
- ),
- ),
- )
- : animateClick(wrapper_settings).then(() => autohide && setTimeout(() => animateTransitionProps(wrapper_settings, { ...propStyles }, { ...propPreStyles }).then(() => autoremove && parent.removeChild(wrapper_settings)), timewait));
- }
- }
-
- return wrapper_settings;
-}
-
-// Prompt
-async function comfyuiDesktopPrompt(title, message, defaultValue) {
- try {
- return await app.extensionManager.dialog.prompt({
- title,
- message,
- defaultValue,
- });
- } catch (err) {
- return prompt(title, message);
- }
-}
-
-// Alert
-function comfyuiDesktopAlert(message) {
- try {
- app.extensionManager.toast.addAlert(message);
- } catch (err) {
- alert(message);
- }
-}
-
-// Confirm
-function confirmModal({ title, message }) {
- return new Promise((res) => {
- const overlay = makeElement("div", {
- class: ["alekpet_confOverlay"],
- style: {
- background: "rgba(0, 0, 0, 0.7)",
- position: "fixed",
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- zIndex: 9999,
- userSelect: "none",
- },
- });
-
- const modal = makeElement("div", {
- class: ["alekpet_confModal"],
- style: {
- ...THEME_MODAL_WINDOW_BASE.stylesBox,
- position: "fixed",
- top: "50%",
- left: "50%",
- fontFamily: "monospace",
- background: "rgb(92 186 255 / 20%)",
- transform: "translate(-50%, -50%)",
- borderColor: "rgba(92, 186, 255, 0.63)",
- boxShadow: "rgba(92, 186, 255, 0.63) 2px 2px 4px",
- },
- });
-
- const titleEl = makeElement("div", {
- class: ["alekpet_confTitle"],
- style: {
- ...THEME_MODAL_WINDOW_BASE.stylesTitle,
- background: "rgba(92, 186, 255, 0.63)",
- },
- textContent: title,
- });
-
- const messageEl = makeElement("div", {
- class: ["alekpet_confMessage"],
- style: {
- display: "flex",
- flexDirection: "column",
- alignItems: "flex-end",
- gap: "5px",
- textWrap: "wrap",
- },
- textContent: message,
- });
-
- const action_box = makeElement("div", {
- class: ["alekpet_confActions"],
- style: {
- display: "flex",
- gap: "5px",
- width: "100%",
- padding: "4px",
- justifyContent: "flex-end",
- },
- });
-
- const remove = () => {
- modal.remove();
- overlay.remove();
- };
-
- const ok = makeElement("div", {
- class: ["alekpet_confButtons", "alekpet_confButtonOk"],
- style: {
- background: "linear-gradient(45deg, green, limegreen) rgb(21, 100, 6)",
- },
- textContent: "Ok",
- onclick: (e) => {
- res(true);
- remove();
- },
- });
-
- const Cancel = makeElement("div", {
- class: ["alekpet_confButtons", "alekpet_confButtonCancel"],
- style: {
- background: "linear-gradient(45deg, #b64396, #a52a8b) rgb(135 3 161)",
- },
- textContent: "Cancel",
- onclick: (e) => {
- res(false);
- remove();
- },
- });
-
- action_box.append(ok, Cancel);
- modal.append(titleEl, messageEl, action_box);
- overlay.append(modal);
- document.body.append(overlay);
- });
-}
-
-async function comfyuiDesktopConfirm(message) {
- try {
- const result = await confirmModal({
- title: "Confirm",
- message: message,
- });
-
- // Wait update comfyui frontend! Confirm Cancel not return value! Fixed in ComfyUI_frontend ver. v1.10.8
- // https://github.com/Comfy-Org/ComfyUI_frontend/issues/2649
- // const result = await app.extensionManager.dialog.confirm({
- // title: "Confirm",
- // message: message,
- // });
- return result;
- } catch (err) {
- return confirm(message);
- }
-}
-
-export {
- makeModal,
- createWindowModal,
- animateTransitionProps,
- animateClick,
- showHide,
- makeElement,
- getDataJSON,
- isEmptyObject,
- isValidStyle,
- rgbToHex,
- findWidget,
- THEMES_MODAL_WINDOW,
- //
- comfyuiDesktopConfirm,
- comfyuiDesktopPrompt,
- comfyuiDesktopAlert,
-};
diff --git a/comfy_extras/eval_web/eval_python.js b/comfy_extras/eval_web/eval_python.js
index d8021a7b2..a7e2fa19f 100644
--- a/comfy_extras/eval_web/eval_python.js
+++ b/comfy_extras/eval_web/eval_python.js
@@ -24,7 +24,6 @@
* SOFTWARE.
*/
import { app } from "../../scripts/app.js";
-import { makeElement, findWidget } from "./ace_utils.js";
// Load Ace editor using script tag for Safari compatibility
// The noconflict build includes AMD loader that works in all browsers
@@ -34,7 +33,7 @@ const aceLoadPromise = new Promise((resolve) => {
ace = window.ace;
resolve();
} else {
- const script = document.createElement('script');
+ const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/ace-builds@1.43.4/src-noconflict/ace.js";
script.onload = () => {
ace = window.ace;
@@ -45,44 +44,108 @@ const aceLoadPromise = new Promise((resolve) => {
}
});
+// todo: do we really want to do this here?
await aceLoadPromise;
+const findWidget = (node, value, attr = "name", func = "find") => {
+ return node?.widgets ? node.widgets[func]((w) => (Array.isArray(value) ? value.includes(w[attr]) : w[attr] === value)) : null;
+};
+const makeElement = (tag, attrs = {}) => {
+ if (!tag) tag = "div";
+ const element = document.createElement(tag);
+ Object.keys(attrs).forEach((key) => {
+ const currValue = attrs[key];
+ if (key === "class") {
+ if (Array.isArray(currValue)) {
+ element.classList.add(...currValue);
+ } else if (currValue instanceof String || typeof currValue === "string") {
+ element.className = currValue;
+ }
+ } else if (key === "dataset") {
+ try {
+ if (Array.isArray(currValue)) {
+ currValue.forEach((datasetArr) => {
+ const [prop, propval] = Object.entries(datasetArr)[0];
+ element.dataset[prop] = propval;
+ });
+ } else {
+ Object.entries(currValue).forEach((datasetArr) => {
+ const [prop, propval] = datasetArr;
+ element.dataset[prop] = propval;
+ });
+ }
+ } catch (err) {
+ // todo: what is this trying to do?
+ }
+ } else if (key === "style") {
+ if (typeof currValue === "object" && !Array.isArray(currValue) && Object.keys(currValue).length) {
+ Object.assign(element[key], currValue);
+ } else if (typeof currValue === "object" && Array.isArray(currValue) && currValue.length) {
+ element[key] = [...currValue];
+ } else if (currValue instanceof String || typeof currValue === "string") {
+ element[key] = currValue;
+ }
+ } else if (["for"].includes(key)) {
+ element.setAttribute(key, currValue);
+ } else if (key === "children") {
+ element.append(...(currValue instanceof Array ? currValue : [currValue]));
+ } else if (key === "parent") {
+ currValue.append(element);
+ } else {
+ element[key] = currValue;
+ }
+ });
+ return element;
+};
-function getPosition(node, ctx, w_width, y, n_height) {
+const getPosition = (node, 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);
- const scale = new DOMMatrix().scaleSelf(transform.a, transform.d);
+ const transform = ctx.getTransform();
+ const scale = app.canvas.ds.scale;
+
+ // The context is already transformed to draw at the widget position
+ // transform.e and transform.f give us the canvas coordinates (in canvas pixels)
+ // We need to convert these to screen pixels by accounting for the canvas scale
+ // rect gives us the canvas element's position on the page
+
+ // The transform matrix has scale baked in (transform.a = transform.d = scale)
+ // transform.e and transform.f are the translation in canvas-pixel space
+ const canvasPixelToScreenPixel = rect.width / ctx.canvas.width;
+
+ const x = transform.e * canvasPixelToScreenPixel + rect.left;
+ const y_pos = transform.f * canvasPixelToScreenPixel + rect.top;
+
+ // Convert widget dimensions from canvas coordinates to screen pixels
+ const scaledWidth = w_width * scale;
+ const scaledHeight = (n_height - y - 15) * scale;
+ const scaledMargin = margin * scale;
+ const scaledY = y * scale;
return {
- transformOrigin: "0 0",
- transform: scale,
- left: `${transform.a + transform.e + rect.left}px`,
- top: `${transform.d + transform.f + rect.top}px`,
- maxWidth: `${w_width - margin * 2}px`,
- maxHeight: `${n_height - margin * 2 - y - 15}px`,
- width: `${w_width - margin * 2}px`,
- height: "90%",
+ left: `${x + scaledMargin}px`,
+ top: `${y_pos + scaledY + scaledMargin}px`,
+ width: `${scaledWidth - scaledMargin * 2}px`,
+ maxWidth: `${scaledWidth - scaledMargin * 2}px`,
+ height: `${scaledHeight - scaledMargin * 2}px`,
+ maxHeight: `${scaledHeight - scaledMargin * 2}px`,
position: "absolute",
scrollbarColor: "var(--descrip-text) var(--bg-color)",
scrollbarWidth: "thin",
zIndex: app.graph._nodes.indexOf(node),
};
-}
+};
// Create code editor widget
-function codeEditor(node, inputName, inputData) {
+const codeEditor = (node, inputName, inputData) => {
const widget = {
- type: "pycode",
+ type: "code_block_python",
name: inputName,
options: { hideOnZoom: true },
value: inputData[1]?.default || "",
draw(ctx, node, widgetWidth, y) {
- const hidden = node.flags?.collapsed || (!!this.options.hideOnZoom && app.canvas.ds.scale < 0.5) || this.type === "converted-widget" || this.type === "hidden";
+ const hidden = node.flags?.collapsed || (!!this.options.hideOnZoom && app.canvas.ds.scale < 0.5) || this.type === "converted-widget" || this.type === "hidden" || this.type === "converted-widget";
this.codeElement.hidden = hidden;
@@ -122,19 +185,19 @@ function codeEditor(node, inputName, inputData) {
};
return widget;
-}
+};
// Trigger workflow change tracking
-function markWorkflowChanged() {
+const markWorkflowChanged = () => {
app?.extensionManager?.workflow?.activeWorkflow?.changeTracker?.checkState();
-}
+};
// Register extensions
app.registerExtension({
name: "Comfy.EvalPython",
getCustomWidgets(app) {
return {
- PYCODE: (node, inputName, inputData) => {
+ CODE_BLOCK_PYTHON: (node, inputName, inputData) => {
const widget = codeEditor(node, inputName, inputData);
widget.editor.getSession().on("change", () => {
@@ -165,7 +228,7 @@ app.registerExtension({
originalOnConfigure?.apply(this, arguments);
if (info?.widgets_values?.length) {
- const widgetCodeIndex = findWidget(this, "pycode", "type", "findIndex");
+ const widgetCodeIndex = findWidget(this, "code_block_python", "type", "findIndex");
const editor = this.widgets[widgetCodeIndex]?.editor;
if (editor) {
diff --git a/comfy_extras/nodes/nodes_eval.py b/comfy_extras/nodes/nodes_eval.py
index ff04522eb..bda41a02d 100644
--- a/comfy_extras/nodes/nodes_eval.py
+++ b/comfy_extras/nodes/nodes_eval.py
@@ -36,7 +36,7 @@ return {", ".join([f"value{i}" for i in range(inputs)])}
return {
"required": {
"pycode": (
- "PYCODE",
+ "CODE_BLOCK_PYTHON",
{
"default": default_code
},
@@ -47,16 +47,19 @@ return {", ".join([f"value{i}" for i in range(inputs)])}
RETURN_TYPES = tuple(IO.ANY for _ in range(outputs))
RETURN_NAMES = tuple(f"item{i}" for i in range(outputs))
- OUTPUT_IS_LIST = output_is_list
- INPUT_IS_LIST = input_is_list is not None
FUNCTION = "exec_py"
DESCRIPTION = ""
CATEGORY = "eval"
+ @classmethod
+ def VALIDATE_INPUTS(cls, *args, **kwargs):
+ ctx = current_execution_context()
+
+ return ctx.configuration.enable_eval
+
def exec_py(self, pycode, **kwargs):
ctx = current_execution_context()
- # Ensure all value inputs have a default of None
kwargs = {
**{f"value{i}": None for i in range(inputs)},
**kwargs,
@@ -68,11 +71,9 @@ return {", ".join([f"value{i}" for i in range(inputs)])}
if not ctx.configuration.enable_eval:
raise ValueError("Python eval is disabled")
- # Extract value arguments in order
value_args = [kwargs.pop(f"value{i}") for i in range(inputs)]
arg_names = ", ".join(f"value{i}=None" for i in range(inputs))
- # Wrap pycode in a function to support return statements
wrapped_code = f"def _eval_func({arg_names}):\n"
for line in pycode.splitlines():
wrapped_code += " " + line + "\n"
@@ -83,13 +84,8 @@ return {", ".join([f"value{i}" for i in range(inputs)])}
"print": print,
}
- # Execute wrapped function definition
exec(wrapped_code, globals_for_eval)
-
- # Call the function with value arguments
results = globals_for_eval["_eval_func"](*value_args)
-
- # Normalize results to match output count
if not isinstance(results, tuple):
results = (results,)
@@ -100,22 +96,24 @@ return {", ".join([f"value{i}" for i in range(inputs)])}
return results
- # Set the class name for better debugging/introspection
+ # todo: interact better with the weird comfyui machinery for this
+ if input_is_list is not None:
+ setattr(EvalPythonNode, "INPUT_IS_LIST", input_is_list)
+
+ if output_is_list is not None:
+ setattr(EvalPythonNode, "OUTPUT_IS_LIST", output_is_list)
+
EvalPythonNode.__name__ = name
EvalPythonNode.__qualname__ = name
return EvalPythonNode
-# Create the default EvalPython node with 5 inputs and 5 outputs
+EvalPython_1_1 = eval_python(inputs=1, outputs=1, name="EvalPython_1_1")
EvalPython_5_5 = eval_python(inputs=5, outputs=5, name="EvalPython_5_5")
-EvalPython = EvalPython_5_5 # Backward compatibility alias
-
-# Create list variants
EvalPython_List_1 = eval_python(inputs=1, outputs=1, name="EvalPython_List_1", input_is_list=True, output_is_list=None)
EvalPython_1_List = eval_python(inputs=1, outputs=1, name="EvalPython_1_List", input_is_list=None, output_is_list=(True,))
EvalPython_List_List = eval_python(inputs=1, outputs=1, name="EvalPython_List_List", input_is_list=True, output_is_list=(True,))
-
export_custom_nodes()
export_package_as_web_directory("comfy_extras.eval_web")
diff --git a/tests/unit/test_eval_nodes.py b/tests/unit/test_eval_nodes.py
index f2cb0c763..71076daef 100644
--- a/tests/unit/test_eval_nodes.py
+++ b/tests/unit/test_eval_nodes.py
@@ -4,9 +4,8 @@ from unittest.mock import Mock, patch
from comfy.cli_args import default_configuration
from comfy.execution_context import context_configuration
from comfy_extras.nodes.nodes_eval import (
- EvalPython,
- EvalPython_5_5,
eval_python,
+ EvalPython_5_5,
EvalPython_List_1,
EvalPython_1_List,
EvalPython_List_List,
@@ -447,7 +446,7 @@ def test_eval_python_input_types():
assert "required" in input_types
assert "optional" in input_types
assert "pycode" in input_types["required"]
- assert input_types["required"]["pycode"][0] == "PYCODE"
+ assert input_types["required"]["pycode"][0] == "CODE_BLOCK_PYTHON"
# Check optional inputs
for i in range(5):
@@ -560,7 +559,6 @@ def test_eval_python_list_1_input_is_list(eval_context):
# Verify INPUT_IS_LIST is set
assert EvalPython_List_1.INPUT_IS_LIST is True
- assert EvalPython_List_1.OUTPUT_IS_LIST is None
# Test that value0 receives a list
result = node.exec_py(
@@ -586,7 +584,6 @@ def test_eval_python_1_list_output_is_list(eval_context):
node = EvalPython_1_List()
# Verify OUTPUT_IS_LIST is set
- assert EvalPython_1_List.INPUT_IS_LIST is False
assert EvalPython_1_List.OUTPUT_IS_LIST == (True,)
# Test that returns a list
@@ -652,7 +649,6 @@ def test_eval_python_factory_with_list_flags(eval_context):
ListInputNode = eval_python(inputs=1, outputs=1, input_is_list=True, output_is_list=None)
assert ListInputNode.INPUT_IS_LIST is True
- assert ListInputNode.OUTPUT_IS_LIST is None
node = ListInputNode()
result = node.exec_py(
@@ -666,7 +662,6 @@ def test_eval_python_factory_scalar_output_list(eval_context):
"""Test factory function with scalar input and list output"""
ScalarToListNode = eval_python(inputs=1, outputs=1, input_is_list=None, output_is_list=(True,))
- assert ScalarToListNode.INPUT_IS_LIST is False
assert ScalarToListNode.OUTPUT_IS_LIST == (True,)
node = ScalarToListNode()
@@ -686,8 +681,3 @@ def test_eval_python_list_empty_list(eval_context):
value0=[]
)
assert result == ([],)
-
-
-def test_eval_python_backward_compatibility():
- """Test that EvalPython is an alias for EvalPython_5_5"""
- assert EvalPython is EvalPython_5_5