var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { q as app, cI as ComfyApp, cJ as ComfyDialog, cK as $el, aj as LiteGraph, an as LGraphCanvas, cL as isComboWidget, ab as useChainCallback, aH as useExtensionService, cM as processDynamicPrompt, cN as isValidUrl, bm as electronAPI, a5 as isElectron, v as useWorkflowStore, f as useToastStore, $ as useExternalLink, t, o as useDialogService, cO as ExecutableNodeDTO, cP as ComfyDialog$1, cQ as PREFIX, cR as SEPARATOR, aZ as DraggableList, cS as getInputSpecType, cT as isIntInputSpec, cU as isFloatInputSpec, cV as isComboInputSpec, cW as lcm, cX as getComboSpecComboOptions, cY as CONFIG, cZ as GET_CONFIG, c_ as ComfyWidgets, ax as LGraphNode, c$ as applyTextReplacements, d0 as isValidWidgetType, d1 as addValueControlWidgets, d2 as NodeSlot, d3 as GROUP, A as useExecutionStore, a as api, C as useNodeDefStore, d4 as useWidgetStore, d5 as deserialiseAndCreate, d6 as serialise, at as SubgraphNode, ay as LGraphGroup, e as useSettingStore, d7 as Load3dUtils, cH as useLoad3d, d8 as nodeToLoad3dMap, cG as useLoad3dService, d9 as ComponentWidgetImpl, da as addWidget, db as isLoad3dNode, u as useDialogStore, cF as Load3DViewerContent, dc as useMaskEditorStore, dd as useMaskEditor, R as downloadBlob, de as createBounds, c5 as getNodeByLocatorId, df as useNodeFileInput, dg as useNodeDragAndDrop, dh as useNodePaste, di as isComboInputSpecV1 } from "./index-BqxAUePh.js";
import { bs as shallowReactive, cw as log, cZ as toolkit, n as nextTick, ey as mediaRecorderConstructor } from "./vendor-other-DdEHkzYa.js";
import { _ as _sfc_main } from "./Load3D.vue_vue_type_script_setup_true_lang-C4Pgqhqg.js";
import { g as getResourceURL, s as splitFilePath } from "./audioUtils-BurLDP37.js";
import { u as useAudioService } from "./audioService-BYJv4FKM.js";
import "./vendor-primevue-DmDWdkDd.js";
import "./vendor-vue-BIRc7P93.js";
import "./vendor-xterm-BF8peZ5_.js";
import "./vendor-three-_gcZkhit.js";
import "./vendor-tiptap-B76LFN34.js";
class ClipspaceDialog extends ComfyDialog {
  static {
    __name(this, "ClipspaceDialog");
  }
  static items = [];
  static instance = null;
  static registerButton(name, contextPredicate, callback) {
    const item = $el("button", {
      type: "button",
      textContent: name,
      contextPredicate,
      onclick: callback
    });
    ClipspaceDialog.items.push(item);
  }
  static invalidatePreview() {
    if (ComfyApp.clipspace && ComfyApp.clipspace.imgs && ComfyApp.clipspace.imgs.length > 0) {
      const img_preview = document.getElementById(
        "clipspace_preview"
      );
      if (img_preview) {
        img_preview.src = ComfyApp.clipspace.imgs[ComfyApp.clipspace["selectedIndex"]].src;
        img_preview.style.maxHeight = "100%";
        img_preview.style.maxWidth = "100%";
      }
    }
  }
  static invalidate() {
    if (ClipspaceDialog.instance) {
      const self = ClipspaceDialog.instance;
      const children = $el("div.comfy-modal-content", [
        self.createImgSettings(),
        ...self.createButtons()
      ]);
      if (self.element) {
        if (self.element.firstChild) {
          self.element.removeChild(self.element.firstChild);
        }
        self.element.appendChild(children);
      } else {
        self.element = $el("div.comfy-modal", { parent: document.body }, [
          children
        ]);
      }
      if (self.element.children[0].children.length <= 1) {
        self.element.children[0].appendChild(
          $el("p", {}, [
            "Unable to find the features to edit content of a format stored in the current Clipspace."
          ])
        );
      }
      ClipspaceDialog.invalidatePreview();
    }
  }
  constructor() {
    super();
  }
  createButtons() {
    const buttons = [];
    for (let idx in ClipspaceDialog.items) {
      const item = ClipspaceDialog.items[idx];
      if (!item.contextPredicate || item.contextPredicate())
        buttons.push(ClipspaceDialog.items[idx]);
    }
    buttons.push(
      $el("button", {
        type: "button",
        textContent: "Close",
        onclick: /* @__PURE__ */ __name(() => {
          this.close();
        }, "onclick")
      })
    );
    return buttons;
  }
  createImgSettings() {
    if (ComfyApp.clipspace?.imgs) {
      const combo_items = [];
      const imgs = ComfyApp.clipspace.imgs;
      for (let i = 0; i < imgs.length; i++) {
        combo_items.push($el("option", { value: i }, [`${i}`]));
      }
      const combo1 = $el(
        "select",
        {
          id: "clipspace_img_selector",
          onchange: /* @__PURE__ */ __name((event) => {
            if (event.target && ComfyApp.clipspace) {
              ComfyApp.clipspace["selectedIndex"] = event.target.selectedIndex;
              ClipspaceDialog.invalidatePreview();
            }
          }, "onchange")
        },
        combo_items
      );
      const row1 = $el("tr", {}, [
        $el("td", {}, [$el("font", { color: "white" }, ["Select Image"])]),
        $el("td", {}, [combo1])
      ]);
      const combo2 = $el(
        "select",
        {
          id: "clipspace_img_paste_mode",
          onchange: /* @__PURE__ */ __name((event) => {
            if (event.target && ComfyApp.clipspace) {
              ComfyApp.clipspace["img_paste_mode"] = event.target.value;
            }
          }, "onchange")
        },
        [
          $el("option", { value: "selected" }, "selected"),
          $el("option", { value: "all" }, "all")
        ]
      );
      combo2.value = ComfyApp.clipspace["img_paste_mode"];
      const row2 = $el("tr", {}, [
        $el("td", {}, [$el("font", { color: "white" }, ["Paste Mode"])]),
        $el("td", {}, [combo2])
      ]);
      const td = $el(
        "td",
        { align: "center", width: "100px", height: "100px", colSpan: "2" },
        [$el("img", { id: "clipspace_preview", ondragstart: /* @__PURE__ */ __name(() => false, "ondragstart") }, [])]
      );
      const row3 = $el("tr", {}, [td]);
      return $el("table", {}, [row1, row2, row3]);
    } else {
      return [];
    }
  }
  createImgPreview() {
    if (ComfyApp.clipspace?.imgs) {
      return $el("img", { id: "clipspace_preview", ondragstart: /* @__PURE__ */ __name(() => false, "ondragstart") });
    } else return [];
  }
  show() {
    ClipspaceDialog.invalidate();
    this.element.style.display = "block";
  }
}
app.registerExtension({
  name: "Comfy.Clipspace",
  init(app2) {
    app2.openClipspace = function() {
      if (!ClipspaceDialog.instance) {
        ClipspaceDialog.instance = new ClipspaceDialog();
        ComfyApp.clipspace_invalidate_handler = ClipspaceDialog.invalidate;
      }
      if (ComfyApp.clipspace) {
        ClipspaceDialog.instance.show();
      } else app2.ui.dialog.show("Clipspace is Empty!");
    };
  }
});
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.clipspace = window.comfyAPI.clipspace || {};
window.comfyAPI.clipspace.ClipspaceDialog = ClipspaceDialog;
const ext$4 = {
  name: "Comfy.ContextMenuFilter",
  init() {
    const ctxMenu = LiteGraph.ContextMenu;
    LiteGraph.ContextMenu = function(values, options) {
      const ctx = new ctxMenu(values, options);
      if (options?.className === "dark" && values?.length > 4) {
        const filter = document.createElement("input");
        filter.classList.add("comfy-context-menu-filter");
        filter.placeholder = "Filter list";
        ctx.root.prepend(filter);
        const items = Array.from(
          ctx.root.querySelectorAll(".litemenu-entry")
        );
        let displayedItems = [...items];
        let itemCount = displayedItems.length;
        requestAnimationFrame(() => {
          const currentNode = LGraphCanvas.active_canvas.current_node;
          const clickedComboValue = currentNode?.widgets?.filter(
            (w) => isComboWidget(w) && w.options.values?.length === values.length
          ).find(
            (w) => (
              // @ts-expect-error Poorly typed; filter above "should" mitigate exceptions
              w.options.values?.every((v, i) => v === values[i])
            )
          )?.value;
          let selectedIndex = clickedComboValue ? values.findIndex((v) => v === clickedComboValue) : 0;
          if (selectedIndex < 0) {
            selectedIndex = 0;
          }
          let selectedItem = displayedItems[selectedIndex];
          updateSelected();
          function updateSelected() {
            selectedItem?.style.setProperty("background-color", "");
            selectedItem?.style.setProperty("color", "");
            selectedItem = displayedItems[selectedIndex];
            selectedItem?.style.setProperty(
              "background-color",
              "#ccc",
              "important"
            );
            selectedItem?.style.setProperty("color", "#000", "important");
          }
          __name(updateSelected, "updateSelected");
          const positionList = /* @__PURE__ */ __name(() => {
            const rect = ctx.root.getBoundingClientRect();
            if (rect.top < 0) {
              const scale = 1 - ctx.root.getBoundingClientRect().height / ctx.root.clientHeight;
              const shift = ctx.root.clientHeight * scale / 2;
              ctx.root.style.top = -shift + "px";
            }
          }, "positionList");
          filter.addEventListener("keydown", (event) => {
            switch (event.key) {
              case "ArrowUp":
                event.preventDefault();
                if (selectedIndex === 0) {
                  selectedIndex = itemCount - 1;
                } else {
                  selectedIndex--;
                }
                updateSelected();
                break;
              case "ArrowRight":
                event.preventDefault();
                selectedIndex = itemCount - 1;
                updateSelected();
                break;
              case "ArrowDown":
                event.preventDefault();
                if (selectedIndex === itemCount - 1) {
                  selectedIndex = 0;
                } else {
                  selectedIndex++;
                }
                updateSelected();
                break;
              case "ArrowLeft":
                event.preventDefault();
                selectedIndex = 0;
                updateSelected();
                break;
              case "Enter":
                selectedItem?.click();
                break;
              case "Escape":
                ctx.close();
                break;
            }
          });
          filter.addEventListener("input", () => {
            const term = filter.value.toLocaleLowerCase();
            displayedItems = items.filter((item) => {
              const isVisible = !term || item.textContent?.toLocaleLowerCase().includes(term);
              item.style.display = isVisible ? "block" : "none";
              return isVisible;
            });
            selectedIndex = 0;
            if (displayedItems.includes(selectedItem)) {
              selectedIndex = displayedItems.findIndex(
                (d) => d === selectedItem
              );
            }
            itemCount = displayedItems.length;
            updateSelected();
            if (options.event) {
              let top = options.event.clientY - 10;
              const bodyRect = document.body.getBoundingClientRect();
              const rootRect = ctx.root.getBoundingClientRect();
              if (bodyRect.height && top > bodyRect.height - rootRect.height - 10) {
                top = Math.max(0, bodyRect.height - rootRect.height - 10);
              }
              ctx.root.style.top = top + "px";
              positionList();
            }
          });
          requestAnimationFrame(() => {
            filter.focus();
            positionList();
          });
        });
      }
      return ctx;
    };
    LiteGraph.ContextMenu.prototype = ctxMenu.prototype;
  }
};
app.registerExtension(ext$4);
function applyToGraph(extraLinks = []) {
  if (!this.outputs[0].links?.length || !this.graph) return;
  const links = [
    ...this.outputs[0].links.map((l) => this.graph.links[l]),
    ...extraLinks
  ];
  let v = this.widgets?.[0].value;
  for (const linkInfo of links) {
    const node = this.graph?.getNodeById(linkInfo.target_id);
    const input = node?.inputs[linkInfo.target_slot];
    if (!input) {
      console.warn("Unable to resolve node or input for link", linkInfo);
      continue;
    }
    const widgetName = input.widget?.name;
    if (!widgetName) {
      console.warn("Invalid widget or widget name", input.widget);
      continue;
    }
    const widget = node.widgets?.find((w) => w.name === widgetName);
    if (!widget) {
      console.warn(`Unable to find widget "${widgetName}" on node [${node.id}]`);
      continue;
    }
    widget.value = v;
    widget.callback?.(
      widget.value,
      app.canvas,
      node,
      app.canvas.graph_mouse,
      {}
    );
  }
}
__name(applyToGraph, "applyToGraph");
function onNodeCreated() {
  this.applyToGraph = useChainCallback(this.applyToGraph, applyToGraph);
  const comboWidget = this.widgets[0];
  const values = shallowReactive([]);
  comboWidget.options.values = values;
  const updateCombo = /* @__PURE__ */ __name(() => {
    values.splice(
      0,
      values.length,
      ...this.widgets.filter(
        (w) => w.name.startsWith("option") && w.value
      ).map((w) => `${w.value}`)
    );
    if (app.configuringGraph) return;
    if (values.includes(`${comboWidget.value}`)) return;
    comboWidget.value = values[0] ?? "";
    comboWidget.callback?.(comboWidget.value);
  }, "updateCombo");
  comboWidget.callback = useChainCallback(
    comboWidget.callback,
    () => this.applyToGraph()
  );
  function addOption(node) {
    if (!node.widgets) return;
    const newCount = node.widgets.length - 1;
    node.addWidget("string", `option${newCount}`, "", () => {
    });
    const widget = node.widgets.at(-1);
    if (!widget) return;
    let value = "";
    Object.defineProperty(widget, "value", {
      get() {
        return value;
      },
      set(v) {
        value = v;
        updateCombo();
        if (!node.widgets) return;
        const lastWidget = node.widgets.at(-1);
        if (lastWidget === this) {
          if (v) addOption(node);
          return;
        }
        if (v || node.widgets.at(-2) !== this || lastWidget?.value) return;
        node.widgets.pop();
        node.computeSize(node.size);
        this.callback(v);
      }
    });
  }
  __name(addOption, "addOption");
  addOption(this);
}
__name(onNodeCreated, "onNodeCreated");
app.registerExtension({
  name: "Comfy.CustomCombo",
  beforeRegisterNodeDef(nodeType, nodeData) {
    if (nodeData?.name !== "CustomCombo") return;
    nodeType.prototype.onNodeCreated = useChainCallback(
      nodeType.prototype.onNodeCreated,
      onNodeCreated
    );
  }
});
useExtensionService().registerExtension({
  name: "Comfy.DynamicPrompts",
  nodeCreated(node) {
    if (node.widgets) {
      const widgets = node.widgets.filter((w) => w.dynamicPrompts);
      for (const widget of widgets) {
        widget.serializeValue = (workflowNode, widgetIndex) => {
          if (typeof widget.value !== "string") return widget.value;
          const prompt = processDynamicPrompt(widget.value);
          if (workflowNode?.widgets_values)
            workflowNode.widgets_values[widgetIndex] = prompt;
          return prompt;
        };
      }
    }
  }
});
app.registerExtension({
  name: "Comfy.EditAttention",
  init() {
    const editAttentionDelta = app.ui.settings.addSetting({
      id: "Comfy.EditAttention.Delta",
      category: ["Comfy", "EditTokenWeight", "Delta"],
      name: "Ctrl+up/down precision",
      type: "slider",
      attrs: {
        min: 0.01,
        max: 0.5,
        step: 0.01
      },
      defaultValue: 0.05
    });
    function incrementWeight(weight, delta) {
      const floatWeight = parseFloat(weight);
      if (isNaN(floatWeight)) return weight;
      const newWeight = floatWeight + delta;
      return String(Number(newWeight.toFixed(10)));
    }
    __name(incrementWeight, "incrementWeight");
    function findNearestEnclosure(text, cursorPos) {
      let start = cursorPos, end = cursorPos;
      let openCount = 0, closeCount = 0;
      while (start >= 0) {
        start--;
        if (text[start] === "(" && openCount === closeCount) break;
        if (text[start] === "(") openCount++;
        if (text[start] === ")") closeCount++;
      }
      if (start < 0) return null;
      openCount = 0;
      closeCount = 0;
      while (end < text.length) {
        if (text[end] === ")" && openCount === closeCount) break;
        if (text[end] === "(") openCount++;
        if (text[end] === ")") closeCount++;
        end++;
      }
      if (end === text.length) return null;
      return { start: start + 1, end };
    }
    __name(findNearestEnclosure, "findNearestEnclosure");
    function addWeightToParentheses(text) {
      const parenRegex = /^\((.*)\)$/;
      const parenMatch = text.match(parenRegex);
      const floatRegex = /:([+-]?(\d*\.)?\d+([eE][+-]?\d+)?)/;
      const floatMatch = text.match(floatRegex);
      if (parenMatch && !floatMatch) {
        return `(${parenMatch[1]}:1.0)`;
      } else {
        return text;
      }
    }
    __name(addWeightToParentheses, "addWeightToParentheses");
    function editAttention(event) {
      const inputField = event.composedPath()[0];
      const delta = parseFloat(editAttentionDelta.value);
      if (inputField.tagName !== "TEXTAREA") return;
      if (!(event.key === "ArrowUp" || event.key === "ArrowDown")) return;
      if (!event.ctrlKey && !event.metaKey) return;
      event.preventDefault();
      let start = inputField.selectionStart;
      let end = inputField.selectionEnd;
      let selectedText = inputField.value.substring(start, end);
      if (!selectedText) {
        const nearestEnclosure = findNearestEnclosure(inputField.value, start);
        if (nearestEnclosure) {
          start = nearestEnclosure.start;
          end = nearestEnclosure.end;
          selectedText = inputField.value.substring(start, end);
        } else {
          const delimiters = " .,\\/!?%^*;:{}=-_`~()\r\n	";
          while (!delimiters.includes(inputField.value[start - 1]) && start > 0) {
            start--;
          }
          while (!delimiters.includes(inputField.value[end]) && end < inputField.value.length) {
            end++;
          }
          selectedText = inputField.value.substring(start, end);
          if (!selectedText) return;
        }
      }
      if (selectedText[selectedText.length - 1] === " ") {
        selectedText = selectedText.substring(0, selectedText.length - 1);
        end -= 1;
      }
      if (inputField.value[start - 1] === "(" && inputField.value[end] === ")") {
        start -= 1;
        end += 1;
        selectedText = inputField.value.substring(start, end);
      }
      if (selectedText[0] !== "(" || selectedText[selectedText.length - 1] !== ")") {
        selectedText = `(${selectedText})`;
      }
      selectedText = addWeightToParentheses(selectedText);
      const weightDelta = event.key === "ArrowUp" ? delta : -delta;
      const updatedText = selectedText.replace(
        /\((.*):([+-]?\d+(?:\.\d+)?)\)/,
        (_, text, weight) => {
          weight = incrementWeight(weight, weightDelta);
          if (weight == 1) {
            return text;
          } else {
            return `(${text}:${weight})`;
          }
        }
      );
      inputField.setSelectionRange(start, end);
      document.execCommand("insertText", false, updatedText);
      inputField.setSelectionRange(start, start + updatedText.length);
    }
    __name(editAttention, "editAttention");
    window.addEventListener("keydown", editAttention);
  }
});
const PYTHON_MIRROR = {
  validationPathSuffix: "/20250115/cpython-3.10.16+20250115-aarch64-apple-darwin-debug-full.tar.zst.sha256"
};
const checkMirrorReachable = /* @__PURE__ */ __name(async (mirror) => {
  return isValidUrl(mirror) && await electronAPI().NetWork.canAccessUrl(mirror);
}, "checkMirrorReachable");
(async () => {
  if (!isElectron()) return;
  const electronAPI$1 = electronAPI();
  const desktopAppVersion = await electronAPI$1.getElectronVersion();
  const workflowStore = useWorkflowStore();
  const toastStore = useToastStore();
  const { staticUrls, buildDocsUrl } = useExternalLink();
  const onChangeRestartApp = /* @__PURE__ */ __name((newValue, oldValue) => {
    if (oldValue !== void 0 && newValue !== oldValue) {
      electronAPI$1.restartApp("Restart ComfyUI to apply changes.", 1500);
    }
  }, "onChangeRestartApp");
  app.registerExtension({
    name: "Comfy.ElectronAdapter",
    settings: [
      {
        id: "Comfy-Desktop.AutoUpdate",
        category: ["Comfy-Desktop", "General", "AutoUpdate"],
        name: "Automatically check for updates",
        type: "boolean",
        defaultValue: true,
        onChange: onChangeRestartApp
      },
      {
        id: "Comfy-Desktop.SendStatistics",
        category: ["Comfy-Desktop", "General", "Send Statistics"],
        name: "Send anonymous usage metrics",
        type: "boolean",
        defaultValue: true,
        onChange: onChangeRestartApp
      },
      {
        id: "Comfy-Desktop.WindowStyle",
        category: ["Comfy-Desktop", "General", "Window Style"],
        name: "Window Style",
        tooltip: "Custom: Replace the system title bar with ComfyUI's Top menu",
        type: "combo",
        experimental: true,
        defaultValue: "default",
        options: ["default", "custom"],
        onChange: /* @__PURE__ */ __name((newValue, oldValue) => {
          if (!oldValue) return;
          electronAPI$1.Config.setWindowStyle(newValue);
        }, "onChange")
      },
      {
        id: "Comfy-Desktop.UV.PythonInstallMirror",
        name: "Python Install Mirror",
        tooltip: `Managed Python installations are downloaded from the Astral python-build-standalone project. This variable can be set to a mirror URL to use a different source for Python installations. The provided URL will replace https://github.com/astral-sh/python-build-standalone/releases/download in, e.g., https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz. Distributions can be read from a local directory by using the file:// URL scheme.`,
        type: "url",
        defaultValue: "",
        attrs: {
          validateUrlFn(mirror) {
            return checkMirrorReachable(
              mirror + PYTHON_MIRROR.validationPathSuffix
            );
          }
        }
      },
      {
        id: "Comfy-Desktop.UV.PypiInstallMirror",
        name: "Pypi Install Mirror",
        tooltip: `Default pip install mirror`,
        type: "url",
        defaultValue: "",
        attrs: {
          validateUrlFn: checkMirrorReachable
        }
      },
      {
        id: "Comfy-Desktop.UV.TorchInstallMirror",
        name: "Torch Install Mirror",
        tooltip: `Pip install mirror for pytorch`,
        type: "url",
        defaultValue: "",
        attrs: {
          validateUrlFn: checkMirrorReachable
        }
      }
    ],
    commands: [
      {
        id: "Comfy-Desktop.Folders.OpenLogsFolder",
        label: "Open Logs Folder",
        icon: "pi pi-folder-open",
        function() {
          electronAPI$1.openLogsFolder();
        }
      },
      {
        id: "Comfy-Desktop.Folders.OpenModelsFolder",
        label: "Open Models Folder",
        icon: "pi pi-folder-open",
        function() {
          electronAPI$1.openModelsFolder();
        }
      },
      {
        id: "Comfy-Desktop.Folders.OpenOutputsFolder",
        label: "Open Outputs Folder",
        icon: "pi pi-folder-open",
        function() {
          electronAPI$1.openOutputsFolder();
        }
      },
      {
        id: "Comfy-Desktop.Folders.OpenInputsFolder",
        label: "Open Inputs Folder",
        icon: "pi pi-folder-open",
        function() {
          electronAPI$1.openInputsFolder();
        }
      },
      {
        id: "Comfy-Desktop.Folders.OpenCustomNodesFolder",
        label: "Open Custom Nodes Folder",
        icon: "pi pi-folder-open",
        function() {
          electronAPI$1.openCustomNodesFolder();
        }
      },
      {
        id: "Comfy-Desktop.Folders.OpenModelConfig",
        label: "Open extra_model_paths.yaml",
        icon: "pi pi-file",
        function() {
          electronAPI$1.openModelConfig();
        }
      },
      {
        id: "Comfy-Desktop.OpenDevTools",
        label: "Open DevTools",
        icon: "pi pi-code",
        function() {
          electronAPI$1.openDevTools();
        }
      },
      {
        id: "Comfy-Desktop.OpenUserGuide",
        label: "Desktop User Guide",
        icon: "pi pi-book",
        function() {
          window.open(
            buildDocsUrl("/installation/desktop", {
              includeLocale: true,
              platform: true
            }),
            "_blank"
          );
        }
      },
      {
        id: "Comfy-Desktop.CheckForUpdates",
        label: "Check for Updates",
        icon: "pi pi-sync",
        async function() {
          try {
            const updateInfo = await electronAPI$1.checkForUpdates({
              disableUpdateReadyAction: true
            });
            if (!updateInfo.isUpdateAvailable) {
              toastStore.add({
                severity: "info",
                summary: t("desktopUpdate.noUpdateFound"),
                life: 5e3
              });
              return;
            }
            const proceed = await useDialogService().confirm({
              title: t("desktopUpdate.updateFoundTitle", {
                version: updateInfo.version
              }),
              message: t("desktopUpdate.updateAvailableMessage"),
              type: "default"
            });
            if (proceed) {
              try {
                electronAPI$1.restartAndInstall();
              } catch (error) {
                log.error("Error installing update:", error);
                toastStore.add({
                  severity: "error",
                  summary: t("g.error"),
                  detail: t("desktopUpdate.errorInstallingUpdate"),
                  life: 1e4
                });
              }
            }
          } catch (error) {
            log.error("Error checking for updates:", error);
            toastStore.add({
              severity: "error",
              summary: t("g.error"),
              detail: t("desktopUpdate.errorCheckingUpdate"),
              life: 1e4
            });
          }
        }
      },
      {
        id: "Comfy-Desktop.Reinstall",
        label: "Reinstall",
        icon: "pi pi-refresh",
        async function() {
          const proceed = await useDialogService().confirm({
            message: t("desktopMenu.confirmReinstall"),
            title: t("desktopMenu.reinstall"),
            type: "reinstall"
          });
          if (proceed) electronAPI$1.reinstall();
        }
      },
      {
        id: "Comfy-Desktop.Restart",
        label: "Restart",
        icon: "pi pi-refresh",
        function() {
          electronAPI$1.restartApp();
        }
      },
      {
        id: "Comfy-Desktop.Quit",
        label: "Quit",
        icon: "pi pi-sign-out",
        async function() {
          if (workflowStore.modifiedWorkflows.length > 0) {
            const confirmed = await useDialogService().confirm({
              message: t("desktopMenu.confirmQuit"),
              title: t("desktopMenu.quit"),
              type: "default"
            });
            if (!confirmed) return;
          }
          electronAPI$1.quit();
        }
      }
    ],
    menuCommands: [
      {
        path: ["Help"],
        commands: ["Comfy-Desktop.OpenUserGuide"]
      },
      {
        path: ["Help"],
        commands: ["Comfy-Desktop.OpenDevTools"]
      },
      {
        path: ["Help", "Open Folder"],
        commands: [
          "Comfy-Desktop.Folders.OpenLogsFolder",
          "Comfy-Desktop.Folders.OpenModelsFolder",
          "Comfy-Desktop.Folders.OpenOutputsFolder",
          "Comfy-Desktop.Folders.OpenInputsFolder",
          "Comfy-Desktop.Folders.OpenCustomNodesFolder",
          "Comfy-Desktop.Folders.OpenModelConfig"
        ]
      },
      {
        path: ["Help"],
        commands: ["Comfy-Desktop.CheckForUpdates", "Comfy-Desktop.Reinstall"]
      }
    ],
    keybindings: [
      {
        commandId: "Workspace.CloseWorkflow",
        combo: {
          key: "w",
          ctrl: true
        }
      }
    ],
    aboutPageBadges: [
      {
        label: "ComfyUI_desktop v" + desktopAppVersion,
        url: staticUrls.githubElectron,
        icon: "pi pi-github"
      }
    ]
  });
})();
class ExecutableGroupNodeChildDTO extends ExecutableNodeDTO {
  static {
    __name(this, "ExecutableGroupNodeChildDTO");
  }
  groupNodeHandler;
  constructor(node, subgraphNodePath, nodesByExecutionId, subgraphNode, groupNodeHandler) {
    super(node, subgraphNodePath, nodesByExecutionId, subgraphNode);
    this.groupNodeHandler = groupNodeHandler;
  }
  resolveInput(slot) {
    if (this.id.split(":").length > 2) {
      throw new Error(
        "Group nodes inside subgraphs are not supported. Please convert the group node to a subgraph instead."
      );
    }
    const inputNode = this.node.getInputNode(slot);
    if (!inputNode) return;
    const link = this.node.getInputLink(slot);
    if (!link) throw new Error("Failed to get input link");
    const inputNodeId = String(inputNode.id);
    let inputNodeDto = this.nodesByExecutionId?.get(inputNodeId);
    if (!inputNodeDto) {
      const id2 = inputNodeId.split(":").at(-1);
      if (id2 !== void 0) {
        inputNodeDto = this.nodesByExecutionId?.get(id2);
      }
    }
    if (!inputNodeDto) {
      throw new Error(
        `Failed to get input node ${inputNodeId} for group node child ${this.id} with slot ${slot}`
      );
    }
    return {
      node: inputNodeDto,
      origin_id: inputNodeId,
      origin_slot: link.origin_slot
    };
  }
}
const ORDER = /* @__PURE__ */ Symbol();
function merge(target, source) {
  if (typeof target === "object" && typeof source === "object") {
    for (const key in source) {
      const sv = source[key];
      if (typeof sv === "object") {
        let tv = target[key];
        if (!tv) tv = target[key] = {};
        merge(tv, source[key]);
      } else {
        target[key] = sv;
      }
    }
  }
  return target;
}
__name(merge, "merge");
class ManageGroupDialog extends ComfyDialog$1 {
  static {
    __name(this, "ManageGroupDialog");
  }
  // @ts-expect-error fixme ts strict error
  tabs;
  selectedNodeIndex;
  selectedTab = "Inputs";
  selectedGroup;
  modifications = {};
  // @ts-expect-error fixme ts strict error
  nodeItems;
  app;
  // @ts-expect-error fixme ts strict error
  groupNodeType;
  groupNodeDef;
  groupData;
  // @ts-expect-error fixme ts strict error
  innerNodesList;
  // @ts-expect-error fixme ts strict error
  widgetsPage;
  // @ts-expect-error fixme ts strict error
  inputsPage;
  // @ts-expect-error fixme ts strict error
  outputsPage;
  draggable;
  get selectedNodeInnerIndex() {
    return +this.nodeItems[this.selectedNodeIndex].dataset.nodeindex;
  }
  // @ts-expect-error fixme ts strict error
  constructor(app2) {
    super();
    this.app = app2;
    this.element = $el("dialog.comfy-group-manage", {
      parent: document.body
    });
  }
  // @ts-expect-error fixme ts strict error
  changeTab(tab) {
    this.tabs[this.selectedTab].tab.classList.remove("active");
    this.tabs[this.selectedTab].page.classList.remove("active");
    this.tabs[tab].tab.classList.add("active");
    this.tabs[tab].page.classList.add("active");
    this.selectedTab = tab;
  }
  // @ts-expect-error fixme ts strict error
  changeNode(index, force) {
    if (!force && this.selectedNodeIndex === index) return;
    if (this.selectedNodeIndex != null) {
      this.nodeItems[this.selectedNodeIndex].classList.remove("selected");
    }
    this.nodeItems[index].classList.add("selected");
    this.selectedNodeIndex = index;
    if (!this.buildInputsPage() && this.selectedTab === "Inputs") {
      this.changeTab("Widgets");
    }
    if (!this.buildWidgetsPage() && this.selectedTab === "Widgets") {
      this.changeTab("Outputs");
    }
    if (!this.buildOutputsPage() && this.selectedTab === "Outputs") {
      this.changeTab("Inputs");
    }
    this.changeTab(this.selectedTab);
  }
  getGroupData() {
    this.groupNodeType = LiteGraph.registered_node_types[`${PREFIX}${SEPARATOR}` + this.selectedGroup];
    this.groupNodeDef = this.groupNodeType.nodeData;
    this.groupData = GroupNodeHandler.getGroupData(this.groupNodeType);
  }
  // @ts-expect-error fixme ts strict error
  changeGroup(group, reset = true) {
    this.selectedGroup = group;
    this.getGroupData();
    const nodes = this.groupData.nodeData.nodes;
    this.nodeItems = nodes.map(
      (n, i) => $el(
        "li.draggable-item",
        {
          dataset: {
            nodeindex: n.index + ""
          },
          onclick: /* @__PURE__ */ __name(() => {
            this.changeNode(i);
          }, "onclick")
        },
        [
          $el("span.drag-handle"),
          $el(
            "div",
            {
              textContent: n.title ?? n.type
            },
            n.title ? $el("span", {
              textContent: n.type
            }) : []
          )
        ]
      )
    );
    this.innerNodesList.replaceChildren(...this.nodeItems);
    if (reset) {
      this.selectedNodeIndex = null;
      this.changeNode(0);
    } else {
      const items = this.draggable.getAllItems();
      let index = items.findIndex((item) => item.classList.contains("selected"));
      if (index === -1) index = this.selectedNodeIndex;
      this.changeNode(index, true);
    }
    const ordered = [...nodes];
    this.draggable?.dispose();
    this.draggable = new DraggableList(this.innerNodesList, "li");
    this.draggable.addEventListener(
      "dragend",
      // @ts-expect-error fixme ts strict error
      ({ detail: { oldPosition, newPosition } }) => {
        if (oldPosition === newPosition) return;
        ordered.splice(newPosition, 0, ordered.splice(oldPosition, 1)[0]);
        for (let i = 0; i < ordered.length; i++) {
          this.storeModification({
            nodeIndex: ordered[i].index,
            section: ORDER,
            prop: "order",
            value: i
          });
        }
      }
    );
  }
  storeModification(props) {
    const { nodeIndex, section, prop, value } = props;
    const groupMod = this.modifications[this.selectedGroup] ??= {};
    const nodesMod = groupMod.nodes ??= {};
    const nodeMod = nodesMod[nodeIndex ?? this.selectedNodeInnerIndex] ??= {};
    const typeMod = nodeMod[section] ??= {};
    if (typeof value === "object") {
      const objMod = typeMod[prop] ??= {};
      Object.assign(objMod, value);
    } else {
      typeMod[prop] = value;
    }
  }
  // @ts-expect-error fixme ts strict error
  getEditElement(section, prop, value, placeholder, checked, checkable = true) {
    if (value === placeholder) value = "";
    const mods = (
      // @ts-expect-error fixme ts strict error
      this.modifications[this.selectedGroup]?.nodes?.[this.selectedNodeInnerIndex]?.[section]?.[prop]
    );
    if (mods) {
      if (mods.name != null) {
        value = mods.name;
      }
      if (mods.visible != null) {
        checked = mods.visible;
      }
    }
    return $el("div", [
      $el("input", {
        value,
        placeholder,
        type: "text",
        // @ts-expect-error fixme ts strict error
        onchange: /* @__PURE__ */ __name((e) => {
          this.storeModification({
            section,
            prop,
            value: { name: e.target.value }
          });
        }, "onchange")
      }),
      $el("label", { textContent: "Visible" }, [
        $el("input", {
          type: "checkbox",
          checked,
          disabled: !checkable,
          // @ts-expect-error fixme ts strict error
          onchange: /* @__PURE__ */ __name((e) => {
            this.storeModification({
              section,
              prop,
              value: { visible: !!e.target.checked }
            });
          }, "onchange")
        })
      ])
    ]);
  }
  buildWidgetsPage() {
    const widgets = this.groupData.oldToNewWidgetMap[this.selectedNodeInnerIndex];
    const items = Object.keys(widgets ?? {});
    const type = app.rootGraph.extra.groupNodes[this.selectedGroup];
    const config = type.config?.[this.selectedNodeInnerIndex]?.input;
    this.widgetsPage.replaceChildren(
      ...items.map((oldName) => {
        return this.getEditElement(
          "input",
          oldName,
          widgets[oldName],
          oldName,
          config?.[oldName]?.visible !== false
        );
      })
    );
    return !!items.length;
  }
  buildInputsPage() {
    const inputs = this.groupData.nodeInputs[this.selectedNodeInnerIndex];
    const items = Object.keys(inputs ?? {});
    const type = app.rootGraph.extra.groupNodes[this.selectedGroup];
    const config = type.config?.[this.selectedNodeInnerIndex]?.input;
    this.inputsPage.replaceChildren(
      ...items.map((oldName) => {
        let value = inputs[oldName];
        if (!value) {
          return;
        }
        return this.getEditElement(
          "input",
          oldName,
          value,
          oldName,
          config?.[oldName]?.visible !== false
        );
      }).filter(Boolean)
    );
    return !!items.length;
  }
  buildOutputsPage() {
    const nodes = this.groupData.nodeData.nodes;
    const innerNodeDef = this.groupData.getNodeDef(
      nodes[this.selectedNodeInnerIndex]
    );
    const outputs = innerNodeDef?.output ?? [];
    const groupOutputs = this.groupData.oldToNewOutputMap[this.selectedNodeInnerIndex];
    const type = app.rootGraph.extra.groupNodes[this.selectedGroup];
    const config = type.config?.[this.selectedNodeInnerIndex]?.output;
    const node = this.groupData.nodeData.nodes[this.selectedNodeInnerIndex];
    const checkable = node.type !== "PrimitiveNode";
    this.outputsPage.replaceChildren(
      ...outputs.map((type2, slot) => {
        const groupOutputIndex = groupOutputs?.[slot];
        const oldName = innerNodeDef.output_name?.[slot] ?? type2;
        let value = config?.[slot]?.name;
        const visible = config?.[slot]?.visible || groupOutputIndex != null;
        if (!value || value === oldName) {
          value = "";
        }
        return this.getEditElement(
          "output",
          slot,
          value,
          oldName,
          visible,
          checkable
        );
      }).filter(Boolean)
    );
    return !!outputs.length;
  }
  // @ts-expect-error fixme ts strict error
  show(type) {
    const groupNodes = Object.keys(app.rootGraph.extra?.groupNodes ?? {}).sort(
      (a, b) => a.localeCompare(b)
    );
    this.innerNodesList = $el(
      "ul.comfy-group-manage-list-items"
    );
    this.widgetsPage = $el("section.comfy-group-manage-node-page");
    this.inputsPage = $el("section.comfy-group-manage-node-page");
    this.outputsPage = $el("section.comfy-group-manage-node-page");
    const pages = $el("div", [
      this.widgetsPage,
      this.inputsPage,
      this.outputsPage
    ]);
    this.tabs = [
      ["Inputs", this.inputsPage],
      ["Widgets", this.widgetsPage],
      ["Outputs", this.outputsPage]
      // @ts-expect-error fixme ts strict error
    ].reduce((p, [name, page]) => {
      p[name] = {
        tab: $el("a", {
          onclick: /* @__PURE__ */ __name(() => {
            this.changeTab(name);
          }, "onclick"),
          textContent: name
        }),
        page
      };
      return p;
    }, {});
    const outer = $el("div.comfy-group-manage-outer", [
      $el("header", [
        $el("h2", "Group Nodes"),
        $el(
          "select",
          {
            // @ts-expect-error fixme ts strict error
            onchange: /* @__PURE__ */ __name((e) => {
              this.changeGroup(e.target.value);
            }, "onchange")
          },
          groupNodes.map(
            (g) => $el("option", {
              textContent: g,
              selected: `${PREFIX}${SEPARATOR}${g}` === type,
              value: g
            })
          )
        )
      ]),
      $el("main", [
        $el("section.comfy-group-manage-list", this.innerNodesList),
        $el("section.comfy-group-manage-node", [
          $el(
            "header",
            Object.values(this.tabs).map((t2) => t2.tab)
          ),
          pages
        ])
      ]),
      $el("footer", [
        $el(
          "button.comfy-btn",
          {
            onclick: /* @__PURE__ */ __name(() => {
              const node = app.rootGraph.nodes.find(
                (n) => n.type === `${PREFIX}${SEPARATOR}` + this.selectedGroup
              );
              if (node) {
                useToastStore().addAlert(
                  "This group node is in use in the current workflow, please first remove these."
                );
                return;
              }
              if (confirm(
                `Are you sure you want to remove the node: "${this.selectedGroup}"`
              )) {
                delete app.rootGraph.extra.groupNodes[this.selectedGroup];
                LiteGraph.unregisterNodeType(
                  `${PREFIX}${SEPARATOR}` + this.selectedGroup
                );
              }
              this.show();
            }, "onclick")
          },
          "Delete Group Node"
        ),
        $el(
          "button.comfy-btn",
          {
            onclick: /* @__PURE__ */ __name(async () => {
              let nodesByType;
              let recreateNodes = [];
              const types = {};
              for (const g in this.modifications) {
                const type2 = app.rootGraph.extra.groupNodes[g];
                let config = type2.config ??= {};
                let nodeMods = this.modifications[g]?.nodes;
                if (nodeMods) {
                  const keys = Object.keys(nodeMods);
                  if (nodeMods[keys[0]][ORDER]) {
                    const orderedNodes = [];
                    const orderedMods = {};
                    const orderedConfig = {};
                    for (const n of keys) {
                      const order = nodeMods[n][ORDER].order;
                      orderedNodes[order] = type2.nodes[+n];
                      orderedMods[order] = nodeMods[n];
                      orderedNodes[order].index = order;
                    }
                    for (const l of type2.links) {
                      if (l[0] != null) l[0] = type2.nodes[l[0]].index;
                      if (l[2] != null) l[2] = type2.nodes[l[2]].index;
                    }
                    if (type2.external) {
                      for (const ext2 of type2.external) {
                        ext2[0] = type2.nodes[ext2[0]];
                      }
                    }
                    for (const id2 of keys) {
                      if (config[id2]) {
                        orderedConfig[type2.nodes[id2].index] = config[id2];
                      }
                      delete config[id2];
                    }
                    type2.nodes = orderedNodes;
                    nodeMods = orderedMods;
                    type2.config = config = orderedConfig;
                  }
                  merge(config, nodeMods);
                }
                types[g] = type2;
                if (!nodesByType) {
                  nodesByType = app.rootGraph.nodes.reduce((p, n) => {
                    p[n.type] ??= [];
                    p[n.type].push(n);
                    return p;
                  }, {});
                }
                const nodes = nodesByType[`${PREFIX}${SEPARATOR}` + g];
                if (nodes) recreateNodes.push(...nodes);
              }
              await GroupNodeConfig.registerFromWorkflow(types, {});
              for (const node of recreateNodes) {
                node.recreate();
              }
              this.modifications = {};
              this.app.canvas.setDirty(true, true);
              this.changeGroup(this.selectedGroup, false);
            }, "onclick")
          },
          "Save"
        ),
        $el(
          "button.comfy-btn",
          { onclick: /* @__PURE__ */ __name(() => this.element.close(), "onclick") },
          "Close"
        )
      ])
    ]);
    this.element.replaceChildren(outer);
    this.changeGroup(
      type ? groupNodes.find((g) => `${PREFIX}${SEPARATOR}${g}` === type) ?? groupNodes[0] : groupNodes[0]
    );
    this.element.showModal();
    this.element.addEventListener("close", () => {
      this.draggable?.dispose();
      this.element.remove();
    });
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.groupNodeManage = window.comfyAPI.groupNodeManage || {};
window.comfyAPI.groupNodeManage.ManageGroupDialog = ManageGroupDialog;
const IGNORE_KEYS = /* @__PURE__ */ new Set([
  "default",
  "forceInput",
  "defaultInput",
  "control_after_generate",
  "multiline",
  "tooltip",
  "dynamicPrompts"
]);
const getRange = /* @__PURE__ */ __name((options) => {
  const min = options.min ?? -Infinity;
  const max = options.max ?? Infinity;
  return { min, max };
}, "getRange");
const mergeNumericInputSpec = /* @__PURE__ */ __name((spec1, spec2) => {
  const type = spec1[0];
  const options1 = spec1[1] ?? {};
  const options2 = spec2[1] ?? {};
  const range1 = getRange(options1);
  const range2 = getRange(options2);
  if (range1.min > range2.max || range1.max < range2.min) {
    return null;
  }
  const step1 = options1.step ?? 1;
  const step2 = options2.step ?? 1;
  const mergedOptions = {
    // Take intersection of ranges
    min: Math.max(range1.min, range2.min),
    max: Math.min(range1.max, range2.max),
    step: lcm(step1, step2)
  };
  return mergeCommonInputSpec(
    [type, { ...options1, ...mergedOptions }],
    [type, { ...options2, ...mergedOptions }]
  );
}, "mergeNumericInputSpec");
const mergeComboInputSpec = /* @__PURE__ */ __name((spec1, spec2) => {
  const options1 = spec1[1] ?? {};
  const options2 = spec2[1] ?? {};
  const comboOptions1 = getComboSpecComboOptions(spec1);
  const comboOptions2 = getComboSpecComboOptions(spec2);
  const intersection = toolkit.intersection(comboOptions1, comboOptions2);
  if (intersection.length === 0) {
    return null;
  }
  return mergeCommonInputSpec(
    ["COMBO", { ...options1, options: intersection }],
    ["COMBO", { ...options2, options: intersection }]
  );
}, "mergeComboInputSpec");
const mergeCommonInputSpec = /* @__PURE__ */ __name((spec1, spec2) => {
  const type = getInputSpecType(spec1);
  const options1 = spec1[1] ?? {};
  const options2 = spec2[1] ?? {};
  const compareKeys = toolkit.union(toolkit.keys(options1), toolkit.keys(options2)).filter(
    (key) => !IGNORE_KEYS.has(key)
  );
  const mergeIsValid = compareKeys.every((key) => {
    const value1 = options1[key];
    const value2 = options2[key];
    return value1 === value2 || toolkit.isNil(value1) && toolkit.isNil(value2);
  });
  return mergeIsValid ? [type, { ...options1, ...options2 }] : null;
}, "mergeCommonInputSpec");
const mergeInputSpec = /* @__PURE__ */ __name((spec1, spec2) => {
  const type1 = getInputSpecType(spec1);
  const type2 = getInputSpecType(spec2);
  if (type1 !== type2) {
    return null;
  }
  if (isIntInputSpec(spec1) || isFloatInputSpec(spec1)) {
    return mergeNumericInputSpec(spec1, spec2);
  }
  if (isComboInputSpec(spec1)) {
    return mergeComboInputSpec(spec1, spec2);
  }
  return mergeCommonInputSpec(spec1, spec2);
}, "mergeInputSpec");
const isPrimitiveNode = /* @__PURE__ */ __name((node) => node.type === "PrimitiveNode", "isPrimitiveNode");
const replacePropertyName = "Run widget replace on values";
class PrimitiveNode extends LGraphNode {
  static {
    __name(this, "PrimitiveNode");
  }
  controlValues;
  lastType;
  static category;
  constructor(title) {
    super(title);
    this.addOutput("connect to widget input", "*");
    this.serialize_widgets = true;
    this.isVirtualNode = true;
    if (!this.properties || !(replacePropertyName in this.properties)) {
      this.addProperty(replacePropertyName, false, "boolean");
    }
  }
  applyToGraph(extraLinks = []) {
    if (!this.outputs[0].links?.length || !this.graph) return;
    const links = [
      ...this.outputs[0].links.map((l) => this.graph.links[l]),
      ...extraLinks
    ];
    let v = this.widgets?.[0].value;
    if (v && this.properties[replacePropertyName]) {
      v = applyTextReplacements(this.graph, v);
    }
    for (const linkInfo of links) {
      const node = this.graph?.getNodeById(linkInfo.target_id);
      const input = node?.inputs[linkInfo.target_slot];
      if (!input) {
        console.warn("Unable to resolve node or input for link", linkInfo);
        continue;
      }
      const widgetName = input.widget?.name;
      if (!widgetName) {
        console.warn("Invalid widget or widget name", input.widget);
        continue;
      }
      const widget = node.widgets?.find((w) => w.name === widgetName);
      if (!widget) {
        console.warn(
          `Unable to find widget "${widgetName}" on node [${node.id}]`
        );
        continue;
      }
      widget.value = v;
      widget.callback?.(
        widget.value,
        app.canvas,
        node,
        app.canvas.graph_mouse,
        {}
      );
    }
  }
  refreshComboInNode() {
    const widget = this.widgets?.[0];
    if (widget?.type === "combo") {
      widget.options.values = this.outputs[0].widget[GET_CONFIG]()[0];
      if (!widget.options.values.includes(widget.value)) {
        widget.value = widget.options.values[0];
        widget.callback(widget.value);
      }
    }
  }
  onAfterGraphConfigured() {
    if (this.outputs[0].links?.length && !this.widgets?.length) {
      this.#onFirstConnection();
      if (this.widgets && this.widgets_values) {
        for (let i = 0; i < this.widgets_values.length; i++) {
          const w = this.widgets[i];
          if (w) {
            w.value = this.widgets_values[i];
          }
        }
      }
      this.#mergeWidgetConfig();
    }
  }
  onConnectionsChange(_type, _index, connected) {
    if (app.configuringGraph) {
      return;
    }
    const links = this.outputs[0].links;
    if (connected) {
      if (links?.length && !this.widgets?.length) {
        this.#onFirstConnection();
      }
    } else {
      this.#mergeWidgetConfig();
      if (!links?.length) {
        this.onLastDisconnect();
      }
    }
  }
  onConnectOutput(slot, _type, input, target_node, target_slot) {
    if (!input.widget && !(input.type in ComfyWidgets)) {
      return false;
    }
    if (this.outputs[slot].links?.length) {
      const valid = this.#isValidConnection(input);
      if (valid) {
        this.applyToGraph([{ target_id: target_node.id, target_slot }]);
      }
      return valid;
    }
    return true;
  }
  #onFirstConnection(recreating) {
    if (!this.outputs[0].links || !this.graph) {
      this.onLastDisconnect();
      return;
    }
    const linkId = this.outputs[0].links[0];
    const link = this.graph.links[linkId];
    if (!link) return;
    const theirNode = this.graph.getNodeById(link.target_id);
    if (!theirNode || !theirNode.inputs) return;
    const input = theirNode.inputs[link.target_slot];
    if (!input) return;
    let widget;
    if (!input.widget) {
      if (!(input.type in ComfyWidgets)) return;
      widget = { name: input.name, [GET_CONFIG]: () => [input.type, {}] };
    } else {
      widget = input.widget;
    }
    const config = widget[GET_CONFIG]?.();
    if (!config) return;
    const { type } = getWidgetType(config);
    this.outputs[0].type = type;
    this.outputs[0].name = type;
    this.outputs[0].widget = widget;
    this.#createWidget(
      widget[CONFIG] ?? config,
      theirNode,
      widget.name,
      // @ts-expect-error fixme ts strict error
      recreating
    );
  }
  #createWidget(inputData, node, widgetName, recreating) {
    let type = inputData[0];
    if (type instanceof Array) {
      type = "COMBO";
    }
    const [oldWidth, oldHeight] = this.size;
    let widget;
    if (isValidWidgetType(type)) {
      widget = (ComfyWidgets[type](this, "value", inputData, app) || {}).widget;
    } else {
      widget = this.addWidget(type, "value", null, () => {
      }, {});
    }
    if (node?.widgets && widget) {
      const theirWidget = node.widgets.find((w) => w.name === widgetName);
      if (theirWidget) {
        widget.value = theirWidget.value;
      }
    }
    if (!inputData?.[1]?.control_after_generate && (widget.type === "number" || widget.type === "combo")) {
      let control_value = this.widgets_values?.[1];
      if (!control_value) {
        control_value = "fixed";
      }
      addValueControlWidgets(
        this,
        widget,
        control_value,
        void 0,
        inputData
      );
      if (this.widgets?.[1]) widget.linkedWidgets = [this.widgets[1]];
      let filter = this.widgets_values?.[2];
      if (filter && this.widgets && this.widgets.length === 3) {
        this.widgets[2].value = filter;
      }
    }
    const controlValues = this.controlValues;
    if (this.widgets && this.lastType === this.widgets[0]?.type && controlValues?.length === this.widgets.length - 1) {
      for (let i = 0; i < controlValues.length; i++) {
        this.widgets[i + 1].value = controlValues[i];
      }
    }
    widget.callback = useChainCallback(widget.callback, () => {
      this.applyToGraph();
    });
    this.setSize([
      Math.max(this.size[0], oldWidth),
      Math.max(this.size[1], oldHeight)
    ]);
    if (!recreating) {
      const sz = this.computeSize();
      if (this.size[0] < sz[0]) {
        this.size[0] = sz[0];
      }
      if (this.size[1] < sz[1]) {
        this.size[1] = sz[1];
      }
      requestAnimationFrame(() => {
        this.onResize?.(this.size);
      });
    }
  }
  recreateWidget() {
    const values = this.widgets?.map((w) => w.value);
    this.#removeWidgets();
    this.#onFirstConnection(true);
    if (values?.length && this.widgets) {
      for (let i = 0; i < this.widgets.length; i++)
        this.widgets[i].value = values[i];
    }
    return this.widgets?.[0];
  }
  #mergeWidgetConfig() {
    const output = this.outputs[0];
    const links = output.links ?? [];
    const hasConfig = !!output.widget?.[CONFIG];
    if (hasConfig) {
      delete output.widget?.[CONFIG];
    }
    if (links?.length < 2 && hasConfig) {
      if (links.length) {
        this.recreateWidget();
      }
      return;
    }
    const config1 = output.widget?.[GET_CONFIG]?.();
    if (!config1) return;
    const isNumber = config1[0] === "INT" || config1[0] === "FLOAT";
    if (!isNumber || !this.graph) return;
    for (const linkId of links) {
      const link = this.graph.links[linkId];
      if (!link) continue;
      const theirNode = this.graph.getNodeById(link.target_id);
      if (!theirNode) continue;
      const theirInput = theirNode.inputs[link.target_slot];
      this.#isValidConnection(theirInput, hasConfig);
    }
  }
  #isValidConnection(input, forceUpdate) {
    const output = this.outputs?.[0];
    const config2 = input.widget?.[GET_CONFIG]?.();
    if (!config2) return false;
    return !!mergeIfValid.call(
      this,
      output,
      config2,
      forceUpdate,
      this.recreateWidget
    );
  }
  #removeWidgets() {
    if (this.widgets) {
      for (const w of this.widgets) {
        if (w.onRemove) {
          w.onRemove();
        }
      }
      this.controlValues = [];
      this.lastType = this.widgets[0]?.type;
      for (let i = 1; i < this.widgets.length; i++) {
        this.controlValues.push(this.widgets[i].value);
      }
      setTimeout(() => {
        delete this.lastType;
        delete this.controlValues;
      }, 15);
      this.widgets.length = 0;
    }
  }
  onLastDisconnect() {
    this.outputs[0].type = "*";
    this.outputs[0].name = "connect to widget input";
    delete this.outputs[0].widget;
    this.#removeWidgets();
  }
}
function getWidgetConfig(slot) {
  return slot.widget?.[CONFIG] ?? slot.widget?.[GET_CONFIG]?.() ?? [
    "*",
    {}
  ];
}
__name(getWidgetConfig, "getWidgetConfig");
function getConfig(widgetName) {
  const { nodeData } = this.constructor;
  return nodeData?.input?.required?.[widgetName] ?? nodeData?.input?.optional?.[widgetName];
}
__name(getConfig, "getConfig");
function convertToInput(node, widget) {
  console.warn(
    "Please remove call to convertToInput. Widget to socket conversion is no longer necessary, as they co-exist now."
  );
  return node.inputs.find((slot) => slot.widget?.name === widget.name);
}
__name(convertToInput, "convertToInput");
function getWidgetType(config) {
  let type = config[0];
  if (type instanceof Array) {
    type = "COMBO";
  }
  return { type };
}
__name(getWidgetType, "getWidgetType");
function setWidgetConfig(slot, config) {
  if (!slot.widget) return;
  if (config) {
    slot.widget[GET_CONFIG] = () => config;
  } else {
    delete slot.widget;
  }
  if (!(slot instanceof NodeSlot)) return;
  const graph = slot.node.graph;
  if (!graph) return;
  const link = graph.links[slot.link ?? -1];
  if (!link) return;
  const originNode = graph.getNodeById(link.origin_id);
  if (!originNode || !isPrimitiveNode(originNode)) return;
  if (config) {
    originNode.recreateWidget();
  } else if (!app.configuringGraph) {
    originNode.disconnectOutput(0);
    originNode.onLastDisconnect();
  }
}
__name(setWidgetConfig, "setWidgetConfig");
function mergeIfValid(output, config2, forceUpdate, recreateWidget, config1) {
  if (!config1) {
    config1 = getWidgetConfig(output);
  }
  const customSpec = mergeInputSpec(config1, config2);
  if (customSpec || forceUpdate) {
    if (customSpec) {
      output.widget[CONFIG] = customSpec;
    }
    const widget = recreateWidget?.call(this);
    if (widget) {
      const min = widget.options.min;
      const max = widget.options.max;
      if (min != null && widget.value < min) widget.value = min;
      if (max != null && widget.value > max) widget.value = max;
      widget.callback(widget.value);
    }
  }
  return { customConfig: customSpec?.[1] ?? {} };
}
__name(mergeIfValid, "mergeIfValid");
app.registerExtension({
  name: "Comfy.WidgetInputs",
  async beforeRegisterNodeDef(nodeType, _nodeData, app2) {
    nodeType.prototype.convertWidgetToInput = function() {
      console.warn(
        "Please remove call to convertWidgetToInput. Widget to socket conversion is no longer necessary, as they co-exist now."
      );
      return false;
    };
    nodeType.prototype.onGraphConfigured = useChainCallback(
      nodeType.prototype.onGraphConfigured,
      function() {
        if (!this.inputs) return;
        this.widgets ??= [];
        for (const input of this.inputs) {
          if (input.widget) {
            const name = input.widget.name;
            if (!input.widget[GET_CONFIG]) {
              input.widget[GET_CONFIG] = () => getConfig.call(this, name);
            }
            const w = this.widgets?.find((w2) => w2.name === name);
            if (!w) {
              this.removeInput(this.inputs.findIndex((i) => i === input));
            }
          }
        }
      }
    );
    nodeType.prototype.onConfigure = useChainCallback(
      nodeType.prototype.onConfigure,
      function() {
        if (!app2.configuringGraph && this.inputs) {
          for (const input of this.inputs) {
            if (input.widget && !input.widget[GET_CONFIG]) {
              const name = input.widget.name;
              input.widget[GET_CONFIG] = () => getConfig.call(this, name);
            }
          }
        }
      }
    );
    const origOnInputDblClick = nodeType.prototype.onInputDblClick;
    nodeType.prototype.onInputDblClick = function(...[slot, ...args]) {
      const r = origOnInputDblClick?.apply(this, [slot, ...args]);
      const input = this.inputs[slot];
      if (!input.widget) {
        if (!(input.type in ComfyWidgets) && !(input.widget?.[GET_CONFIG]?.()?.[0] instanceof Array)) {
          return r;
        }
      }
      const node = LiteGraph.createNode("PrimitiveNode");
      const graph = app2.canvas.graph;
      if (!node || !graph) return r;
      graph?.add(node);
      const pos = [
        this.pos[0] - node.size[0] - 30,
        this.pos[1]
      ];
      while (graph.getNodeOnPos(pos[0], pos[1], graph.nodes))
        pos[1] += LiteGraph.NODE_TITLE_HEIGHT;
      node.pos = pos;
      node.connect(0, this, slot);
      node.title = input.name;
      return r;
    };
  },
  registerCustomNodes() {
    LiteGraph.registerNodeType(
      "PrimitiveNode",
      Object.assign(PrimitiveNode, {
        title: "Primitive"
      })
    );
    PrimitiveNode.category = "utils";
  }
});
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.widgetInputs = window.comfyAPI.widgetInputs || {};
window.comfyAPI.widgetInputs.PrimitiveNode = PrimitiveNode;
window.comfyAPI.widgetInputs.getWidgetConfig = getWidgetConfig;
window.comfyAPI.widgetInputs.convertToInput = convertToInput;
window.comfyAPI.widgetInputs.setWidgetConfig = setWidgetConfig;
window.comfyAPI.widgetInputs.mergeIfValid = mergeIfValid;
const Workflow = {
  InUse: {
    Free: 0,
    Registered: 1,
    InWorkflow: 2
  },
  // @ts-expect-error fixme ts strict error
  isInUseGroupNode(name) {
    const id2 = `${PREFIX}${SEPARATOR}${name}`;
    if (app.rootGraph.extra?.groupNodes?.[name]) {
      if (app.rootGraph.nodes.find((n) => n.type === id2)) {
        return Workflow.InUse.InWorkflow;
      } else {
        return Workflow.InUse.Registered;
      }
    }
    return Workflow.InUse.Free;
  },
  storeGroupNode(name, data) {
    let extra = app.rootGraph.extra;
    if (!extra) app.rootGraph.extra = extra = {};
    let groupNodes = extra.groupNodes;
    if (!groupNodes) extra.groupNodes = groupNodes = {};
    groupNodes[name] = data;
  }
};
class GroupNodeBuilder {
  static {
    __name(this, "GroupNodeBuilder");
  }
  nodes;
  // @ts-expect-error fixme ts strict error
  nodeData;
  constructor(nodes) {
    this.nodes = nodes;
  }
  async build() {
    const name = await this.getName();
    if (!name) return;
    this.sortNodes();
    this.nodeData = this.getNodeData();
    Workflow.storeGroupNode(name, this.nodeData);
    return { name, nodeData: this.nodeData };
  }
  async getName() {
    const name = await useDialogService().prompt({
      title: t("groupNode.create"),
      message: t("groupNode.enterName"),
      defaultValue: ""
    });
    if (!name) return;
    const used = Workflow.isInUseGroupNode(name);
    switch (used) {
      case Workflow.InUse.InWorkflow:
        useToastStore().addAlert(
          "An in use group node with this name already exists embedded in this workflow, please remove any instances or use a new name."
        );
        return;
      case Workflow.InUse.Registered:
        if (!confirm(
          "A group node with this name already exists embedded in this workflow, are you sure you want to overwrite it?"
        )) {
          return;
        }
        break;
    }
    return name;
  }
  sortNodes() {
    const nodesInOrder = app.rootGraph.computeExecutionOrder(false);
    this.nodes = this.nodes.map((node) => ({ index: nodesInOrder.indexOf(node), node })).sort((a, b) => a.index - b.index || a.node.id - b.node.id).map(({ node }) => node);
  }
  getNodeData() {
    const storeLinkTypes = /* @__PURE__ */ __name((config) => {
      for (const link of config.links) {
        const origin = app.rootGraph.getNodeById(link[4]);
        const type = origin.outputs[link[1]].type;
        link.push(type);
      }
    }, "storeLinkTypes");
    const storeExternalLinks = /* @__PURE__ */ __name((config) => {
      config.external = [];
      for (let i = 0; i < this.nodes.length; i++) {
        const node = this.nodes[i];
        if (!node.outputs?.length) continue;
        for (let slot = 0; slot < node.outputs.length; slot++) {
          let hasExternal = false;
          const output = node.outputs[slot];
          let type = output.type;
          if (!output.links?.length) continue;
          for (const l of output.links) {
            const link = app.rootGraph.links[l];
            if (!link) continue;
            if (type === "*") type = link.type;
            if (!app.canvas.selected_nodes[link.target_id]) {
              hasExternal = true;
              break;
            }
          }
          if (hasExternal) {
            config.external.push([i, slot, type]);
          }
        }
      }
    }, "storeExternalLinks");
    try {
      const serialised = serialise(this.nodes, app.canvas?.graph);
      const config = JSON.parse(serialised);
      storeLinkTypes(config);
      storeExternalLinks(config);
      return config;
    } finally {
    }
  }
}
class GroupNodeConfig {
  static {
    __name(this, "GroupNodeConfig");
  }
  name;
  nodeData;
  inputCount;
  oldToNewOutputMap;
  newToOldOutputMap;
  oldToNewInputMap;
  oldToNewWidgetMap;
  newToOldWidgetMap;
  primitiveDefs;
  widgetToPrimitive;
  primitiveToWidget;
  nodeInputs;
  outputVisibility;
  // @ts-expect-error fixme ts strict error
  nodeDef;
  // @ts-expect-error fixme ts strict error
  inputs;
  // @ts-expect-error fixme ts strict error
  linksFrom;
  // @ts-expect-error fixme ts strict error
  linksTo;
  // @ts-expect-error fixme ts strict error
  externalFrom;
  // @ts-expect-error fixme ts strict error
  constructor(name, nodeData) {
    this.name = name;
    this.nodeData = nodeData;
    this.getLinks();
    this.inputCount = 0;
    this.oldToNewOutputMap = {};
    this.newToOldOutputMap = {};
    this.oldToNewInputMap = {};
    this.oldToNewWidgetMap = {};
    this.newToOldWidgetMap = {};
    this.primitiveDefs = {};
    this.widgetToPrimitive = {};
    this.primitiveToWidget = {};
    this.nodeInputs = {};
    this.outputVisibility = [];
  }
  async registerType(source = PREFIX) {
    this.nodeDef = {
      output: [],
      output_name: [],
      output_is_list: [],
      // @ts-expect-error Unused, doesn't exist
      output_is_hidden: [],
      name: source + SEPARATOR + this.name,
      display_name: this.name,
      category: "group nodes" + (SEPARATOR + source),
      input: { required: {} },
      description: `Group node combining ${this.nodeData.nodes.map((n) => n.type).join(", ")}`,
      python_module: "custom_nodes." + this.name,
      [GROUP]: this
    };
    this.inputs = [];
    const seenInputs = {};
    const seenOutputs = {};
    for (let i = 0; i < this.nodeData.nodes.length; i++) {
      const node = this.nodeData.nodes[i];
      node.index = i;
      this.processNode(node, seenInputs, seenOutputs);
    }
    for (const p of this.#convertedToProcess) {
      p();
    }
    this.#convertedToProcess = null;
    await app.registerNodeDef(`${PREFIX}${SEPARATOR}` + this.name, this.nodeDef);
    useNodeDefStore().addNodeDef(this.nodeDef);
  }
  getLinks() {
    this.linksFrom = {};
    this.linksTo = {};
    this.externalFrom = {};
    for (const l of this.nodeData.links) {
      const [sourceNodeId, sourceNodeSlot, targetNodeId, targetNodeSlot] = l;
      if (sourceNodeId == null) continue;
      if (!this.linksFrom[sourceNodeId]) {
        this.linksFrom[sourceNodeId] = {};
      }
      if (!this.linksFrom[sourceNodeId][sourceNodeSlot]) {
        this.linksFrom[sourceNodeId][sourceNodeSlot] = [];
      }
      this.linksFrom[sourceNodeId][sourceNodeSlot].push(l);
      if (!this.linksTo[targetNodeId]) {
        this.linksTo[targetNodeId] = {};
      }
      this.linksTo[targetNodeId][targetNodeSlot] = l;
    }
    if (this.nodeData.external) {
      for (const ext2 of this.nodeData.external) {
        if (!this.externalFrom[ext2[0]]) {
          this.externalFrom[ext2[0]] = { [ext2[1]]: ext2[2] };
        } else {
          this.externalFrom[ext2[0]][ext2[1]] = ext2[2];
        }
      }
    }
  }
  // @ts-expect-error fixme ts strict error
  processNode(node, seenInputs, seenOutputs) {
    const def = this.getNodeDef(node);
    if (!def) return;
    const inputs = { ...def.input?.required, ...def.input?.optional };
    this.inputs.push(this.processNodeInputs(node, seenInputs, inputs));
    if (def.output?.length) this.processNodeOutputs(node, seenOutputs, def);
  }
  // @ts-expect-error fixme ts strict error
  getNodeDef(node) {
    const def = globalDefs[node.type];
    if (def) return def;
    const linksFrom = this.linksFrom[node.index];
    if (node.type === "PrimitiveNode") {
      if (!linksFrom) return;
      let type = linksFrom["0"][0][5];
      if (type === "COMBO") {
        const source = node.outputs[0].widget.name;
        const fromTypeName = this.nodeData.nodes[linksFrom["0"][0][2]].type;
        const fromType = globalDefs[fromTypeName];
        const input = fromType.input.required[source] ?? fromType.input.optional[source];
        type = input[0];
      }
      const def2 = this.primitiveDefs[node.index] = {
        input: {
          required: {
            value: [type, {}]
          }
        },
        output: [type],
        output_name: [],
        output_is_list: []
      };
      return def2;
    } else if (node.type === "Reroute") {
      const linksTo = this.linksTo[node.index];
      if (linksTo && linksFrom && !this.externalFrom[node.index]?.[0]) {
        return null;
      }
      let config = {};
      let rerouteType = "*";
      if (linksFrom) {
        for (const [, , id2, slot] of linksFrom["0"]) {
          const node2 = this.nodeData.nodes[id2];
          const input = node2.inputs[slot];
          if (rerouteType === "*") {
            rerouteType = input.type;
          }
          if (input.widget) {
            const targetDef = globalDefs[node2.type];
            const targetWidget = targetDef.input.required[input.widget.name] ?? targetDef.input.optional[input.widget.name];
            const widget = [targetWidget[0], config];
            const res = mergeIfValid(
              {
                // @ts-expect-error fixme ts strict error
                widget
              },
              targetWidget,
              false,
              null,
              widget
            );
            config = res?.customConfig ?? config;
          }
        }
      } else if (linksTo) {
        const [id2, slot] = linksTo["0"];
        rerouteType = this.nodeData.nodes[id2].outputs[slot].type;
      } else {
        for (const l of this.nodeData.links) {
          if (l[2] === node.index) {
            rerouteType = l[5];
            break;
          }
        }
        if (rerouteType === "*") {
          const t2 = this.externalFrom[node.index]?.[0];
          if (t2) {
            rerouteType = t2;
          }
        }
      }
      config.forceInput = true;
      return {
        input: {
          required: {
            [rerouteType]: [rerouteType, config]
          }
        },
        output: [rerouteType],
        output_name: [],
        output_is_list: []
      };
    }
    console.warn(
      "Skipping virtual node " + node.type + " when building group node " + this.name
    );
  }
  // @ts-expect-error fixme ts strict error
  getInputConfig(node, inputName, seenInputs, config, extra) {
    const customConfig = this.nodeData.config?.[node.index]?.input?.[inputName];
    let name = customConfig?.name ?? // @ts-expect-error fixme ts strict error
    node.inputs?.find((inp) => inp.name === inputName)?.label ?? inputName;
    let key = name;
    let prefix = "";
    if (node.type === "PrimitiveNode" && node.title || name in seenInputs) {
      prefix = `${node.title ?? node.type} `;
      key = name = `${prefix}${inputName}`;
      if (name in seenInputs) {
        name = `${prefix}${seenInputs[name]} ${inputName}`;
      }
    }
    seenInputs[key] = (seenInputs[key] ?? 1) + 1;
    if (inputName === "seed" || inputName === "noise_seed") {
      if (!extra) extra = {};
      extra.control_after_generate = `${prefix}control_after_generate`;
    }
    if (config[0] === "IMAGEUPLOAD") {
      if (!extra) extra = {};
      extra.widget = // @ts-expect-error fixme ts strict error
      this.oldToNewWidgetMap[node.index]?.[config[1]?.widget ?? "image"] ?? "image";
    }
    if (extra) {
      config = [config[0], { ...config[1], ...extra }];
    }
    return { name, config, customConfig };
  }
  // @ts-expect-error fixme ts strict error
  processWidgetInputs(inputs, node, inputNames, seenInputs) {
    const slots = [];
    const converted = /* @__PURE__ */ new Map();
    const widgetMap = this.oldToNewWidgetMap[node.index] = {};
    for (const inputName of inputNames) {
      if (useWidgetStore().inputIsWidget(inputs[inputName])) {
        const convertedIndex = node.inputs?.findIndex(
          // @ts-expect-error fixme ts strict error
          (inp) => inp.name === inputName && inp.widget?.name === inputName
        );
        if (convertedIndex > -1) {
          converted.set(convertedIndex, inputName);
          widgetMap[inputName] = null;
        } else {
          const { name, config } = this.getInputConfig(
            node,
            inputName,
            seenInputs,
            inputs[inputName]
          );
          this.nodeDef.input.required[name] = config;
          widgetMap[inputName] = name;
          this.newToOldWidgetMap[name] = { node, inputName };
        }
      } else {
        slots.push(inputName);
      }
    }
    return { converted, slots };
  }
  // @ts-expect-error fixme ts strict error
  checkPrimitiveConnection(link, inputName, inputs) {
    const sourceNode = this.nodeData.nodes[link[0]];
    if (sourceNode.type === "PrimitiveNode") {
      const [sourceNodeId, _, targetNodeId, __] = link;
      const primitiveDef = this.primitiveDefs[sourceNodeId];
      const targetWidget = inputs[inputName];
      const primitiveConfig = primitiveDef.input.required.value;
      const output = { widget: primitiveConfig };
      const config = mergeIfValid(
        // @ts-expect-error invalid slot type
        output,
        targetWidget,
        false,
        null,
        primitiveConfig
      );
      primitiveConfig[1] = config?.customConfig ?? inputs[inputName][1] ? { ...inputs[inputName][1] } : {};
      let name = this.oldToNewWidgetMap[sourceNodeId]["value"];
      name = name.substr(0, name.length - 6);
      primitiveConfig[1].control_after_generate = true;
      primitiveConfig[1].control_prefix = name;
      let toPrimitive = this.widgetToPrimitive[targetNodeId];
      if (!toPrimitive) {
        toPrimitive = this.widgetToPrimitive[targetNodeId] = {};
      }
      if (toPrimitive[inputName]) {
        toPrimitive[inputName].push(sourceNodeId);
      }
      toPrimitive[inputName] = sourceNodeId;
      let toWidget = this.primitiveToWidget[sourceNodeId];
      if (!toWidget) {
        toWidget = this.primitiveToWidget[sourceNodeId] = [];
      }
      toWidget.push({ nodeId: targetNodeId, inputName });
    }
  }
  // @ts-expect-error fixme ts strict error
  processInputSlots(inputs, node, slots, linksTo, inputMap, seenInputs) {
    this.nodeInputs[node.index] = {};
    for (let i = 0; i < slots.length; i++) {
      const inputName = slots[i];
      if (linksTo[i]) {
        this.checkPrimitiveConnection(linksTo[i], inputName, inputs);
        continue;
      }
      const { name, config, customConfig } = this.getInputConfig(
        node,
        inputName,
        seenInputs,
        inputs[inputName]
      );
      this.nodeInputs[node.index][inputName] = name;
      if (customConfig?.visible === false) continue;
      this.nodeDef.input.required[name] = config;
      inputMap[i] = this.inputCount++;
    }
  }
  processConvertedWidgets(inputs, node, slots, converted, linksTo, inputMap, seenInputs) {
    const convertedSlots = [...converted.keys()].sort().map((k) => converted.get(k));
    for (let i = 0; i < convertedSlots.length; i++) {
      const inputName = convertedSlots[i];
      if (linksTo[slots.length + i]) {
        this.checkPrimitiveConnection(
          linksTo[slots.length + i],
          inputName,
          inputs
        );
        continue;
      }
      const { name, config } = this.getInputConfig(
        node,
        inputName,
        seenInputs,
        inputs[inputName],
        {
          defaultInput: true
        }
      );
      this.nodeDef.input.required[name] = config;
      this.newToOldWidgetMap[name] = { node, inputName };
      if (!this.oldToNewWidgetMap[node.index]) {
        this.oldToNewWidgetMap[node.index] = {};
      }
      this.oldToNewWidgetMap[node.index][inputName] = name;
      inputMap[slots.length + i] = this.inputCount++;
    }
  }
  #convertedToProcess = [];
  // @ts-expect-error fixme ts strict error
  processNodeInputs(node, seenInputs, inputs) {
    const inputMapping = [];
    const inputNames = Object.keys(inputs);
    if (!inputNames.length) return;
    const { converted, slots } = this.processWidgetInputs(
      inputs,
      node,
      inputNames,
      seenInputs
    );
    const linksTo = this.linksTo[node.index] ?? {};
    const inputMap = this.oldToNewInputMap[node.index] = {};
    this.processInputSlots(inputs, node, slots, linksTo, inputMap, seenInputs);
    this.#convertedToProcess.push(
      () => this.processConvertedWidgets(
        inputs,
        node,
        slots,
        converted,
        linksTo,
        inputMap,
        seenInputs
      )
    );
    return inputMapping;
  }
  // @ts-expect-error fixme ts strict error
  processNodeOutputs(node, seenOutputs, def) {
    const oldToNew = this.oldToNewOutputMap[node.index] = {};
    for (let outputId = 0; outputId < def.output.length; outputId++) {
      const linksFrom = this.linksFrom[node.index];
      const hasLink = (
        // @ts-expect-error fixme ts strict error
        linksFrom?.[outputId] && !this.externalFrom[node.index]?.[outputId]
      );
      const customConfig = this.nodeData.config?.[node.index]?.output?.[outputId];
      const visible = customConfig?.visible ?? !hasLink;
      this.outputVisibility.push(visible);
      if (!visible) {
        continue;
      }
      oldToNew[outputId] = this.nodeDef.output.length;
      this.newToOldOutputMap[this.nodeDef.output.length] = {
        node,
        slot: outputId
      };
      this.nodeDef.output.push(def.output[outputId]);
      this.nodeDef.output_is_list.push(def.output_is_list[outputId]);
      let label = customConfig?.name;
      if (!label) {
        label = def.output_name?.[outputId] ?? def.output[outputId];
        const output = node.outputs.find((o) => o.name === label);
        if (output?.label) {
          label = output.label;
        }
      }
      let name = label;
      if (name in seenOutputs) {
        const prefix = `${node.title ?? node.type} `;
        name = `${prefix}${label}`;
        if (name in seenOutputs) {
          name = `${prefix}${node.index} ${label}`;
        }
      }
      seenOutputs[name] = 1;
      this.nodeDef.output_name.push(name);
    }
  }
  // @ts-expect-error fixme ts strict error
  static async registerFromWorkflow(groupNodes, missingNodeTypes) {
    for (const g in groupNodes) {
      const groupData = groupNodes[g];
      let hasMissing = false;
      for (const n of groupData.nodes) {
        if (!(n.type in LiteGraph.registered_node_types)) {
          missingNodeTypes.push({
            type: n.type,
            hint: ` (In group node '${PREFIX}${SEPARATOR}${g}')`
          });
          missingNodeTypes.push({
            type: `${PREFIX}${SEPARATOR}` + g,
            action: {
              text: "Remove from workflow",
              // @ts-expect-error fixme ts strict error
              callback: /* @__PURE__ */ __name((e) => {
                delete groupNodes[g];
                e.target.textContent = "Removed";
                e.target.style.pointerEvents = "none";
                e.target.style.opacity = 0.7;
              }, "callback")
            }
          });
          hasMissing = true;
        }
      }
      if (hasMissing) continue;
      const config = new GroupNodeConfig(g, groupData);
      await config.registerType();
    }
  }
}
class GroupNodeHandler {
  static {
    __name(this, "GroupNodeHandler");
  }
  node;
  groupData;
  innerNodes;
  constructor(node) {
    this.node = node;
    this.groupData = node.constructor?.nodeData?.[GROUP];
    this.node.setInnerNodes = (innerNodes) => {
      this.innerNodes = innerNodes;
      for (let innerNodeIndex = 0; innerNodeIndex < this.innerNodes.length; innerNodeIndex++) {
        const innerNode = this.innerNodes[innerNodeIndex];
        innerNode.graph ??= this.node.graph;
        for (const w of innerNode.widgets ?? []) {
          if (w.type === "converted-widget") {
            w.serializeValue = w.origSerializeValue;
          }
        }
        innerNode.index = innerNodeIndex;
        innerNode.getInputNode = (slot) => {
          const externalSlot = this.groupData.oldToNewInputMap[innerNode.index]?.[slot];
          if (externalSlot != null) {
            return this.node.getInputNode(externalSlot);
          }
          const innerLink = this.groupData.linksTo[innerNode.index]?.[slot];
          if (!innerLink) return null;
          const inputNode = innerNodes[innerLink[0]];
          if (inputNode.type === "PrimitiveNode") return null;
          return inputNode;
        };
        innerNode.getInputLink = (slot) => {
          const externalSlot = this.groupData.oldToNewInputMap[innerNode.index]?.[slot];
          if (externalSlot != null) {
            const linkId = this.node.inputs[externalSlot].link;
            let link2 = app.rootGraph.links[linkId];
            link2 = {
              ...link2,
              target_id: innerNode.id,
              target_slot: +slot
            };
            return link2;
          }
          let link = this.groupData.linksTo[innerNode.index]?.[slot];
          if (!link) return null;
          link = {
            origin_id: innerNodes[link[0]].id,
            origin_slot: link[1],
            target_id: innerNode.id,
            target_slot: +slot
          };
          return link;
        };
      }
    };
    this.node.updateLink = (link) => {
      link = { ...link };
      const output = this.groupData.newToOldOutputMap[link.origin_slot];
      let innerNode = this.innerNodes[output.node.index];
      let l;
      while (innerNode?.type === "Reroute") {
        l = innerNode.getInputLink(0);
        innerNode = innerNode.getInputNode(0);
      }
      if (!innerNode) {
        return null;
      }
      if (l && GroupNodeHandler.isGroupNode(innerNode)) {
        return innerNode.updateLink(l);
      }
      link.origin_id = innerNode.id;
      link.origin_slot = l?.origin_slot ?? output.slot;
      return link;
    };
    this.node.getInnerNodes = (computedNodeDtos, subgraphNodePath = [], nodes = [], visited = /* @__PURE__ */ new Set()) => {
      if (visited.has(this.node))
        throw new Error("RecursionError: while flattening subgraph");
      visited.add(this.node);
      if (!this.innerNodes) {
        this.node.setInnerNodes(
          // @ts-expect-error fixme ts strict error
          this.groupData.nodeData.nodes.map((n, i) => {
            const innerNode = LiteGraph.createNode(n.type);
            innerNode.configure(n);
            innerNode.id = `${this.node.id}:${i}`;
            innerNode.graph = this.node.graph;
            return innerNode;
          })
        );
      }
      this.updateInnerWidgets();
      const subgraphInstanceIdPath = [...subgraphNodePath, this.node.id];
      const subgraphNode = this.node.graph?.getNodeById(
        subgraphNodePath.at(-1)
      ) ?? void 0;
      for (const node2 of this.innerNodes) {
        node2.graph ??= this.node.graph;
        const currentId = String(node2.id);
        node2.id = currentId.split(":").at(-1);
        const aVeryRealNode = new ExecutableGroupNodeChildDTO(
          node2,
          subgraphInstanceIdPath,
          computedNodeDtos,
          subgraphNode
        );
        node2.id = currentId;
        aVeryRealNode.groupNodeHandler = this;
        nodes.push(aVeryRealNode);
      }
      return nodes;
    };
    this.node.recreate = async () => {
      const id2 = this.node.id;
      const sz = this.node.size;
      const nodes = this.node.convertToNodes();
      const groupNode = LiteGraph.createNode(this.node.type);
      groupNode.id = id2;
      groupNode.setInnerNodes(nodes);
      groupNode[GROUP].populateWidgets();
      app.rootGraph.add(groupNode);
      groupNode.setSize([
        // @ts-expect-error fixme ts strict error
        Math.max(groupNode.size[0], sz[0]),
        // @ts-expect-error fixme ts strict error
        Math.max(groupNode.size[1], sz[1])
      ]);
      const builder = new GroupNodeBuilder(nodes);
      const nodeData = builder.getNodeData();
      groupNode[GROUP].groupData.nodeData.links = nodeData.links;
      groupNode[GROUP].replaceNodes(nodes);
      return groupNode;
    };
    this.node.convertToNodes = () => {
      const addInnerNodes = /* @__PURE__ */ __name(() => {
        const c = { ...this.groupData.nodeData };
        c.nodes = [...c.nodes];
        const innerNodes = this.node.getInnerNodes();
        let ids = [];
        for (let i = 0; i < c.nodes.length; i++) {
          let id2 = innerNodes?.[i]?.id;
          if (id2 == null || isNaN(id2)) {
            id2 = void 0;
          } else {
            ids.push(id2);
          }
          c.nodes[i] = { ...c.nodes[i], id: id2 };
        }
        deserialiseAndCreate(JSON.stringify(c), app.canvas);
        const [x, y] = this.node.pos;
        let top;
        let left;
        const selectedIds = ids.length ? ids : Object.keys(app.canvas.selected_nodes);
        const newNodes = [];
        for (let i = 0; i < selectedIds.length; i++) {
          const id2 = selectedIds[i];
          const newNode = app.rootGraph.getNodeById(id2);
          const innerNode = innerNodes[i];
          newNodes.push(newNode);
          if (left == null || newNode.pos[0] < left) {
            left = newNode.pos[0];
          }
          if (top == null || newNode.pos[1] < top) {
            top = newNode.pos[1];
          }
          if (!newNode.widgets) continue;
          const map = this.groupData.oldToNewWidgetMap[innerNode.index];
          if (map) {
            const widgets = Object.keys(map);
            for (const oldName of widgets) {
              const newName = map[oldName];
              if (!newName) continue;
              const widgetIndex = this.node.widgets.findIndex(
                (w) => w.name === newName
              );
              if (widgetIndex === -1) continue;
              if (innerNode.type === "PrimitiveNode") {
                for (let i2 = 0; i2 < newNode.widgets.length; i2++) {
                  newNode.widgets[i2].value = // @ts-expect-error fixme ts strict error
                  this.node.widgets[widgetIndex + i2].value;
                }
              } else {
                const outerWidget = this.node.widgets[widgetIndex];
                const newWidget = newNode.widgets.find(
                  (w) => w.name === oldName
                );
                if (!newWidget) continue;
                newWidget.value = outerWidget.value;
                for (let w = 0; w < outerWidget.linkedWidgets?.length; w++) {
                  newWidget.linkedWidgets[w].value = // @ts-expect-error fixme ts strict error
                  outerWidget.linkedWidgets[w].value;
                }
              }
            }
          }
        }
        for (const newNode of newNodes) {
          newNode.pos[0] -= left - x;
          newNode.pos[1] -= top - y;
        }
        return { newNodes, selectedIds };
      }, "addInnerNodes");
      const reconnectInputs = /* @__PURE__ */ __name((selectedIds) => {
        for (const innerNodeIndex in this.groupData.oldToNewInputMap) {
          const id2 = selectedIds[innerNodeIndex];
          const newNode = app.rootGraph.getNodeById(id2);
          const map = this.groupData.oldToNewInputMap[innerNodeIndex];
          for (const innerInputId in map) {
            const groupSlotId = map[innerInputId];
            if (groupSlotId == null) continue;
            const slot = node.inputs[groupSlotId];
            if (slot.link == null) continue;
            const link = app.rootGraph.links[slot.link];
            if (!link) continue;
            const originNode = app.rootGraph.getNodeById(link.origin_id);
            originNode.connect(link.origin_slot, newNode, +innerInputId);
          }
        }
      }, "reconnectInputs");
      const reconnectOutputs = /* @__PURE__ */ __name((selectedIds) => {
        for (let groupOutputId = 0; groupOutputId < node.outputs?.length; groupOutputId++) {
          const output = node.outputs[groupOutputId];
          if (!output.links) continue;
          const links = [...output.links];
          for (const l of links) {
            const slot = this.groupData.newToOldOutputMap[groupOutputId];
            const link = app.rootGraph.links[l];
            const targetNode = app.rootGraph.getNodeById(link.target_id);
            const newNode = app.rootGraph.getNodeById(
              selectedIds[slot.node.index]
            );
            newNode.connect(slot.slot, targetNode, link.target_slot);
          }
        }
      }, "reconnectOutputs");
      app.canvas.emitBeforeChange();
      try {
        const { newNodes, selectedIds } = addInnerNodes();
        reconnectInputs(selectedIds);
        reconnectOutputs(selectedIds);
        app.rootGraph.remove(this.node);
        return newNodes;
      } finally {
        app.canvas.emitAfterChange();
      }
    };
    const getExtraMenuOptions = this.node.getExtraMenuOptions;
    this.node.getExtraMenuOptions = function(_, options) {
      getExtraMenuOptions?.apply(this, arguments);
      let optionIndex = options.findIndex((o) => o?.content === "Outputs");
      if (optionIndex === -1) optionIndex = options.length;
      else optionIndex++;
      options.splice(
        optionIndex,
        0,
        null,
        {
          content: "Convert to nodes",
          // @ts-expect-error
          callback: /* @__PURE__ */ __name(() => {
            return this.convertToNodes();
          }, "callback")
        },
        {
          content: "Manage Group Node",
          callback: /* @__PURE__ */ __name(() => manageGroupNodes(this.type), "callback")
        }
      );
    };
    const onDrawTitleBox = this.node.onDrawTitleBox;
    this.node.onDrawTitleBox = function(ctx, height) {
      onDrawTitleBox?.apply(this, arguments);
      const fill = ctx.fillStyle;
      ctx.beginPath();
      ctx.rect(11, -height + 11, 2, 2);
      ctx.rect(14, -height + 11, 2, 2);
      ctx.rect(17, -height + 11, 2, 2);
      ctx.rect(11, -height + 14, 2, 2);
      ctx.rect(14, -height + 14, 2, 2);
      ctx.rect(17, -height + 14, 2, 2);
      ctx.rect(11, -height + 17, 2, 2);
      ctx.rect(14, -height + 17, 2, 2);
      ctx.rect(17, -height + 17, 2, 2);
      ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
      ctx.fill();
      ctx.fillStyle = fill;
    };
    const onDrawForeground = node.onDrawForeground;
    const groupData = this.groupData.nodeData;
    node.onDrawForeground = function(ctx) {
      onDrawForeground?.apply?.(this, arguments);
      const progressState = useExecutionStore().nodeProgressStates[this.id];
      if (progressState && progressState.state === "running" && this.runningInternalNodeId !== null) {
        const n = groupData.nodes[this.runningInternalNodeId];
        if (!n) return;
        const message = `Running ${n.title || n.type} (${this.runningInternalNodeId}/${groupData.nodes.length})`;
        ctx.save();
        ctx.font = "12px sans-serif";
        const sz = ctx.measureText(message);
        ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
        ctx.beginPath();
        ctx.roundRect(
          0,
          -LiteGraph.NODE_TITLE_HEIGHT - 20,
          sz.width + 12,
          20,
          5
        );
        ctx.fill();
        ctx.fillStyle = "#fff";
        ctx.fillText(message, 6, -LiteGraph.NODE_TITLE_HEIGHT - 6);
        ctx.restore();
      }
    };
    const onExecutionStart = this.node.onExecutionStart;
    this.node.onExecutionStart = function() {
      this.resetExecution = true;
      return onExecutionStart?.apply(this, arguments);
    };
    const self = this;
    const onNodeCreated2 = this.node.onNodeCreated;
    this.node.onNodeCreated = function() {
      if (!this.widgets) {
        return;
      }
      const config = self.groupData.nodeData.config;
      if (config) {
        for (const n in config) {
          const inputs = config[n]?.input;
          for (const w in inputs) {
            if (inputs[w].visible !== false) continue;
            const widgetName = self.groupData.oldToNewWidgetMap[n][w];
            const widget = this.widgets.find((w2) => w2.name === widgetName);
            if (widget) {
              widget.type = "hidden";
              widget.computeSize = () => [0, -4];
            }
          }
        }
      }
      return onNodeCreated2?.apply(this, arguments);
    };
    function handleEvent(type, getId, getEvent) {
      const handler = /* @__PURE__ */ __name(({ detail }) => {
        const id2 = getId(detail);
        if (!id2) return;
        const node2 = app.rootGraph.getNodeById(id2);
        if (node2) return;
        const innerNodeIndex = this.innerNodes?.findIndex((n) => n.id == id2);
        if (innerNodeIndex > -1) {
          this.node.runningInternalNodeId = innerNodeIndex;
          api.dispatchCustomEvent(
            type,
            // @ts-expect-error fixme ts strict error
            getEvent(detail, `${this.node.id}`, this.node)
          );
        }
      }, "handler");
      api.addEventListener(type, handler);
      return handler;
    }
    __name(handleEvent, "handleEvent");
    const executing = handleEvent.call(
      this,
      "executing",
      // @ts-expect-error fixme ts strict error
      (d) => d,
      // @ts-expect-error fixme ts strict error
      (_, id2) => id2
    );
    const executed = handleEvent.call(
      this,
      "executed",
      // @ts-expect-error fixme ts strict error
      (d) => d?.display_node || d?.node,
      // @ts-expect-error fixme ts strict error
      (d, id2, node2) => ({
        ...d,
        node: id2,
        display_node: id2,
        merge: !node2.resetExecution
      })
    );
    const onRemoved = node.onRemoved;
    this.node.onRemoved = function() {
      onRemoved?.apply(this, arguments);
      api.removeEventListener("executing", executing);
      api.removeEventListener("executed", executed);
    };
    this.node.refreshComboInNode = (defs) => {
      for (const widgetName in this.groupData.newToOldWidgetMap) {
        const widget = this.node.widgets.find((w) => w.name === widgetName);
        if (widget?.type === "combo") {
          const old = this.groupData.newToOldWidgetMap[widgetName];
          const def = defs[old.node.type];
          const input = def?.input?.required?.[old.inputName] ?? def?.input?.optional?.[old.inputName];
          if (!input) continue;
          widget.options.values = input[0];
          if (old.inputName !== "image" && // @ts-expect-error Widget values
          !widget.options.values.includes(widget.value)) {
            widget.value = widget.options.values[0];
            widget.callback(widget.value);
          }
        }
      }
    };
  }
  updateInnerWidgets() {
    for (const newWidgetName in this.groupData.newToOldWidgetMap) {
      const newWidget = this.node.widgets.find((w) => w.name === newWidgetName);
      if (!newWidget) continue;
      const newValue = newWidget.value;
      const old = this.groupData.newToOldWidgetMap[newWidgetName];
      let innerNode = this.innerNodes[old.node.index];
      if (innerNode.type === "PrimitiveNode") {
        innerNode.primitiveValue = newValue;
        const primitiveLinked = this.groupData.primitiveToWidget[old.node.index];
        for (const linked of primitiveLinked ?? []) {
          const node = this.innerNodes[linked.nodeId];
          const widget2 = node.widgets.find((w) => w.name === linked.inputName);
          if (widget2) {
            widget2.value = newValue;
          }
        }
        continue;
      } else if (innerNode.type === "Reroute") {
        const rerouteLinks = this.groupData.linksFrom[old.node.index];
        if (rerouteLinks) {
          for (const [_, , targetNodeId, targetSlot] of rerouteLinks["0"]) {
            const node = this.innerNodes[targetNodeId];
            const input = node.inputs[targetSlot];
            if (input.widget) {
              const widget2 = node.widgets?.find(
                // @ts-expect-error fixme ts strict error
                (w) => w.name === input.widget.name
              );
              if (widget2) {
                widget2.value = newValue;
              }
            }
          }
        }
      }
      const widget = innerNode.widgets?.find((w) => w.name === old.inputName);
      if (widget) {
        widget.value = newValue;
      }
    }
  }
  // @ts-expect-error fixme ts strict error
  populatePrimitive(_node, nodeId, oldName) {
    const primitiveId = this.groupData.widgetToPrimitive[nodeId]?.[oldName];
    if (primitiveId == null) return;
    const targetWidgetName = this.groupData.oldToNewWidgetMap[primitiveId]["value"];
    const targetWidgetIndex = this.node.widgets.findIndex(
      (w) => w.name === targetWidgetName
    );
    if (targetWidgetIndex > -1) {
      const primitiveNode = this.innerNodes[primitiveId];
      let len = primitiveNode.widgets.length;
      if (len - 1 !== // @ts-expect-error fixme ts strict error
      this.node.widgets[targetWidgetIndex].linkedWidgets?.length) {
        len = 1;
      }
      for (let i = 0; i < len; i++) {
        this.node.widgets[targetWidgetIndex + i].value = primitiveNode.widgets[i].value;
      }
    }
    return true;
  }
  // @ts-expect-error fixme ts strict error
  populateReroute(node, nodeId, map) {
    if (node.type !== "Reroute") return;
    const link = this.groupData.linksFrom[nodeId]?.[0]?.[0];
    if (!link) return;
    const [, , targetNodeId, targetNodeSlot] = link;
    const targetNode = this.groupData.nodeData.nodes[targetNodeId];
    const inputs = targetNode.inputs;
    const targetWidget = inputs?.[targetNodeSlot]?.widget;
    if (!targetWidget) return;
    const offset = inputs.length - (targetNode.widgets_values?.length ?? 0);
    const v = targetNode.widgets_values?.[targetNodeSlot - offset];
    if (v == null) return;
    const widgetName = Object.values(map)[0];
    const widget = this.node.widgets.find((w) => w.name === widgetName);
    if (widget) {
      widget.value = v;
    }
  }
  populateWidgets() {
    if (!this.node.widgets) return;
    for (let nodeId = 0; nodeId < this.groupData.nodeData.nodes.length; nodeId++) {
      const node = this.groupData.nodeData.nodes[nodeId];
      const map = this.groupData.oldToNewWidgetMap[nodeId] ?? {};
      const widgets = Object.keys(map);
      if (!node.widgets_values?.length) {
        this.populateReroute(node, nodeId, map);
        continue;
      }
      let linkedShift = 0;
      for (let i = 0; i < widgets.length; i++) {
        const oldName = widgets[i];
        const newName = map[oldName];
        const widgetIndex = this.node.widgets.findIndex(
          (w) => w.name === newName
        );
        const mainWidget = this.node.widgets[widgetIndex];
        if (this.populatePrimitive(node, nodeId, oldName) || widgetIndex === -1) {
          const innerWidget = this.innerNodes[nodeId].widgets?.find(
            // @ts-expect-error fixme ts strict error
            (w) => w.name === oldName
          );
          linkedShift += innerWidget?.linkedWidgets?.length ?? 0;
        }
        if (widgetIndex === -1) {
          continue;
        }
        mainWidget.value = node.widgets_values[i + linkedShift];
        for (let w = 0; w < mainWidget.linkedWidgets?.length; w++) {
          this.node.widgets[widgetIndex + w + 1].value = node.widgets_values[i + ++linkedShift];
        }
      }
    }
  }
  // @ts-expect-error fixme ts strict error
  replaceNodes(nodes) {
    let top;
    let left;
    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i];
      if (left == null || node.pos[0] < left) {
        left = node.pos[0];
      }
      if (top == null || node.pos[1] < top) {
        top = node.pos[1];
      }
      this.linkOutputs(node, i);
      app.rootGraph.remove(node);
      node.id = `${this.node.id}:${i}`;
    }
    this.linkInputs();
    this.node.pos = [left, top];
  }
  // @ts-expect-error fixme ts strict error
  linkOutputs(originalNode, nodeId) {
    if (!originalNode.outputs) return;
    for (const output of originalNode.outputs) {
      if (!output.links) continue;
      const links = [...output.links];
      for (const l of links) {
        const link = app.rootGraph.links[l];
        if (!link) continue;
        const targetNode = app.rootGraph.getNodeById(link.target_id);
        const newSlot = this.groupData.oldToNewOutputMap[nodeId]?.[link.origin_slot];
        if (newSlot != null) {
          this.node.connect(newSlot, targetNode, link.target_slot);
        }
      }
    }
  }
  linkInputs() {
    for (const link of this.groupData.nodeData.links ?? []) {
      const [, originSlot, targetId, targetSlot, actualOriginId] = link;
      const originNode = app.rootGraph.getNodeById(actualOriginId);
      if (!originNode) continue;
      originNode.connect(
        originSlot,
        // @ts-expect-error Valid - uses deprecated interface.  Required check: if (graph.getNodeById(this.node.id) !== this.node) report()
        this.node.id,
        this.groupData.oldToNewInputMap[targetId][targetSlot]
      );
    }
  }
  // @ts-expect-error fixme ts strict error
  static getGroupData(node) {
    return (node.nodeData ?? node.constructor?.nodeData)?.[GROUP];
  }
  static isGroupNode(node) {
    return !!node.constructor?.nodeData?.[GROUP];
  }
  static async fromNodes(nodes) {
    const builder = new GroupNodeBuilder(nodes);
    const res = await builder.build();
    if (!res) return;
    const { name, nodeData } = res;
    const config = new GroupNodeConfig(name, nodeData);
    await config.registerType();
    const groupNode = LiteGraph.createNode(`${PREFIX}${SEPARATOR}${name}`);
    groupNode.setInnerNodes(builder.nodes);
    groupNode[GROUP].populateWidgets();
    app.rootGraph.add(groupNode);
    groupNode[GROUP].replaceNodes(builder.nodes);
    return groupNode;
  }
}
const replaceLegacySeparators = /* @__PURE__ */ __name((nodes) => {
  for (const node of nodes) {
    if (typeof node.type === "string" && node.type.startsWith("workflow/")) {
      node.type = node.type.replace(/^workflow\//, `${PREFIX}${SEPARATOR}`);
    }
  }
}, "replaceLegacySeparators");
async function convertSelectedNodesToGroupNode() {
  const nodes = Object.values(app.canvas.selected_nodes ?? {});
  if (nodes.length === 0) {
    throw new Error("No nodes selected");
  }
  if (nodes.length === 1) {
    throw new Error("Please select multiple nodes to convert to group node");
  }
  for (const node of nodes) {
    if (node instanceof SubgraphNode) {
      throw new Error("Selected nodes contain a subgraph node");
    }
    if (GroupNodeHandler.isGroupNode(node)) {
      throw new Error("Selected nodes contain a group node");
    }
  }
  return await GroupNodeHandler.fromNodes(nodes);
}
__name(convertSelectedNodesToGroupNode, "convertSelectedNodesToGroupNode");
const convertDisabled = /* @__PURE__ */ __name((selected) => selected.length < 2 || !!selected.find((n) => GroupNodeHandler.isGroupNode(n)), "convertDisabled");
function ungroupSelectedGroupNodes() {
  const nodes = Object.values(app.canvas.selected_nodes ?? {});
  for (const node of nodes) {
    if (GroupNodeHandler.isGroupNode(node)) {
      node.convertToNodes?.();
    }
  }
}
__name(ungroupSelectedGroupNodes, "ungroupSelectedGroupNodes");
function manageGroupNodes(type) {
  new ManageGroupDialog(app).show(type);
}
__name(manageGroupNodes, "manageGroupNodes");
const id$1 = "Comfy.GroupNode";
let globalDefs;
const ext$3 = {
  name: id$1,
  commands: [
    {
      id: "Comfy.GroupNode.ConvertSelectedNodesToGroupNode",
      label: "Convert selected nodes to group node",
      icon: "pi pi-sitemap",
      versionAdded: "1.3.17",
      function: /* @__PURE__ */ __name(() => convertSelectedNodesToGroupNode(), "function")
    },
    {
      id: "Comfy.GroupNode.UngroupSelectedGroupNodes",
      label: "Ungroup selected group nodes",
      icon: "pi pi-sitemap",
      versionAdded: "1.3.17",
      function: /* @__PURE__ */ __name(() => ungroupSelectedGroupNodes(), "function")
    },
    {
      id: "Comfy.GroupNode.ManageGroupNodes",
      label: "Manage group nodes",
      icon: "pi pi-cog",
      versionAdded: "1.3.17",
      function: /* @__PURE__ */ __name((...args) => manageGroupNodes(args[0]), "function")
    }
  ],
  keybindings: [
    {
      commandId: "Comfy.GroupNode.ConvertSelectedNodesToGroupNode",
      combo: {
        alt: true,
        key: "g"
      }
    },
    {
      commandId: "Comfy.GroupNode.UngroupSelectedGroupNodes",
      combo: {
        alt: true,
        shift: true,
        key: "G"
      }
    }
  ],
  getCanvasMenuItems(canvas) {
    const items = [];
    const selected = Object.values(canvas.selected_nodes ?? {});
    const convertEnabled = !convertDisabled(selected);
    items.push({
      content: `Convert to Group Node (Deprecated)`,
      disabled: !convertEnabled,
      // @ts-expect-error fixme ts strict error - async callback
      callback: /* @__PURE__ */ __name(() => convertSelectedNodesToGroupNode(), "callback")
    });
    const groups = canvas.graph?.extra?.groupNodes;
    const manageDisabled = !groups || !Object.keys(groups).length;
    items.push({
      content: `Manage Group Nodes`,
      disabled: manageDisabled,
      callback: /* @__PURE__ */ __name(() => manageGroupNodes(), "callback")
    });
    return items;
  },
  getNodeMenuItems(node) {
    if (GroupNodeHandler.isGroupNode(node)) {
      return [];
    }
    const selected = Object.values(app.canvas.selected_nodes ?? {});
    const convertEnabled = !convertDisabled(selected);
    return [
      {
        content: `Convert to Group Node (Deprecated)`,
        disabled: !convertEnabled,
        // @ts-expect-error fixme ts strict error - async callback
        callback: /* @__PURE__ */ __name(() => convertSelectedNodesToGroupNode(), "callback")
      }
    ];
  },
  async beforeConfigureGraph(graphData, missingNodeTypes) {
    const nodes = graphData?.extra?.groupNodes;
    if (nodes) {
      replaceLegacySeparators(graphData.nodes);
      await GroupNodeConfig.registerFromWorkflow(nodes, missingNodeTypes);
    }
  },
  addCustomNodeDefs(defs) {
    globalDefs = defs;
  },
  nodeCreated(node) {
    if (GroupNodeHandler.isGroupNode(node)) {
      node[GROUP] = new GroupNodeHandler(node);
      if (node.title && node[GROUP]?.groupData?.nodeData) {
        Workflow.storeGroupNode(node.title, node[GROUP].groupData.nodeData);
      }
    }
  },
  // @ts-expect-error fixme ts strict error
  async refreshComboInNodes(defs) {
    Object.assign(globalDefs, defs);
    const nodes = app.rootGraph.extra?.groupNodes;
    if (nodes) {
      await GroupNodeConfig.registerFromWorkflow(nodes, {});
    }
  }
};
app.registerExtension(ext$3);
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.groupNode = window.comfyAPI.groupNode || {};
window.comfyAPI.groupNode.GroupNodeConfig = GroupNodeConfig;
window.comfyAPI.groupNode.GroupNodeHandler = GroupNodeHandler;
function setNodeMode(node, mode) {
  node.mode = mode;
  node.graph?.change();
}
__name(setNodeMode, "setNodeMode");
function addNodesToGroup(group, items) {
  const padding = useSettingStore().get("Comfy.GroupSelectedNodes.Padding");
  group.resizeTo([...group.children, ...items], padding);
}
__name(addNodesToGroup, "addNodesToGroup");
const ext$2 = {
  name: "Comfy.GroupOptions",
  getCanvasMenuItems(canvas) {
    const items = [];
    const group = canvas.graph.getGroupOnPos(
      canvas.graph_mouse[0],
      canvas.graph_mouse[1]
    );
    if (!group) {
      if (canvas.selectedItems.size > 0) {
        items.push({
          content: "Add Group For Selected Nodes",
          callback: /* @__PURE__ */ __name(() => {
            const group2 = new LGraphGroup();
            addNodesToGroup(group2, canvas.selectedItems);
            canvas.graph.add(group2);
            canvas.graph.change();
            group2.recomputeInsideNodes();
          }, "callback")
        });
      }
      return items;
    }
    group.recomputeInsideNodes();
    const nodesInGroup = group.nodes;
    items.push({
      content: "Add Selected Nodes To Group",
      disabled: !canvas.selectedItems?.size,
      callback: /* @__PURE__ */ __name(() => {
        addNodesToGroup(group, canvas.selectedItems);
        canvas.graph.change();
      }, "callback")
    });
    if (nodesInGroup.length === 0) {
      return items;
    } else {
      items.push(null);
    }
    let allNodesAreSameMode = true;
    for (let i = 1; i < nodesInGroup.length; i++) {
      if (nodesInGroup[i].mode !== nodesInGroup[0].mode) {
        allNodesAreSameMode = false;
        break;
      }
    }
    items.push({
      content: "Fit Group To Nodes",
      callback: /* @__PURE__ */ __name(() => {
        group.recomputeInsideNodes();
        const padding = useSettingStore().get(
          "Comfy.GroupSelectedNodes.Padding"
        );
        group.resizeTo(group.children, padding);
        canvas.graph.change();
      }, "callback")
    });
    items.push({
      content: "Select Nodes",
      callback: /* @__PURE__ */ __name(() => {
        canvas.selectNodes(nodesInGroup);
        canvas.graph.change();
        canvas.canvas.focus();
      }, "callback")
    });
    if (allNodesAreSameMode) {
      const mode = nodesInGroup[0].mode;
      switch (mode) {
        case 0:
          items.push({
            content: "Set Group Nodes to Never",
            callback: /* @__PURE__ */ __name(() => {
              for (const node of nodesInGroup) {
                setNodeMode(node, 2);
              }
            }, "callback")
          });
          items.push({
            content: "Bypass Group Nodes",
            callback: /* @__PURE__ */ __name(() => {
              for (const node of nodesInGroup) {
                setNodeMode(node, 4);
              }
            }, "callback")
          });
          break;
        case 2:
          items.push({
            content: "Set Group Nodes to Always",
            callback: /* @__PURE__ */ __name(() => {
              for (const node of nodesInGroup) {
                setNodeMode(node, 0);
              }
            }, "callback")
          });
          items.push({
            content: "Bypass Group Nodes",
            callback: /* @__PURE__ */ __name(() => {
              for (const node of nodesInGroup) {
                setNodeMode(node, 4);
              }
            }, "callback")
          });
          break;
        case 4:
          items.push({
            content: "Set Group Nodes to Always",
            callback: /* @__PURE__ */ __name(() => {
              for (const node of nodesInGroup) {
                setNodeMode(node, 0);
              }
            }, "callback")
          });
          items.push({
            content: "Set Group Nodes to Never",
            callback: /* @__PURE__ */ __name(() => {
              for (const node of nodesInGroup) {
                setNodeMode(node, 2);
              }
            }, "callback")
          });
          break;
        default:
          items.push({
            content: "Set Group Nodes to Always",
            callback: /* @__PURE__ */ __name(() => {
              for (const node of nodesInGroup) {
                setNodeMode(node, 0);
              }
            }, "callback")
          });
          items.push({
            content: "Set Group Nodes to Never",
            callback: /* @__PURE__ */ __name(() => {
              for (const node of nodesInGroup) {
                setNodeMode(node, 2);
              }
            }, "callback")
          });
          items.push({
            content: "Bypass Group Nodes",
            callback: /* @__PURE__ */ __name(() => {
              for (const node of nodesInGroup) {
                setNodeMode(node, 4);
              }
            }, "callback")
          });
          break;
      }
    } else {
      items.push({
        content: "Set Group Nodes to Always",
        callback: /* @__PURE__ */ __name(() => {
          for (const node of nodesInGroup) {
            setNodeMode(node, 0);
          }
        }, "callback")
      });
      items.push({
        content: "Set Group Nodes to Never",
        callback: /* @__PURE__ */ __name(() => {
          for (const node of nodesInGroup) {
            setNodeMode(node, 2);
          }
        }, "callback")
      });
      items.push({
        content: "Bypass Group Nodes",
        callback: /* @__PURE__ */ __name(() => {
          for (const node of nodesInGroup) {
            setNodeMode(node, 4);
          }
        }, "callback")
      });
    }
    return items;
  }
};
app.registerExtension(ext$2);
useExtensionService().registerExtension({
  name: "Comfy.ImageCompare",
  async nodeCreated(node) {
    if (node.constructor.comfyClass !== "ImageCompare") return;
    const [oldWidth, oldHeight] = node.size;
    node.setSize([Math.max(oldWidth, 400), Math.max(oldHeight, 350)]);
    const onExecuted = node.onExecuted;
    node.onExecuted = function(output) {
      onExecuted?.call(this, output);
      const aImages = output.a_images;
      const bImages = output.b_images;
      const rand = app.getRandParam();
      const beforeUrl = aImages && aImages.length > 0 ? api.apiURL(`/view?${new URLSearchParams(aImages[0])}${rand}`) : "";
      const afterUrl = bImages && bImages.length > 0 ? api.apiURL(`/view?${new URLSearchParams(bImages[0])}${rand}`) : "";
      const widget = node.widgets?.find((w) => w.type === "imagecompare");
      if (widget) {
        widget.value = {
          before: beforeUrl,
          after: afterUrl
        };
        widget.callback?.(widget.value);
      }
    };
  }
});
const EXPORT_FORMATS = [
  { label: "GLB", value: "glb" },
  { label: "OBJ", value: "obj" },
  { label: "STL", value: "stl" }
];
function createExportMenuItems(load3d) {
  return [
    null,
    // Separator
    {
      content: "Save",
      has_submenu: true,
      callback: /* @__PURE__ */ __name((_value, _options, event, prev_menu) => {
        const submenuOptions = EXPORT_FORMATS.map(
          (format) => ({
            content: format.label,
            callback: /* @__PURE__ */ __name(() => {
              void (async () => {
                try {
                  await load3d.exportModel(format.value);
                  useToastStore().add({
                    severity: "success",
                    summary: t("toastMessages.exportSuccess", {
                      format: format.label
                    })
                  });
                } catch (error) {
                  console.error("Export failed:", error);
                  useToastStore().addAlert(
                    t("toastMessages.failedToExportModel", {
                      format: format.label
                    })
                  );
                }
              })();
            }, "callback")
          })
        );
        new LiteGraph.ContextMenu(submenuOptions, {
          event,
          parentMenu: prev_menu
        });
      }, "callback")
    }
  ];
}
__name(createExportMenuItems, "createExportMenuItems");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.exportMenuHelper = window.comfyAPI.exportMenuHelper || {};
window.comfyAPI.exportMenuHelper.createExportMenuItems = createExportMenuItems;
class Load3DConfiguration {
  static {
    __name(this, "Load3DConfiguration");
  }
  constructor(load3d, properties) {
    this.load3d = load3d;
    this.properties = properties;
  }
  configureForSaveMesh(loadFolder, filePath) {
    this.setupModelHandlingForSaveMesh(filePath, loadFolder);
    this.setupDefaultProperties();
  }
  configure(setting) {
    this.setupModelHandling(
      setting.modelWidget,
      setting.loadFolder,
      setting.cameraState
    );
    this.setupTargetSize(setting.width, setting.height);
    this.setupDefaultProperties(setting.bgImagePath);
  }
  setupTargetSize(width, height) {
    if (width && height) {
      this.load3d.setTargetSize(width.value, height.value);
      width.callback = (value) => {
        this.load3d.setTargetSize(value, height.value);
      };
      height.callback = (value) => {
        this.load3d.setTargetSize(width.value, value);
      };
    }
  }
  setupModelHandlingForSaveMesh(filePath, loadFolder) {
    const onModelWidgetUpdate = this.createModelUpdateHandler(loadFolder);
    if (filePath) {
      onModelWidgetUpdate(filePath);
    }
  }
  setupModelHandling(modelWidget, loadFolder, cameraState) {
    const onModelWidgetUpdate = this.createModelUpdateHandler(
      loadFolder,
      cameraState
    );
    if (modelWidget.value) {
      onModelWidgetUpdate(modelWidget.value);
    }
    const originalCallback = modelWidget.callback;
    let currentValue = modelWidget.value;
    Object.defineProperty(modelWidget, "value", {
      get() {
        return currentValue;
      },
      set(newValue) {
        currentValue = newValue;
        if (modelWidget.callback && newValue !== void 0 && newValue !== "") {
          modelWidget.callback(newValue);
        }
      },
      enumerable: true,
      configurable: true
    });
    modelWidget.callback = (value) => {
      onModelWidgetUpdate(value);
      if (originalCallback) {
        originalCallback(value);
      }
    };
  }
  setupDefaultProperties(bgImagePath) {
    const sceneConfig = this.loadSceneConfig();
    this.applySceneConfig(sceneConfig, bgImagePath);
    const cameraConfig = this.loadCameraConfig();
    this.applyCameraConfig(cameraConfig);
    const lightConfig = this.loadLightConfig();
    this.applyLightConfig(lightConfig);
  }
  loadSceneConfig() {
    if (this.properties && "Scene Config" in this.properties) {
      return this.properties["Scene Config"];
    }
    return {
      showGrid: useSettingStore().get("Comfy.Load3D.ShowGrid"),
      backgroundColor: "#" + useSettingStore().get("Comfy.Load3D.BackgroundColor"),
      backgroundImage: ""
    };
  }
  loadCameraConfig() {
    if (this.properties && "Camera Config" in this.properties) {
      return this.properties["Camera Config"];
    }
    return {
      cameraType: useSettingStore().get("Comfy.Load3D.CameraType"),
      fov: 35
    };
  }
  loadLightConfig() {
    if (this.properties && "Light Config" in this.properties) {
      return this.properties["Light Config"];
    }
    return {
      intensity: useSettingStore().get("Comfy.Load3D.LightIntensity")
    };
  }
  loadModelConfig() {
    if (this.properties && "Model Config" in this.properties) {
      return this.properties["Model Config"];
    }
    return {
      upDirection: "original",
      materialMode: "original"
    };
  }
  applySceneConfig(config, bgImagePath) {
    this.load3d.toggleGrid(config.showGrid);
    this.load3d.setBackgroundColor(config.backgroundColor);
    if (config.backgroundImage) {
      if (bgImagePath && bgImagePath != config.backgroundImage) {
        return;
      }
      void this.load3d.setBackgroundImage(config.backgroundImage);
      if (config.backgroundRenderMode) {
        this.load3d.setBackgroundRenderMode(config.backgroundRenderMode);
      }
    }
  }
  applyCameraConfig(config) {
    this.load3d.toggleCamera(config.cameraType);
    this.load3d.setFOV(config.fov);
    if (config.state) {
      this.load3d.setCameraState(config.state);
    }
  }
  applyLightConfig(config) {
    this.load3d.setLightIntensity(config.intensity);
  }
  applyModelConfig(config) {
    this.load3d.setUpDirection(config.upDirection);
    this.load3d.setMaterialMode(config.materialMode);
  }
  createModelUpdateHandler(loadFolder, cameraState) {
    let isFirstLoad = true;
    return async (value) => {
      if (!value) return;
      const filename = value;
      this.setResourceFolder(filename);
      const modelUrl = api.apiURL(
        Load3dUtils.getResourceURL(
          ...Load3dUtils.splitFilePath(filename),
          loadFolder
        )
      );
      await this.load3d.loadModel(modelUrl, filename);
      const modelConfig = this.loadModelConfig();
      this.applyModelConfig(modelConfig);
      if (isFirstLoad && cameraState) {
        try {
          this.load3d.setCameraState(cameraState);
        } catch (error) {
          console.warn("Failed to restore camera state:", error);
        }
        isFirstLoad = false;
      }
    };
  }
  setResourceFolder(filename) {
    const pathParts = filename.split("/").filter((part) => part.trim());
    if (pathParts.length <= 2) {
      return;
    }
    const subfolderParts = pathParts.slice(1, -1);
    const subfolder = subfolderParts.join("/");
    if (subfolder && this.properties) {
      this.properties["Resource Folder"] = subfolder;
    }
  }
}
const inputSpecLoad3D = {
  name: "image",
  type: "Load3D",
  isPreview: false
};
const inputSpecPreview3D = {
  name: "image",
  type: "Preview3D",
  isPreview: true
};
async function handleModelUpload(files, node) {
  if (!files?.length) return;
  const modelWidget = node.widgets?.find(
    (w) => w.name === "model_file"
  );
  try {
    const resourceFolder = node.properties["Resource Folder"] || "";
    const subfolder = resourceFolder.trim() ? `3d/${resourceFolder.trim()}` : "3d";
    const uploadPath = await Load3dUtils.uploadFile(files[0], subfolder);
    if (!uploadPath) {
      useToastStore().addAlert(t("toastMessages.fileUploadFailed"));
      return;
    }
    const modelUrl = api.apiURL(
      Load3dUtils.getResourceURL(
        ...Load3dUtils.splitFilePath(uploadPath),
        "input"
      )
    );
    useLoad3d(node).waitForLoad3d((load3d) => {
      try {
        load3d.loadModel(modelUrl);
      } catch (error) {
        useToastStore().addAlert(t("toastMessages.failedToLoadModel"));
      }
    });
    if (uploadPath && modelWidget) {
      if (!modelWidget.options?.values?.includes(uploadPath)) {
        modelWidget.options?.values?.push(uploadPath);
      }
      modelWidget.value = uploadPath;
    }
  } catch (error) {
    console.error("Model upload failed:", error);
    useToastStore().addAlert(t("toastMessages.fileUploadFailed"));
  }
}
__name(handleModelUpload, "handleModelUpload");
async function handleResourcesUpload(files, node) {
  if (!files?.length) return;
  try {
    const resourceFolder = node.properties["Resource Folder"] || "";
    const subfolder = resourceFolder.trim() ? `3d/${resourceFolder.trim()}` : "3d";
    await Load3dUtils.uploadMultipleFiles(files, subfolder);
  } catch (error) {
    console.error("Extra resources upload failed:", error);
    useToastStore().addAlert(t("toastMessages.extraResourcesUploadFailed"));
  }
}
__name(handleResourcesUpload, "handleResourcesUpload");
function createFileInput(accept, multiple = false) {
  const input = document.createElement("input");
  input.type = "file";
  input.accept = accept;
  input.multiple = multiple;
  input.style.display = "none";
  return input;
}
__name(createFileInput, "createFileInput");
useExtensionService().registerExtension({
  name: "Comfy.Load3D",
  settings: [
    {
      id: "Comfy.Load3D.ShowGrid",
      category: ["3D", "Scene", "Initial Grid Visibility"],
      name: "Initial Grid Visibility",
      tooltip: "Controls whether the grid is visible by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation.",
      type: "boolean",
      defaultValue: true,
      experimental: true
    },
    {
      id: "Comfy.Load3D.BackgroundColor",
      category: ["3D", "Scene", "Initial Background Color"],
      name: "Initial Background Color",
      tooltip: "Controls the default background color of the 3D scene. This setting determines the background appearance when a new 3D widget is created, but can be adjusted individually for each widget after creation.",
      type: "color",
      defaultValue: "282828",
      experimental: true
    },
    {
      id: "Comfy.Load3D.CameraType",
      category: ["3D", "Camera", "Initial Camera Type"],
      name: "Initial Camera Type",
      tooltip: "Controls whether the camera is perspective or orthographic by default when a new 3D widget is created. This default can still be toggled individually for each widget after creation.",
      type: "combo",
      options: ["perspective", "orthographic"],
      defaultValue: "perspective",
      experimental: true
    },
    {
      id: "Comfy.Load3D.LightIntensity",
      category: ["3D", "Light", "Initial Light Intensity"],
      name: "Initial Light Intensity",
      tooltip: "Sets the default brightness level of lighting in the 3D scene. This value determines how intensely lights illuminate objects when a new 3D widget is created, but can be adjusted individually for each widget after creation.",
      type: "number",
      defaultValue: 3,
      experimental: true
    },
    {
      id: "Comfy.Load3D.LightIntensityMaximum",
      category: ["3D", "Light", "Light Intensity Maximum"],
      name: "Light Intensity Maximum",
      tooltip: "Sets the maximum allowable light intensity value for 3D scenes. This defines the upper brightness limit that can be set when adjusting lighting in any 3D widget.",
      type: "number",
      defaultValue: 10,
      experimental: true
    },
    {
      id: "Comfy.Load3D.LightIntensityMinimum",
      category: ["3D", "Light", "Light Intensity Minimum"],
      name: "Light Intensity Minimum",
      tooltip: "Sets the minimum allowable light intensity value for 3D scenes. This defines the lower brightness limit that can be set when adjusting lighting in any 3D widget.",
      type: "number",
      defaultValue: 1,
      experimental: true
    },
    {
      id: "Comfy.Load3D.LightAdjustmentIncrement",
      category: ["3D", "Light", "Light Adjustment Increment"],
      name: "Light Adjustment Increment",
      tooltip: "Controls the increment size when adjusting light intensity in 3D scenes. A smaller step value allows for finer control over lighting adjustments, while a larger value results in more noticeable changes per adjustment.",
      type: "slider",
      attrs: {
        min: 0.1,
        max: 1,
        step: 0.1
      },
      defaultValue: 0.5,
      experimental: true
    },
    {
      id: "Comfy.Load3D.3DViewerEnable",
      category: ["3D", "3DViewer", "Enable"],
      name: "Enable 3D Viewer (Beta)",
      tooltip: "Enables the 3D Viewer (Beta) for selected nodes. This feature allows you to visualize and interact with 3D models directly within the full size 3d viewer.",
      type: "boolean",
      defaultValue: false,
      experimental: true
    },
    {
      id: "Comfy.Load3D.PLYEngine",
      category: ["3D", "PLY", "PLY Engine"],
      name: "PLY Engine",
      tooltip: 'Select the engine for loading PLY files. "threejs" uses the native Three.js PLYLoader (best for mesh PLY files). "fastply" uses an optimized loader for ASCII point cloud PLY files. "sparkjs" uses Spark.js for 3D Gaussian Splatting PLY files.',
      type: "combo",
      options: ["threejs", "fastply", "sparkjs"],
      defaultValue: "threejs",
      experimental: true
    }
  ],
  commands: [
    {
      id: "Comfy.3DViewer.Open3DViewer",
      icon: "pi pi-pencil",
      label: "Open 3D Viewer (Beta) for Selected Node",
      function: /* @__PURE__ */ __name(() => {
        const selectedNodes = app.canvas.selected_nodes;
        if (!selectedNodes || Object.keys(selectedNodes).length !== 1) return;
        const selectedNode = selectedNodes[Object.keys(selectedNodes)[0]];
        if (!isLoad3dNode(selectedNode)) return;
        ComfyApp.copyToClipspace(selectedNode);
        ComfyApp.clipspace_return_node = selectedNode;
        const props = { node: selectedNode };
        useDialogStore().showDialog({
          key: "global-load3d-viewer",
          title: t("load3d.viewer.title"),
          component: Load3DViewerContent,
          props,
          dialogComponentProps: {
            style: "width: 80vw; height: 80vh;",
            maximizable: true,
            onClose: /* @__PURE__ */ __name(async () => {
              await useLoad3dService().handleViewerClose(props.node);
            }, "onClose")
          }
        });
      }, "function")
    }
  ],
  getCustomWidgets() {
    return {
      LOAD_3D(node) {
        const fileInput = createFileInput(
          ".gltf,.glb,.obj,.fbx,.stl,.ply,.spz,.splat,.ksplat",
          false
        );
        node.properties["Resource Folder"] = "";
        fileInput.onchange = async () => {
          await handleModelUpload(fileInput.files, node);
        };
        node.addWidget("button", "upload 3d model", "upload3dmodel", () => {
          fileInput.click();
        });
        const resourcesInput = createFileInput("*", true);
        resourcesInput.onchange = async () => {
          await handleResourcesUpload(resourcesInput.files, node);
          resourcesInput.value = "";
        };
        node.addWidget(
          "button",
          "upload extra resources",
          "uploadExtraResources",
          () => {
            resourcesInput.click();
          }
        );
        node.addWidget("button", "clear", "clear", () => {
          useLoad3d(node).waitForLoad3d((load3d) => {
            load3d.clearModel();
          });
          const modelWidget = node.widgets?.find((w) => w.name === "model_file");
          if (modelWidget) {
            modelWidget.value = "";
          }
        });
        const widget = new ComponentWidgetImpl({
          node,
          name: "image",
          component: _sfc_main,
          inputSpec: inputSpecLoad3D,
          options: {}
        });
        widget.type = "load3D";
        addWidget(node, widget);
        return { widget };
      }
    };
  },
  getNodeMenuItems(node) {
    if (node.constructor.comfyClass !== "Load3D") return [];
    const load3d = useLoad3dService().getLoad3d(node);
    if (!load3d) return [];
    if (load3d.isSplatModel()) return [];
    return createExportMenuItems(load3d);
  },
  async nodeCreated(node) {
    if (node.constructor.comfyClass !== "Load3D") return;
    const [oldWidth, oldHeight] = node.size;
    node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 600)]);
    await nextTick();
    useLoad3d(node).waitForLoad3d((load3d) => {
      const cameraConfig = node.properties["Camera Config"];
      const cameraState = cameraConfig?.state;
      const config = new Load3DConfiguration(load3d, node.properties);
      const modelWidget = node.widgets?.find((w) => w.name === "model_file");
      const width = node.widgets?.find((w) => w.name === "width");
      const height = node.widgets?.find((w) => w.name === "height");
      const sceneWidget = node.widgets?.find((w) => w.name === "image");
      if (modelWidget && width && height && sceneWidget) {
        const settings = {
          loadFolder: "input",
          modelWidget,
          cameraState,
          width,
          height
        };
        config.configure(settings);
        sceneWidget.serializeValue = async () => {
          const currentLoad3d = nodeToLoad3dMap.get(node);
          if (!currentLoad3d) {
            console.error("No load3d instance found for node");
            return null;
          }
          const cameraConfig2 = node.properties["Camera Config"] || {
            cameraType: currentLoad3d.getCurrentCameraType(),
            fov: currentLoad3d.cameraManager.perspectiveCamera.fov
          };
          cameraConfig2.state = currentLoad3d.getCameraState();
          node.properties["Camera Config"] = cameraConfig2;
          currentLoad3d.stopRecording();
          const {
            scene: imageData,
            mask: maskData,
            normal: normalData
          } = await currentLoad3d.captureScene(
            width.value,
            height.value
          );
          const [data, dataMask, dataNormal] = await Promise.all([
            Load3dUtils.uploadTempImage(imageData, "scene"),
            Load3dUtils.uploadTempImage(maskData, "scene_mask"),
            Load3dUtils.uploadTempImage(normalData, "scene_normal")
          ]);
          currentLoad3d.handleResize();
          const returnVal = {
            image: `threed/${data.name} [temp]`,
            mask: `threed/${dataMask.name} [temp]`,
            normal: `threed/${dataNormal.name} [temp]`,
            camera_info: node.properties["Camera Config"]?.state || null,
            recording: ""
          };
          const recordingData = currentLoad3d.getRecordingData();
          if (recordingData) {
            const [recording] = await Promise.all([
              Load3dUtils.uploadTempImage(recordingData, "recording", "mp4")
            ]);
            returnVal["recording"] = `threed/${recording.name} [temp]`;
          }
          return returnVal;
        };
      }
    });
  }
});
useExtensionService().registerExtension({
  name: "Comfy.Preview3D",
  async beforeRegisterNodeDef(_nodeType, nodeData) {
    if ("Preview3D" === nodeData.name) {
      nodeData.input.required.image = ["PREVIEW_3D"];
    }
  },
  getNodeMenuItems(node) {
    if (node.constructor.comfyClass !== "Preview3D") return [];
    const load3d = useLoad3dService().getLoad3d(node);
    if (!load3d) return [];
    if (load3d.isSplatModel()) return [];
    return createExportMenuItems(load3d);
  },
  getCustomWidgets() {
    return {
      PREVIEW_3D(node) {
        const widget = new ComponentWidgetImpl({
          node,
          name: inputSpecPreview3D.name,
          component: _sfc_main,
          inputSpec: inputSpecPreview3D,
          options: {}
        });
        widget.type = "load3D";
        addWidget(node, widget);
        return { widget };
      }
    };
  },
  async nodeCreated(node) {
    if (node.constructor.comfyClass !== "Preview3D") return;
    const [oldWidth, oldHeight] = node.size;
    node.setSize([Math.max(oldWidth, 400), Math.max(oldHeight, 550)]);
    await nextTick();
    const onExecuted = node.onExecuted;
    useLoad3d(node).waitForLoad3d((load3d) => {
      const config = new Load3DConfiguration(load3d, node.properties);
      const modelWidget = node.widgets?.find((w) => w.name === "model_file");
      if (modelWidget) {
        const lastTimeModelFile = node.properties["Last Time Model File"];
        if (lastTimeModelFile) {
          modelWidget.value = lastTimeModelFile;
          const cameraConfig = node.properties["Camera Config"];
          const cameraState = cameraConfig?.state;
          const settings = {
            loadFolder: "output",
            modelWidget,
            cameraState
          };
          config.configure(settings);
        }
        node.onExecuted = function(message) {
          onExecuted?.apply(this, arguments);
          let filePath = message.result[0];
          if (!filePath) {
            const msg = t("toastMessages.unableToGetModelFilePath");
            console.error(msg);
            useToastStore().addAlert(msg);
          }
          let cameraState = message.result[1];
          let bgImagePath = message.result[2];
          modelWidget.value = filePath.replaceAll("\\", "/");
          node.properties["Last Time Model File"] = modelWidget.value;
          const settings = {
            loadFolder: "output",
            modelWidget,
            cameraState,
            bgImagePath
          };
          config.configure(settings);
          if (bgImagePath) {
            load3d.setBackgroundImage(bgImagePath);
          }
        };
      }
    });
  }
});
function openMaskEditor(node) {
  if (!node) {
    console.error("[MaskEditor] No node provided");
    return;
  }
  if (!node.imgs?.length && node.previewMediaType !== "image") {
    console.error("[MaskEditor] Node has no images");
    return;
  }
  useMaskEditor().openMaskEditor(node);
}
__name(openMaskEditor, "openMaskEditor");
function openMaskEditorFromClipspace() {
  const node = ComfyApp.clipspace_return_node;
  if (!node) {
    console.error("[MaskEditor] No clipspace_return_node found");
    return;
  }
  openMaskEditor(node);
}
__name(openMaskEditorFromClipspace, "openMaskEditorFromClipspace");
function isOpened() {
  return useDialogStore().isDialogOpen("global-mask-editor");
}
__name(isOpened, "isOpened");
app.registerExtension({
  name: "Comfy.MaskEditor",
  settings: [
    {
      id: "Comfy.MaskEditor.BrushAdjustmentSpeed",
      category: ["Mask Editor", "BrushAdjustment", "Sensitivity"],
      name: "Brush adjustment speed multiplier",
      tooltip: "Controls how quickly the brush size and hardness change when adjusting. Higher values mean faster changes.",
      type: "slider",
      attrs: {
        min: 0.1,
        max: 2,
        step: 0.1
      },
      defaultValue: 1,
      versionAdded: "1.0.0"
    },
    {
      id: "Comfy.MaskEditor.UseDominantAxis",
      category: ["Mask Editor", "BrushAdjustment", "UseDominantAxis"],
      name: "Lock brush adjustment to dominant axis",
      tooltip: "When enabled, brush adjustments will only affect size OR hardness based on which direction you move more",
      type: "boolean",
      defaultValue: true
    }
  ],
  commands: [
    {
      id: "Comfy.MaskEditor.OpenMaskEditor",
      icon: "pi pi-pencil",
      label: "Open Mask Editor for Selected Node",
      function: /* @__PURE__ */ __name(() => {
        const selectedNodes = app.canvas.selected_nodes;
        if (!selectedNodes || Object.keys(selectedNodes).length !== 1) return;
        const selectedNode = selectedNodes[Object.keys(selectedNodes)[0]];
        openMaskEditor(selectedNode);
      }, "function")
    },
    {
      id: "Comfy.MaskEditor.BrushSize.Increase",
      icon: "pi pi-plus-circle",
      label: "Increase Brush Size in MaskEditor",
      function: /* @__PURE__ */ __name(() => changeBrushSize((old) => toolkit.clamp(old + 4, 1, 100)), "function")
    },
    {
      id: "Comfy.MaskEditor.BrushSize.Decrease",
      icon: "pi pi-minus-circle",
      label: "Decrease Brush Size in MaskEditor",
      function: /* @__PURE__ */ __name(() => changeBrushSize((old) => toolkit.clamp(old - 4, 1, 100)), "function")
    }
  ],
  init() {
    ComfyApp.open_maskeditor = openMaskEditorFromClipspace;
    console.warn(
      "[MaskEditor] ComfyApp.open_maskeditor is deprecated. Plugins should migrate to using the command system or direct node context menu integration."
    );
  }
});
const changeBrushSize = /* @__PURE__ */ __name(async (sizeChanger) => {
  if (!isOpened()) return;
  const store = useMaskEditorStore();
  const oldBrushSize = store.brushSettings.size;
  const newBrushSize = sizeChanger(oldBrushSize);
  store.setBrushSize(newBrushSize);
}, "changeBrushSize");
const id = "Comfy.NodeTemplates";
const file = "comfy.templates.json";
class ManageTemplates extends ComfyDialog {
  static {
    __name(this, "ManageTemplates");
  }
  // @ts-expect-error fixme ts strict error
  templates;
  draggedEl;
  saveVisualCue;
  emptyImg;
  importInput;
  constructor() {
    super();
    this.load().then((v) => {
      this.templates = v;
    });
    this.element.classList.add("comfy-manage-templates");
    this.draggedEl = null;
    this.saveVisualCue = null;
    this.emptyImg = new Image();
    this.emptyImg.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
    this.importInput = $el("input", {
      type: "file",
      accept: ".json",
      multiple: true,
      style: { display: "none" },
      parent: document.body,
      onchange: /* @__PURE__ */ __name(() => this.importAll(), "onchange")
    });
  }
  createButtons() {
    const btns = super.createButtons();
    btns[0].textContent = "Close";
    btns[0].onclick = () => {
      clearTimeout(this.saveVisualCue);
      this.close();
    };
    btns.unshift(
      $el("button", {
        type: "button",
        textContent: "Export",
        onclick: /* @__PURE__ */ __name(() => this.exportAll(), "onclick")
      })
    );
    btns.unshift(
      $el("button", {
        type: "button",
        textContent: "Import",
        onclick: /* @__PURE__ */ __name(() => {
          this.importInput.click();
        }, "onclick")
      })
    );
    return btns;
  }
  async load() {
    let templates = [];
    const res = await api.getUserData(file);
    if (res.status === 200) {
      try {
        templates = await res.json();
      } catch (error) {
      }
    } else if (res.status !== 404) {
      console.error(res.status + " " + res.statusText);
    }
    return templates ?? [];
  }
  async store() {
    const templates = JSON.stringify(this.templates, void 0, 4);
    try {
      await api.storeUserData(file, templates, { stringify: false });
    } catch (error) {
      console.error(error);
      useToastStore().addAlert(error.message);
    }
  }
  async importAll() {
    for (const file2 of this.importInput.files) {
      if (file2.type === "application/json" || file2.name.endsWith(".json")) {
        const reader = new FileReader();
        reader.onload = async () => {
          const importFile = JSON.parse(reader.result);
          if (importFile?.templates) {
            for (const template of importFile.templates) {
              if (template?.name && template?.data) {
                this.templates.push(template);
              }
            }
            await this.store();
          }
        };
        await reader.readAsText(file2);
      }
    }
    this.importInput.value = null;
    this.close();
  }
  exportAll() {
    if (this.templates.length == 0) {
      useToastStore().addAlert(t("toastMessages.noTemplatesToExport"));
      return;
    }
    const json = JSON.stringify({ templates: this.templates }, null, 2);
    const blob = new Blob([json], { type: "application/json" });
    downloadBlob("node_templates.json", blob);
  }
  show() {
    super.show(
      $el(
        "div",
        {},
        this.templates.flatMap((t2, i) => {
          let nameInput;
          return [
            $el(
              "div",
              {
                dataset: { id: i.toString() },
                className: "templateManagerRow",
                style: {
                  display: "grid",
                  gridTemplateColumns: "1fr auto",
                  border: "1px dashed transparent",
                  gap: "5px",
                  backgroundColor: "var(--comfy-menu-bg)"
                },
                // @ts-expect-error fixme ts strict error
                ondragstart: /* @__PURE__ */ __name((e) => {
                  this.draggedEl = e.currentTarget;
                  e.currentTarget.style.opacity = "0.6";
                  e.currentTarget.style.border = "1px dashed yellow";
                  e.dataTransfer.effectAllowed = "move";
                  e.dataTransfer.setDragImage(this.emptyImg, 0, 0);
                }, "ondragstart"),
                // @ts-expect-error fixme ts strict error
                ondragend: /* @__PURE__ */ __name((e) => {
                  e.target.style.opacity = "1";
                  e.currentTarget.style.border = "1px dashed transparent";
                  e.currentTarget.removeAttribute("draggable");
                  this.element.querySelectorAll(".templateManagerRow").forEach((el, i2) => {
                    var prev_i = Number.parseInt(el.dataset.id);
                    if (el == this.draggedEl && prev_i != i2) {
                      this.templates.splice(
                        i2,
                        0,
                        this.templates.splice(prev_i, 1)[0]
                      );
                    }
                    el.dataset.id = i2.toString();
                  });
                  this.store();
                }, "ondragend"),
                // @ts-expect-error fixme ts strict error
                ondragover: /* @__PURE__ */ __name((e) => {
                  e.preventDefault();
                  if (e.currentTarget == this.draggedEl) return;
                  let rect = e.currentTarget.getBoundingClientRect();
                  if (e.clientY > rect.top + rect.height / 2) {
                    e.currentTarget.parentNode.insertBefore(
                      this.draggedEl,
                      e.currentTarget.nextSibling
                    );
                  } else {
                    e.currentTarget.parentNode.insertBefore(
                      this.draggedEl,
                      e.currentTarget
                    );
                  }
                }, "ondragover")
              },
              [
                $el(
                  "label",
                  {
                    textContent: "Name: ",
                    style: {
                      cursor: "grab"
                    },
                    // @ts-expect-error fixme ts strict error
                    onmousedown: /* @__PURE__ */ __name((e) => {
                      if (e.target.localName == "label")
                        e.currentTarget.parentNode.draggable = "true";
                    }, "onmousedown")
                  },
                  [
                    $el("input", {
                      value: t2.name,
                      dataset: { name: t2.name },
                      style: {
                        transitionProperty: "background-color",
                        transitionDuration: "0s"
                      },
                      // @ts-expect-error fixme ts strict error
                      onchange: /* @__PURE__ */ __name((e) => {
                        clearTimeout(this.saveVisualCue);
                        var el = e.target;
                        var row = el.parentNode.parentNode;
                        this.templates[row.dataset.id].name = el.value.trim() || "untitled";
                        this.store();
                        el.style.backgroundColor = "rgb(40, 95, 40)";
                        el.style.transitionDuration = "0s";
                        this.saveVisualCue = setTimeout(function() {
                          el.style.transitionDuration = ".7s";
                          el.style.backgroundColor = "var(--comfy-input-bg)";
                        }, 15);
                      }, "onchange"),
                      // @ts-expect-error fixme ts strict error
                      onkeypress: /* @__PURE__ */ __name((e) => {
                        var el = e.target;
                        clearTimeout(this.saveVisualCue);
                        el.style.transitionDuration = "0s";
                        el.style.backgroundColor = "var(--comfy-input-bg)";
                      }, "onkeypress"),
                      $: /* @__PURE__ */ __name((el) => nameInput = el, "$")
                    })
                  ]
                ),
                $el("div", {}, [
                  $el("button", {
                    textContent: "Export",
                    style: {
                      fontSize: "12px",
                      fontWeight: "normal"
                    },
                    onclick: /* @__PURE__ */ __name(() => {
                      const json = JSON.stringify({ templates: [t2] }, null, 2);
                      const blob = new Blob([json], {
                        type: "application/json"
                      });
                      const name = (nameInput.value || t2.name) + ".json";
                      downloadBlob(name, blob);
                    }, "onclick")
                  }),
                  $el("button", {
                    textContent: "Delete",
                    style: {
                      fontSize: "12px",
                      color: "red",
                      fontWeight: "normal"
                    },
                    // @ts-expect-error fixme ts strict error
                    onclick: /* @__PURE__ */ __name((e) => {
                      const item = e.target.parentNode.parentNode;
                      item.parentNode.removeChild(item);
                      this.templates.splice(item.dataset.id * 1, 1);
                      this.store();
                      var that = this;
                      setTimeout(function() {
                        that.element.querySelectorAll(".templateManagerRow").forEach((el, i2) => {
                          el.dataset.id = i2.toString();
                        });
                      }, 0);
                    }, "onclick")
                  })
                ])
              ]
            )
          ];
        })
      )
    );
  }
}
const manage = new ManageTemplates();
const clipboardAction = /* @__PURE__ */ __name(async (cb) => {
  const old = localStorage.getItem("litegrapheditor_clipboard");
  await cb();
  localStorage.setItem("litegrapheditor_clipboard", old);
}, "clipboardAction");
const ext$1 = {
  name: id,
  getCanvasMenuItems(_canvas) {
    const items = [];
    items.push(null);
    items.push({
      content: `Save Selected as Template`,
      disabled: !Object.keys(app.canvas.selected_nodes || {}).length,
      callback: /* @__PURE__ */ __name(async () => {
        const name = await useDialogService().prompt({
          title: t("nodeTemplates.saveAsTemplate"),
          message: t("nodeTemplates.enterName"),
          defaultValue: ""
        });
        if (!name?.trim()) return;
        clipboardAction(() => {
          app.canvas.copyToClipboard();
          let data = localStorage.getItem("litegrapheditor_clipboard");
          data = JSON.parse(data || "{}");
          const nodeIds = Object.keys(app.canvas.selected_nodes);
          for (let i = 0; i < nodeIds.length; i++) {
            const node = app.canvas.graph?.getNodeById(nodeIds[i]);
            const nodeData = node?.constructor.nodeData;
            let groupData = GroupNodeHandler.getGroupData(node);
            if (groupData) {
              groupData = groupData.nodeData;
              if (!data.groupNodes) {
                data.groupNodes = {};
              }
              if (nodeData == null) throw new TypeError("nodeData is not set");
              data.groupNodes[nodeData.name] = groupData;
              data.nodes[i].type = nodeData.name;
            }
          }
          manage.templates.push({
            name,
            data: JSON.stringify(data)
          });
          manage.store();
        });
      }, "callback")
    });
    const subItems = manage.templates.map((t2) => {
      return {
        content: t2.name,
        callback: /* @__PURE__ */ __name(() => {
          clipboardAction(async () => {
            const data = JSON.parse(t2.data);
            await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {});
            if (!data.reroutes) {
              deserialiseAndCreate(t2.data, app.canvas);
            } else {
              localStorage.setItem("litegrapheditor_clipboard", t2.data);
              app.canvas.pasteFromClipboard();
            }
          });
        }, "callback")
      };
    });
    subItems.push(null, {
      content: "Manage",
      callback: /* @__PURE__ */ __name(() => manage.show(), "callback")
    });
    items.push({
      content: "Node Templates",
      submenu: {
        options: subItems
      }
    });
    return items;
  }
};
app.registerExtension(ext$1);
app.registerExtension({
  name: "Comfy.NoteNode",
  registerCustomNodes() {
    class NoteNode extends LGraphNode {
      static {
        __name(this, "NoteNode");
      }
      static category;
      static collapsable;
      static title_mode;
      groupcolor = LGraphCanvas.node_colors.yellow.groupcolor;
      isVirtualNode;
      constructor(title) {
        super(title);
        this.color = LGraphCanvas.node_colors.yellow.color;
        this.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor;
        if (!this.properties) {
          this.properties = { text: "" };
        }
        ComfyWidgets.STRING(
          this,
          "text",
          ["STRING", { default: this.properties.text, multiline: true }],
          app
        );
        this.serialize_widgets = true;
        this.isVirtualNode = true;
      }
    }
    LiteGraph.registerNodeType(
      "Note",
      Object.assign(NoteNode, {
        title_mode: LiteGraph.NORMAL_TITLE,
        title: "Note",
        collapsable: true
      })
    );
    NoteNode.category = "utils";
    class MarkdownNoteNode extends LGraphNode {
      static {
        __name(this, "MarkdownNoteNode");
      }
      static title = "Markdown Note";
      groupcolor = LGraphCanvas.node_colors.yellow.groupcolor;
      constructor(title) {
        super(title);
        this.color = LGraphCanvas.node_colors.yellow.color;
        this.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor;
        if (!this.properties) {
          this.properties = { text: "" };
        }
        ComfyWidgets.MARKDOWN(
          this,
          "text",
          ["STRING", { default: this.properties.text }],
          app
        );
        this.serialize_widgets = true;
        this.isVirtualNode = true;
      }
    }
    LiteGraph.registerNodeType("MarkdownNote", MarkdownNoteNode);
    MarkdownNoteNode.category = "utils";
  }
});
useExtensionService().registerExtension({
  name: "Comfy.PreviewAny",
  async beforeRegisterNodeDef(nodeType, nodeData) {
    if (nodeData.name === "PreviewAny") {
      const onNodeCreated2 = nodeType.prototype.onNodeCreated;
      nodeType.prototype.onNodeCreated = function() {
        onNodeCreated2 ? onNodeCreated2.apply(this, []) : void 0;
        const showValueWidget = ComfyWidgets["MARKDOWN"](
          this,
          "preview",
          ["MARKDOWN", {}],
          app
        ).widget;
        const showValueWidgetPlain = ComfyWidgets["STRING"](
          this,
          "preview",
          ["STRING", { multiline: true }],
          app
        ).widget;
        const showAsPlaintextWidget = ComfyWidgets["BOOLEAN"](
          this,
          "previewMode",
          [
            "BOOLEAN",
            { label_on: "Markdown", label_off: "Plaintext", default: false }
          ],
          app
        );
        showAsPlaintextWidget.widget.callback = (value) => {
          showValueWidget.hidden = !value;
          showValueWidget.options.hidden = !value;
          showValueWidgetPlain.hidden = value;
          showValueWidgetPlain.options.hidden = value;
        };
        showValueWidget.hidden = true;
        showValueWidget.options.hidden = true;
        showValueWidget.options.read_only = true;
        showValueWidget.element.readOnly = true;
        showValueWidget.element.disabled = true;
        showValueWidget.serialize = false;
        showValueWidgetPlain.hidden = false;
        showValueWidgetPlain.options.hidden = false;
        showValueWidgetPlain.options.read_only = true;
        showValueWidgetPlain.element.readOnly = true;
        showValueWidgetPlain.element.disabled = true;
        showValueWidgetPlain.serialize = false;
      };
      const onExecuted = nodeType.prototype.onExecuted;
      nodeType.prototype.onExecuted = function(message) {
        onExecuted === null || onExecuted === void 0 ? void 0 : onExecuted.apply(this, [message]);
        const previewWidgets = this.widgets?.filter((w) => w.name === "preview") ?? [];
        for (const previewWidget of previewWidgets) {
          const text = message.text ?? "";
          previewWidget.value = Array.isArray(text) ? text[0] ?? "" : text;
        }
      };
    }
  }
});
app.registerExtension({
  name: "Comfy.RerouteNode",
  registerCustomNodes(app2) {
    class RerouteNode extends LGraphNode {
      static {
        __name(this, "RerouteNode");
      }
      static category;
      static defaultVisibility = false;
      constructor(title) {
        super(title ?? "");
        if (!this.properties) {
          this.properties = {};
        }
        this.properties.showOutputText = RerouteNode.defaultVisibility;
        this.properties.horizontal = false;
        this.addInput("", "*");
        this.addOutput(this.properties.showOutputText ? "*" : "", "*");
        this.isVirtualNode = true;
      }
      onAfterGraphConfigured() {
        requestAnimationFrame(() => {
          this.onConnectionsChange(LiteGraph.INPUT, void 0, true);
        });
      }
      clone() {
        const cloned = super.clone();
        if (!cloned) return cloned;
        cloned.removeOutput(0);
        cloned.addOutput(this.properties.showOutputText ? "*" : "", "*");
        cloned.setSize(cloned.computeSize());
        return cloned;
      }
      onConnectionsChange(type, _index, connected) {
        const { graph } = this;
        if (!graph) return;
        if (app2.configuringGraph) return;
        if (connected && type === LiteGraph.OUTPUT) {
          const types = new Set(
            this.outputs[0].links?.map((l) => graph.links[l]?.type)?.filter((t2) => t2 && t2 !== "*") ?? []
          );
          if (types.size > 1) {
            const linksToDisconnect = [];
            for (const linkId of this.outputs[0].links ?? []) {
              const link = graph.links[linkId];
              linksToDisconnect.push(link);
            }
            linksToDisconnect.pop();
            for (const link of linksToDisconnect) {
              const node = graph.getNodeById(link.target_id);
              node?.disconnectInput(link.target_slot);
            }
          }
        }
        let currentNode = this;
        let updateNodes = [];
        let inputType = null;
        let inputNode = null;
        while (currentNode) {
          updateNodes.unshift(currentNode);
          const linkId = currentNode.inputs[0].link;
          if (linkId !== null) {
            const link = graph.links[linkId];
            if (!link) return;
            const node = graph.getNodeById(link.origin_id);
            if (!node) return;
            if (node instanceof RerouteNode) {
              if (node === this) {
                currentNode.disconnectInput(link.target_slot);
                currentNode = null;
              } else {
                currentNode = node;
              }
            } else {
              inputNode = currentNode;
              inputType = node.outputs[link.origin_slot]?.type ?? null;
              break;
            }
          } else {
            currentNode = null;
            break;
          }
        }
        const nodes = [this];
        let outputType = null;
        while (nodes.length) {
          currentNode = nodes.pop();
          const outputs = currentNode.outputs?.[0]?.links ?? [];
          for (const linkId of outputs) {
            const link = graph.links[linkId];
            if (!link) continue;
            const node = graph.getNodeById(link.target_id);
            if (!node) continue;
            if (node instanceof RerouteNode) {
              nodes.push(node);
              updateNodes.push(node);
            } else {
              const nodeInput = node.inputs[link.target_slot];
              const nodeOutType = nodeInput.type;
              const keep = !inputType || !nodeOutType || LiteGraph.isValidConnection(inputType, nodeOutType);
              if (!keep) {
                node.disconnectInput(link.target_slot);
                continue;
              }
              node.onConnectionsChange?.(
                LiteGraph.INPUT,
                link.target_slot,
                keep,
                link,
                nodeInput
              );
              outputType = node.inputs[link.target_slot].type;
            }
          }
        }
        const displayType = inputType || outputType || "*";
        const color = LGraphCanvas.link_type_colors[displayType];
        let widgetConfig;
        let widgetType;
        for (const node of updateNodes) {
          node.outputs[0].type = inputType || "*";
          node.__outputType = displayType;
          node.outputs[0].name = node.properties.showOutputText ? `${displayType}` : "";
          node.setSize(node.computeSize());
          for (const l of node.outputs[0].links || []) {
            const link = graph.links[l];
            if (!link) continue;
            link.color = color;
            if (app2.configuringGraph) continue;
            const targetNode = graph.getNodeById(link.target_id);
            if (!targetNode) continue;
            const targetInput = targetNode.inputs?.[link.target_slot];
            if (targetInput?.widget) {
              const config = getWidgetConfig(targetInput);
              if (!widgetConfig) {
                widgetConfig = config[1] ?? {};
                widgetType = config[0];
              }
              const merged = mergeIfValid(targetInput, [
                config[0],
                widgetConfig
              ]);
              if (merged.customConfig) {
                widgetConfig = merged.customConfig;
              }
            }
          }
        }
        for (const node of updateNodes) {
          if (widgetConfig && outputType) {
            node.inputs[0].widget = { name: "value" };
            setWidgetConfig(node.inputs[0], [
              widgetType ?? `${displayType}`,
              widgetConfig
            ]);
          } else {
            setWidgetConfig(node.inputs[0], void 0);
          }
        }
        if (inputNode?.inputs?.[0]?.link) {
          const link = graph.links[inputNode.inputs[0].link];
          if (link) {
            link.color = color;
          }
        }
      }
      getExtraMenuOptions(_, options) {
        options.unshift(
          {
            content: (this.properties.showOutputText ? "Hide" : "Show") + " Type",
            callback: /* @__PURE__ */ __name(() => {
              this.properties.showOutputText = !this.properties.showOutputText;
              if (this.properties.showOutputText) {
                this.outputs[0].name = `${this.__outputType || this.outputs[0].type}`;
              } else {
                this.outputs[0].name = "";
              }
              this.setSize(this.computeSize());
              app2.canvas.setDirty(true, true);
            }, "callback")
          },
          {
            content: (RerouteNode.defaultVisibility ? "Hide" : "Show") + " Type By Default",
            callback: /* @__PURE__ */ __name(() => {
              RerouteNode.setDefaultTextVisibility(
                !RerouteNode.defaultVisibility
              );
            }, "callback")
          }
        );
        return [];
      }
      computeSize() {
        return [
          this.properties.showOutputText && this.outputs && this.outputs.length ? Math.max(
            75,
            LiteGraph.NODE_TEXT_SIZE * this.outputs[0].name.length * 0.6 + 40
          ) : 75,
          26
        ];
      }
      static setDefaultTextVisibility(visible) {
        RerouteNode.defaultVisibility = visible;
        if (visible) {
          localStorage["Comfy.RerouteNode.DefaultVisibility"] = "true";
        } else {
          delete localStorage["Comfy.RerouteNode.DefaultVisibility"];
        }
      }
    }
    RerouteNode.setDefaultTextVisibility(
      !!localStorage["Comfy.RerouteNode.DefaultVisibility"]
    );
    LiteGraph.registerNodeType(
      "Reroute",
      Object.assign(RerouteNode, {
        title_mode: LiteGraph.NO_TITLE,
        title: "Reroute",
        collapsable: false
      })
    );
    RerouteNode.category = "utils";
  }
});
const saveNodeTypes = /* @__PURE__ */ new Set([
  "SaveImage",
  "SaveVideo",
  "SaveAnimatedWEBP",
  "SaveWEBM",
  "SaveAudio",
  "SaveGLB",
  "SaveAnimatedPNG",
  "CLIPSave",
  "VAESave",
  "ModelSave",
  "LoraSave",
  "SaveLatent"
]);
app.registerExtension({
  name: "Comfy.SaveImageExtraOutput",
  async beforeRegisterNodeDef(nodeType, nodeData, app2) {
    if (saveNodeTypes.has(nodeData.name)) {
      const onNodeCreated2 = nodeType.prototype.onNodeCreated;
      nodeType.prototype.onNodeCreated = function() {
        const r = onNodeCreated2 ? (
          // @ts-expect-error fixme ts strict error
          onNodeCreated2.apply(this, arguments)
        ) : void 0;
        const widget = this.widgets.find((w) => w.name === "filename_prefix");
        widget.serializeValue = () => {
          return applyTextReplacements(app2.graph, widget.value);
        };
        return r;
      };
    } else {
      const onNodeCreated2 = nodeType.prototype.onNodeCreated;
      nodeType.prototype.onNodeCreated = function() {
        const r = onNodeCreated2 ? (
          // @ts-expect-error fixme ts strict error
          onNodeCreated2.apply(this, arguments)
        ) : void 0;
        if (!this.properties || !("Node name for S&R" in this.properties)) {
          this.addProperty("Node name for S&R", this.constructor.type, "string");
        }
        return r;
      };
    }
  }
});
const inputSpec = {
  name: "image",
  type: "Preview3D",
  isPreview: true
};
useExtensionService().registerExtension({
  name: "Comfy.SaveGLB",
  async beforeRegisterNodeDef(_nodeType, nodeData) {
    if ("SaveGLB" === nodeData.name) {
      nodeData.input.required.image = ["PREVIEW_3D"];
    }
  },
  getCustomWidgets() {
    return {
      PREVIEW_3D(node) {
        const widget = new ComponentWidgetImpl({
          node,
          name: inputSpec.name,
          component: _sfc_main,
          inputSpec,
          options: {}
        });
        widget.type = "load3D";
        addWidget(node, widget);
        return { widget };
      }
    };
  },
  getNodeMenuItems(node) {
    if (node.constructor.comfyClass !== "SaveGLB") return [];
    const load3d = useLoad3dService().getLoad3d(node);
    if (!load3d) return [];
    if (load3d.isSplatModel()) return [];
    return createExportMenuItems(load3d);
  },
  async nodeCreated(node) {
    if (node.constructor.comfyClass !== "SaveGLB") return;
    const [oldWidth, oldHeight] = node.size;
    node.setSize([Math.max(oldWidth, 400), Math.max(oldHeight, 550)]);
    await nextTick();
    const onExecuted = node.onExecuted;
    node.onExecuted = function(message) {
      onExecuted?.apply(this, arguments);
      const fileInfo = message["3d"][0];
      useLoad3d(node).waitForLoad3d((load3d) => {
        const modelWidget = node.widgets?.find((w) => w.name === "image");
        if (load3d && modelWidget) {
          const filePath = fileInfo["subfolder"] + "/" + fileInfo["filename"];
          modelWidget.value = filePath;
          const config = new Load3DConfiguration(load3d, node.properties);
          config.configureForSaveMesh(fileInfo["type"], filePath);
        }
      });
    };
  }
});
function drawSelectionBorder(ctx, canvas) {
  const selectedItems = canvas.selectedItems;
  if (selectedItems.size <= 1) return;
  const bounds = createBounds(selectedItems, 10);
  if (!bounds) return;
  const [x, y, width, height] = bounds;
  ctx.save();
  const borderWidth = 2 / canvas.ds.scale;
  ctx.lineWidth = borderWidth;
  ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue("--border-color").trim() || "#ffffff66";
  const dashSize = 5 / canvas.ds.scale;
  ctx.setLineDash([dashSize, dashSize]);
  ctx.beginPath();
  ctx.roundRect(x, y, width, height, 8 / canvas.ds.scale);
  ctx.stroke();
  ctx.restore();
}
__name(drawSelectionBorder, "drawSelectionBorder");
const ext = {
  name: "Comfy.SelectionBorder",
  async init() {
    const originalDrawForeground = app.canvas.onDrawForeground;
    app.canvas.onDrawForeground = function(ctx, visibleArea) {
      originalDrawForeground?.call(this, ctx, visibleArea);
      drawSelectionBorder(ctx, app.canvas);
    };
  }
};
app.registerExtension(ext);
let touchZooming = false;
let touchCount = 0;
app.registerExtension({
  name: "Comfy.SimpleTouchSupport",
  setup() {
    let touchDist = null;
    let touchTime = null;
    let lastTouch = null;
    let lastScale = null;
    function getMultiTouchPos(e) {
      return Math.hypot(
        e.touches[0].clientX - e.touches[1].clientX,
        e.touches[0].clientY - e.touches[1].clientY
      );
    }
    __name(getMultiTouchPos, "getMultiTouchPos");
    function getMultiTouchCenter(e) {
      return {
        clientX: (e.touches[0].clientX + e.touches[1].clientX) / 2,
        clientY: (e.touches[0].clientY + e.touches[1].clientY) / 2
      };
    }
    __name(getMultiTouchCenter, "getMultiTouchCenter");
    app.canvasEl.parentElement?.addEventListener(
      "touchstart",
      (e) => {
        touchCount += e.changedTouches.length;
        lastTouch = null;
        lastScale = null;
        if (e.touches?.length === 1) {
          touchTime = /* @__PURE__ */ new Date();
          lastTouch = e.touches[0];
        } else {
          touchTime = null;
          if (e.touches?.length === 2) {
            lastScale = app.canvas.ds.scale;
            lastTouch = getMultiTouchCenter(e);
            touchDist = getMultiTouchPos(e);
            app.canvas.pointer.isDown = false;
          }
        }
      },
      true
    );
    app.canvasEl.parentElement?.addEventListener(
      "touchend",
      (e) => {
        touchCount -= e.changedTouches.length;
        if (e.touches?.length !== 1) touchZooming = false;
        if (touchTime && !e.touches?.length) {
          if ((/* @__PURE__ */ new Date()).getTime() - touchTime.getTime() > 600) {
            if (e.target === app.canvasEl) {
              const touch = {
                button: 2,
                // Right click
                clientX: e.changedTouches[0].clientX,
                clientY: e.changedTouches[0].clientY,
                pointerId: 1,
                // changedTouches' id is 0, set it to any number
                isPrimary: true
                // changedTouches' isPrimary is false, so set it to true
              };
              app.canvasEl.dispatchEvent(new PointerEvent("pointerdown", touch));
              setTimeout(() => {
                app.canvasEl.dispatchEvent(new PointerEvent("pointerup", touch));
              });
              e.preventDefault();
            }
          }
          touchTime = null;
        }
      }
    );
    const resetTouchState = /* @__PURE__ */ __name(() => {
      touchCount = 0;
      touchZooming = false;
      touchTime = null;
      lastTouch = null;
      lastScale = null;
      touchDist = null;
    }, "resetTouchState");
    document.addEventListener("visibilitychange", () => {
      if (document.hidden) {
        resetTouchState();
      }
    });
    app.canvasEl.parentElement?.addEventListener("touchcancel", resetTouchState);
    app.canvasEl.parentElement?.addEventListener(
      "touchmove",
      (e) => {
        if (touchTime && lastTouch && e.touches?.length === 1) {
          const onlyTouch = e.touches[0];
          const deltaX = onlyTouch.clientX - lastTouch.clientX;
          const deltaY = onlyTouch.clientY - lastTouch.clientY;
          if (deltaX * deltaX + deltaY * deltaY > 30) {
            touchTime = null;
          }
        }
        if (e.touches?.length === 2 && lastTouch && !e.ctrlKey && !e.shiftKey) {
          e.preventDefault();
          app.canvas.pointer.isDown = false;
          touchZooming = true;
          LiteGraph.closeAllContextMenus(window);
          app.canvas.search_box?.close();
          const newTouchDist = getMultiTouchPos(e);
          const center = getMultiTouchCenter(e);
          if (lastScale === null || touchDist === null) return;
          let scale = lastScale * newTouchDist / touchDist;
          const newX = (center.clientX - lastTouch.clientX) / scale;
          const newY = (center.clientY - lastTouch.clientY) / scale;
          if (scale < app.canvas.ds.min_scale) {
            scale = app.canvas.ds.min_scale;
          } else if (scale > app.canvas.ds.max_scale) {
            scale = app.canvas.ds.max_scale;
          }
          const oldScale = app.canvas.ds.scale;
          app.canvas.ds.scale = scale;
          if (Math.abs(app.canvas.ds.scale - 1) < 0.01) {
            app.canvas.ds.scale = 1;
          }
          const newScale = app.canvas.ds.scale;
          const convertScaleToOffset = /* @__PURE__ */ __name((scale2) => [
            center.clientX / scale2 - app.canvas.ds.offset[0],
            center.clientY / scale2 - app.canvas.ds.offset[1]
          ], "convertScaleToOffset");
          var oldCenter = convertScaleToOffset(oldScale);
          var newCenter = convertScaleToOffset(newScale);
          app.canvas.ds.offset[0] += newX + newCenter[0] - oldCenter[0];
          app.canvas.ds.offset[1] += newY + newCenter[1] - oldCenter[1];
          lastTouch.clientX = center.clientX;
          lastTouch.clientY = center.clientY;
          app.canvas.setDirty(true, true);
        }
      },
      true
    );
  }
});
const processMouseDown = LGraphCanvas.prototype.processMouseDown;
LGraphCanvas.prototype.processMouseDown = function(e) {
  if (touchZooming || touchCount) {
    return;
  }
  app.canvas.pointer.isDown = false;
  return processMouseDown.apply(this, [e]);
};
const processMouseMove = LGraphCanvas.prototype.processMouseMove;
LGraphCanvas.prototype.processMouseMove = function(e) {
  if (touchZooming || touchCount > 1) {
    return;
  }
  return processMouseMove.apply(this, [e]);
};
app.registerExtension({
  name: "Comfy.SlotDefaults",
  suggestionsNumber: null,
  init() {
    LiteGraph.search_filter_enabled = true;
    LiteGraph.middle_click_slot_add_default_node = true;
    this.suggestionsNumber = app.ui.settings.addSetting({
      id: "Comfy.NodeSuggestions.number",
      category: ["Comfy", "Node Search Box", "NodeSuggestions"],
      name: "Number of nodes suggestions",
      tooltip: "Only for litegraph searchbox/context menu",
      type: "slider",
      attrs: {
        min: 1,
        max: 100,
        step: 1
      },
      defaultValue: 5,
      onChange: /* @__PURE__ */ __name((newVal) => {
        this.setDefaults(newVal);
      }, "onChange")
    });
  },
  slot_types_default_out: {},
  slot_types_default_in: {},
  async beforeRegisterNodeDef(nodeType, nodeData) {
    var nodeId = nodeData.name;
    const inputs = nodeData["input"]?.["required"];
    for (const inputKey in inputs) {
      var input = inputs[inputKey];
      if (typeof input[0] !== "string") continue;
      var type = input[0];
      if (type in ComfyWidgets) {
        var customProperties = input[1];
        if (!customProperties?.forceInput) continue;
      }
      if (!(type in this.slot_types_default_out)) {
        this.slot_types_default_out[type] = ["Reroute"];
      }
      if (this.slot_types_default_out[type].includes(nodeId)) continue;
      this.slot_types_default_out[type].push(nodeId);
      const lowerType = type.toLocaleLowerCase();
      if (!(lowerType in LiteGraph.registered_slot_in_types)) {
        LiteGraph.registered_slot_in_types[lowerType] = { nodes: [] };
      }
      LiteGraph.registered_slot_in_types[lowerType].nodes.push(
        // @ts-expect-error ComfyNode
        nodeType.comfyClass
      );
    }
    var outputs = nodeData["output"] ?? [];
    for (const el of outputs) {
      const type2 = el;
      if (!(type2 in this.slot_types_default_in)) {
        this.slot_types_default_in[type2] = ["Reroute"];
      }
      if (this.slot_types_default_in[type2].includes(nodeId)) continue;
      this.slot_types_default_in[type2].push(nodeId);
      if (!(type2 in LiteGraph.registered_slot_out_types)) {
        LiteGraph.registered_slot_out_types[type2] = { nodes: [] };
      }
      LiteGraph.registered_slot_out_types[type2].nodes.push(nodeType.comfyClass);
      if (!LiteGraph.slot_types_out.includes(type2)) {
        LiteGraph.slot_types_out.push(type2);
      }
    }
    var maxNum = this.suggestionsNumber.value;
    this.setDefaults(maxNum);
  },
  setDefaults(maxNum) {
    LiteGraph.slot_types_default_out = {};
    LiteGraph.slot_types_default_in = {};
    for (const type in this.slot_types_default_out) {
      LiteGraph.slot_types_default_out[type] = this.slot_types_default_out[type].slice(0, maxNum);
    }
    for (const type in this.slot_types_default_in) {
      LiteGraph.slot_types_default_in[type] = this.slot_types_default_in[type].slice(0, maxNum);
    }
  }
});
async function uploadFile(audioWidget, audioUIWidget, file2, updateNode, pasted = false) {
  try {
    const body = new FormData();
    body.append("image", file2);
    if (pasted) body.append("subfolder", "pasted");
    const resp = await api.fetchApi("/upload/image", {
      method: "POST",
      body
    });
    if (resp.status === 200) {
      const data = await resp.json();
      let path = data.name;
      if (data.subfolder) path = data.subfolder + "/" + path;
      if (!audioWidget.options.values.includes(path)) {
        audioWidget.options.values.push(path);
      }
      if (updateNode) {
        audioUIWidget.element.src = api.apiURL(
          getResourceURL(...splitFilePath(path))
        );
        audioWidget.value = path;
        audioWidget.callback?.(path);
      }
    } else {
      useToastStore().addAlert(resp.status + " - " + resp.statusText);
    }
  } catch (error) {
    useToastStore().addAlert(error);
  }
}
__name(uploadFile, "uploadFile");
app.registerExtension({
  name: "Comfy.AudioWidget",
  async beforeRegisterNodeDef(nodeType, nodeData) {
    if ([
      "LoadAudio",
      "SaveAudio",
      "PreviewAudio",
      "SaveAudioMP3",
      "SaveAudioOpus"
    ].includes(
      // @ts-expect-error fixme ts strict error
      nodeType.prototype.comfyClass
    )) {
      nodeData.input.required.audioUI = ["AUDIO_UI", {}];
    }
  },
  getCustomWidgets() {
    return {
      AUDIO_UI(node, inputName) {
        const audio = document.createElement("audio");
        audio.controls = true;
        audio.classList.add("comfy-audio");
        audio.setAttribute("name", "media");
        const audioUIWidget = node.addDOMWidget(
          inputName,
          /* name=*/
          "audioUI",
          audio
        );
        audioUIWidget.serialize = false;
        const { nodeData } = node.constructor;
        if (nodeData == null) throw new TypeError("nodeData is null");
        const isOutputNode = nodeData.output_node;
        if (isOutputNode) {
          audioUIWidget.element.classList.add("empty-audio-widget");
          const onExecuted = node.onExecuted;
          node.onExecuted = function(message) {
            onExecuted?.apply(this, arguments);
            const audios = message.audio;
            if (!audios) return;
            const audio2 = audios[0];
            audioUIWidget.element.src = api.apiURL(
              getResourceURL(audio2.subfolder, audio2.filename, audio2.type)
            );
            audioUIWidget.element.classList.remove("empty-audio-widget");
          };
        }
        audioUIWidget.onRemove = useChainCallback(
          audioUIWidget.onRemove,
          () => {
            if (!audioUIWidget.element) return;
            audioUIWidget.element.pause();
            audioUIWidget.element.src = "";
            audioUIWidget.element.remove();
          }
        );
        return { widget: audioUIWidget };
      }
    };
  },
  onNodeOutputsUpdated(nodeOutputs) {
    for (const [nodeLocatorId, output] of Object.entries(nodeOutputs)) {
      if ("audio" in output) {
        const node = getNodeByLocatorId(app.rootGraph, nodeLocatorId);
        if (!node) continue;
        const audioUIWidget = node.widgets.find(
          (w) => w.name === "audioUI"
        );
        const audio = output.audio[0];
        audioUIWidget.element.src = api.apiURL(
          getResourceURL(audio.subfolder, audio.filename, audio.type)
        );
        audioUIWidget.element.classList.remove("empty-audio-widget");
      }
    }
  }
});
app.registerExtension({
  name: "Comfy.UploadAudio",
  async beforeRegisterNodeDef(_nodeType, nodeData) {
    if (nodeData?.input?.required?.audio?.[1]?.audio_upload === true) {
      nodeData.input.required.upload = ["AUDIOUPLOAD", {}];
    }
  },
  getCustomWidgets() {
    return {
      AUDIOUPLOAD(node, inputName) {
        const audioWidget = node.widgets.find(
          (w) => w.name === "audio"
        );
        const audioUIWidget = node.widgets.find(
          (w) => w.name === "audioUI"
        );
        audioUIWidget.options.canvasOnly = true;
        const onAudioWidgetUpdate = /* @__PURE__ */ __name(() => {
          if (typeof audioWidget.value !== "string") return;
          audioUIWidget.element.src = api.apiURL(
            getResourceURL(...splitFilePath(audioWidget.value))
          );
        }, "onAudioWidgetUpdate");
        if (audioWidget.value) {
          onAudioWidgetUpdate();
        }
        audioWidget.callback = onAudioWidgetUpdate;
        const onGraphConfigured = node.onGraphConfigured;
        node.onGraphConfigured = function() {
          onGraphConfigured?.apply(this, arguments);
          if (audioWidget.value) {
            onAudioWidgetUpdate();
          }
        };
        const handleUpload = /* @__PURE__ */ __name(async (files) => {
          if (files?.length) {
            uploadFile(audioWidget, audioUIWidget, files[0], true);
          }
          return files;
        }, "handleUpload");
        const isAudioFile = /* @__PURE__ */ __name((file2) => file2.type.startsWith("audio/"), "isAudioFile");
        const { openFileSelection } = useNodeFileInput(node, {
          accept: "audio/*",
          onSelect: handleUpload
        });
        const uploadWidget = node.addWidget(
          "button",
          inputName,
          "",
          openFileSelection,
          { serialize: false, canvasOnly: true }
        );
        uploadWidget.label = t("g.choose_file_to_upload");
        useNodeDragAndDrop(node, {
          fileFilter: isAudioFile,
          onDrop: handleUpload
        });
        useNodePaste(node, {
          fileFilter: isAudioFile,
          onPaste: handleUpload
        });
        node.previewMediaType = "audio";
        return { widget: uploadWidget };
      }
    };
  }
});
app.registerExtension({
  name: "Comfy.RecordAudio",
  getCustomWidgets() {
    return {
      AUDIO_RECORD(node, inputName) {
        const audio = document.createElement("audio");
        audio.controls = true;
        audio.classList.add("comfy-audio");
        audio.setAttribute("name", "media");
        const audioUIWidget = node.addDOMWidget(
          inputName,
          /* name=*/
          "audioUI",
          audio
        );
        audioUIWidget.options.canvasOnly = false;
        let mediaRecorder = null;
        let isRecording = false;
        let audioChunks = [];
        let currentStream = null;
        let recordWidget = null;
        let stopPromise = null;
        let stopResolve = null;
        audioUIWidget.serializeValue = async () => {
          if (isRecording && mediaRecorder) {
            stopPromise = new Promise((resolve) => {
              stopResolve = resolve;
            });
            mediaRecorder.stop();
            await stopPromise;
          }
          const audioSrc = audioUIWidget.element.src;
          if (!audioSrc) {
            useToastStore().addAlert(t("g.noAudioRecorded"));
            return "";
          }
          const blob = await fetch(audioSrc).then((r) => r.blob());
          return await useAudioService().convertBlobToFileAndSubmit(blob);
        };
        recordWidget = node.addWidget(
          "button",
          inputName,
          "",
          async () => {
            if (!isRecording) {
              try {
                currentStream = await navigator.mediaDevices.getUserMedia({
                  audio: true
                });
                mediaRecorder = new mediaRecorderConstructor(currentStream, {
                  mimeType: "audio/wav"
                });
                audioChunks = [];
                mediaRecorder.ondataavailable = (event) => {
                  audioChunks.push(event.data);
                };
                mediaRecorder.onstop = async () => {
                  const audioBlob = new Blob(audioChunks, { type: "audio/wav" });
                  useAudioService().stopAllTracks(currentStream);
                  if (audioUIWidget.element.src && audioUIWidget.element.src.startsWith("blob:")) {
                    URL.revokeObjectURL(audioUIWidget.element.src);
                  }
                  audioUIWidget.element.src = URL.createObjectURL(audioBlob);
                  isRecording = false;
                  if (recordWidget) {
                    recordWidget.label = t("g.startRecording");
                  }
                  if (stopResolve) {
                    stopResolve();
                    stopResolve = null;
                    stopPromise = null;
                  }
                };
                mediaRecorder.onerror = (event) => {
                  console.error("MediaRecorder error:", event);
                  useAudioService().stopAllTracks(currentStream);
                  isRecording = false;
                  if (recordWidget) {
                    recordWidget.label = t("g.startRecording");
                  }
                  if (stopResolve) {
                    stopResolve();
                    stopResolve = null;
                    stopPromise = null;
                  }
                };
                mediaRecorder.start();
                isRecording = true;
                if (recordWidget) {
                  recordWidget.label = t("g.stopRecording");
                }
              } catch (err) {
                console.error("Error accessing microphone:", err);
                useToastStore().addAlert(t("g.micPermissionDenied"));
                if (mediaRecorder) {
                  try {
                    mediaRecorder.stop();
                  } catch {
                  }
                }
                useAudioService().stopAllTracks(currentStream);
                currentStream = null;
                isRecording = false;
                if (recordWidget) {
                  recordWidget.label = t("g.startRecording");
                }
              }
            } else if (mediaRecorder && isRecording) {
              mediaRecorder.stop();
            }
          },
          { serialize: false, canvasOnly: false }
        );
        recordWidget.label = t("g.startRecording");
        recordWidget.type = "audiorecord";
        const originalOnRemoved = node.onRemoved;
        node.onRemoved = function() {
          if (isRecording && mediaRecorder) {
            mediaRecorder.stop();
          }
          useAudioService().stopAllTracks(currentStream);
          if (audioUIWidget.element.src?.startsWith("blob:")) {
            URL.revokeObjectURL(audioUIWidget.element.src);
          }
          originalOnRemoved?.call(this);
        };
        return { widget: recordWidget };
      }
    };
  },
  async nodeCreated(node) {
    if (node.constructor.comfyClass !== "RecordAudio") return;
    await useAudioService().registerWavEncoder();
  }
});
const isMediaUploadComboInput = /* @__PURE__ */ __name((inputSpec2) => {
  const [inputName, inputOptions] = inputSpec2;
  if (!inputOptions) return false;
  const isUploadInput = inputOptions["image_upload"] === true || inputOptions["video_upload"] === true || inputOptions["animated_image_upload"] === true;
  return isUploadInput && (isComboInputSpecV1(inputSpec2) || inputName === "COMBO");
}, "isMediaUploadComboInput");
const createUploadInput = /* @__PURE__ */ __name((imageInputName, imageInputOptions) => [
  "IMAGEUPLOAD",
  {
    ...imageInputOptions[1],
    imageInputName
  }
], "createUploadInput");
app.registerExtension({
  name: "Comfy.UploadImage",
  beforeRegisterNodeDef(_nodeType, nodeData) {
    const { input } = nodeData ?? {};
    const { required } = input ?? {};
    if (!required) return;
    const found = Object.entries(required).find(
      ([_, input2]) => isMediaUploadComboInput(input2)
    );
    if (found) {
      const [inputName, inputSpec2] = found;
      required.upload = createUploadInput(inputName, inputSpec2);
    }
  }
});
const WEBCAM_READY = /* @__PURE__ */ Symbol();
app.registerExtension({
  name: "Comfy.WebcamCapture",
  getCustomWidgets() {
    return {
      WEBCAM(node, inputName) {
        let res;
        node[WEBCAM_READY] = new Promise((resolve) => res = resolve);
        const container = document.createElement("div");
        container.style.background = "rgba(0,0,0,0.25)";
        container.style.textAlign = "center";
        const video = document.createElement("video");
        video.style.height = video.style.width = "100%";
        const loadVideo = /* @__PURE__ */ __name(async () => {
          try {
            const stream = await navigator.mediaDevices.getUserMedia({
              video: true,
              audio: false
            });
            container.replaceChildren(video);
            setTimeout(() => res(video), 500);
            video.addEventListener("loadedmetadata", () => res(video), false);
            video.srcObject = stream;
            video.play();
          } catch (error) {
            const label = document.createElement("div");
            label.style.color = "red";
            label.style.overflow = "auto";
            label.style.maxHeight = "100%";
            label.style.whiteSpace = "pre-wrap";
            if (window.isSecureContext) {
              label.textContent = "Unable to load webcam, please ensure access is granted:\n" + // @ts-expect-error fixme ts strict error
              error.message;
            } else {
              label.textContent = "Unable to load webcam. A secure context is required, if you are not accessing ComfyUI on localhost (127.0.0.1) you will have to enable TLS (https)\n\n" + // @ts-expect-error fixme ts strict error
              error.message;
            }
            container.replaceChildren(label);
          }
        }, "loadVideo");
        loadVideo();
        return { widget: node.addDOMWidget(inputName, "WEBCAM", container) };
      }
    };
  },
  nodeCreated(node) {
    if (node.type, node.constructor.comfyClass !== "WebcamCapture") return;
    let video;
    const camera = node.widgets.find((w2) => w2.name === "image");
    const w = node.widgets.find((w2) => w2.name === "width");
    const h = node.widgets.find((w2) => w2.name === "height");
    const captureOnQueue = node.widgets.find(
      (w2) => w2.name === "capture_on_queue"
    );
    const canvas = document.createElement("canvas");
    const capture = /* @__PURE__ */ __name(() => {
      canvas.width = w.value;
      canvas.height = h.value;
      const ctx = canvas.getContext("2d");
      ctx.drawImage(video, 0, 0, w.value, h.value);
      const data = canvas.toDataURL("image/png");
      const img = new Image();
      img.onload = () => {
        node.imgs = [img];
        app.canvas.setDirty(true);
      };
      img.src = data;
    }, "capture");
    const btn = node.addWidget(
      "button",
      "waiting for camera...",
      "capture",
      capture,
      { canvasOnly: true }
    );
    btn.disabled = true;
    btn.serializeValue = () => void 0;
    camera.serializeValue = async () => {
      if (captureOnQueue.value) {
        capture();
      } else if (!node.imgs?.length) {
        const err = `No webcam image captured`;
        useToastStore().addAlert(err);
        throw new Error(err);
      }
      const blob = await new Promise((r) => canvas.toBlob(r));
      const name = `${+/* @__PURE__ */ new Date()}.png`;
      const file2 = new File([blob], name);
      const body = new FormData();
      body.append("image", file2);
      body.append("subfolder", "webcam");
      body.append("type", "temp");
      const resp = await api.fetchApi("/upload/image", {
        method: "POST",
        body
      });
      if (resp.status !== 200) {
        const err = `Error uploading camera image: ${resp.status} - ${resp.statusText}`;
        useToastStore().addAlert(err);
        throw new Error(err);
      }
      return `webcam/${name} [temp]`;
    };
    node[WEBCAM_READY].then((v) => {
      video = v;
      if (!w.value) {
        w.value = video.videoWidth || 640;
        h.value = video.videoHeight || 480;
      }
      btn.disabled = false;
      btn.label = t("g.capture");
    });
  }
});
//# sourceMappingURL=index-B6o-AXrW.js.map
