const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./index-B6o-AXrW.js","./vendor-other-DdEHkzYa.js","./vendor-other-DODGPXtn.css","./Load3D.vue_vue_type_script_setup_true_lang-C4Pgqhqg.js","./vendor-primevue-DmDWdkDd.js","./Load3D-DM6CyeJS.css","./audioUtils-BurLDP37.js","./audioService-BYJv4FKM.js","./vendor-vue-BIRc7P93.js","./vendor-xterm-BF8peZ5_.js","./vendor-xterm-BKlWQB97.css","./vendor-three-_gcZkhit.js","./vendor-tiptap-B76LFN34.js","./index-BorZ9tvu.css","./MediaVideoTop-DrDqLo2M.js","./MediaAudioTop-CgldzfMV.js","./MediaImageTop-BmT4ess7.js","./Media3DTop-BUnGfk-N.js","./MediaVideoBottom-uKfTCjRu.js","./MediaTitle.vue_vue_type_script_setup_true_lang-Cagtukl2.js","./MediaAudioBottom-v4U8_pF9.js","./MediaImageBottom-Bd4M6dHo.js","./Media3DBottom-Dbp8kDWi.js","./WidgetButton-dYpFIcYD.js","./widgetPropFilter-BIbGSUAt.js","./WidgetInputText-CBfvHKGO.js","./index-Csoifk32.js","./WidgetLayoutField.vue_vue_type_script_setup_true_lang-BZ0_zQzJ.js","./WidgetInputNumber-cOgMBsme.js","./WidgetInputNumber.vue_vue_type_script_setup_true_lang-DBC4YFDC.js","./WidgetWithControl.vue_vue_type_script_setup_true_lang-Dugrx1Hx.js","./WidgetInputNumber-B3tW5hr6.css","./WidgetToggleSwitch-fPkC2AfT.js","./WidgetSelect-BbENZ2wo.js","./WidgetSelect.vue_vue_type_script_setup_true_lang-Dlin4Adg.js","./LazyImage.vue_vue_type_script_setup_true_lang-q3mcFzhk.js","./WidgetColorPicker-BoLiq7pp.js","./WidgetTextarea-CWEGHPVp.js","./WidgetChart-DE0SfMVO.js","./WidgetImageCompare-Bvg3Od2A.js","./WidgetGalleria-Cv_jTk4g.js","./WidgetGalleria-BgqeQwXL.css","./WidgetMarkdown-BIS1u9hf.js","./WidgetLegacy-Pu5H3QVb.js","./WidgetRecordAudio-vbi6hQm8.js","./AudioPreviewPlayer-CmLbwr_w.js","./AudioPreviewPlayer-B8FjNqFP.css","./WidgetAudioUI-Bq-GLU2i.js","./Load3D-TOov0GRg.js","./AboutPanel-EiS1z7jb.js","./LegacyCreditsPanel-DdzYlNTV.js","./UserPanel-D45LmBad.js","./UserAvatar.vue_vue_type_script_setup_true_lang-3ve79e_P.js","./KeybindingPanel-1oAuqHzd.js","./keybindingService-De8iQgyB.js","./KeybindingPanel-Bzxa0uFm.css","./ExtensionPanel-Ce96aMFv.js","./ServerConfigPanel-FGSzg5tP.js","./serverConfigStore-ClbvVDb_.js","./GraphView-DAIlTCBt.js","./GraphView-DHPVPhqW.css","./UserSelectView-DWDBg_h9.js"])))=>i.map(i=>d[i]);
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { _ as __vitePreload, s as script, a as script$1, b as script$2, c as script$3, d as script$4, e as script$5, f as script$6, g as script$7, h as script$8, i as script$9, j as script$a, k as script$b, l as script$c, m as script$d, n as script$e, u as useToast, o as script$f, p as script$g, q as script$h, r as script$i, t as script$j, v as script$k, w as script$l, x as script$m, y as script$n, z as script$o, A as script$p, B as script$q, C as zodResolver, D as script$r, E as script$s, F as script$t, G as script$u, H as script$v, I as script$w, J as script$x, K as script$y, L as script$z, M as script$A, N as script$B, O as script$C, P as script$D, Q as script$E, R as script$F, S as script$G, T as script$H, U as script$I, V as definePreset, W as index, X as Tooltip, Y as PrimeVue, Z as ConfirmationService, $ as ToastService } from "./vendor-primevue-DmDWdkDd.js";
import { r as ref, ci as axios, cj as trimEnd, ck as get, cl as twMerge, cm as clsx, cn as cva, bq as defineComponent, j as createBlock, k as withCtx, s as normalizeClass, br as unref, co as Primitive, c as createElementBlock, q as createCommentVNode, p as renderSlot, d as openBlock, E as computed, e as createBaseVNode, z as createVNode, u as toDisplayString, f as resolveComponent, A as createTextVNode, cp as useAsyncState, cq as semverExports, y as renderList, F as Fragment, cr as useElementBounding, cs as z, ct as fromZodError, cu as clamp, cv as YMap, cw as log, cx as Doc, cy as customRef, cz as applyUpdate, cA as encodeStateAsUpdate, cB as memoize, cC as without, cD as pull, cE as toString, cF as mergeModels, cG as useModel, o as onMounted, w as watch, bd as markRaw, bb as toRaw, cH as useLocalStorage, cI as GoogleAuthProvider, cJ as GithubAuthProvider, cK as setPersistence, cL as onAuthStateChanged, bi as onIdTokenChanged, cM as FirebaseError, cN as AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY, cO as signInWithEmailAndPassword, cP as createUserWithEmailAndPassword, cQ as signInWithPopup, cR as signOut, cS as sendPasswordResetEmail, cT as updatePassword, cU as deleteUser, bm as browserLocalPersistence, cV as useTimeoutFn, bg as onScopeDispose, cW as defaultWindow, cX as useEventListener, cY as createSharedComposable, cZ as toolkit, c_ as whenever, c$ as Fuse, d0 as remove, d1 as watchDebounced, l as withDirectives, I as withModifiers, J as withKeys, n as nextTick, d2 as merge, bs as shallowReactive, c6 as onUnmounted, d3 as debounce, d4 as useElementHover, h as resolveDirective, v as vShow, d5 as until, bp as shallowRef, L as toValue, d6 as useElementSize, d7 as useScroll, d8 as onBeforeUnmount, G as normalizeStyle, d9 as vModelText, da as toRef$1, i as inject, bu as provide, db as useSlots, dc as useClipboard, dd as isObject, de as onClickOutside, m as mergeProps, be as toRef$2, t as resolveDynamicComponent, D as createSlots, df as defineAsyncComponent, dg as useKeyModifier, dh as refDebounced, di as sortBy, dj as useResizeObserver, dk as useDebounceFn, b9 as isRef, dl as draggable, dm as dropTargetForElements, dn as setCustomNativeDragPreview, dp as DownloadStatus, x as Teleport, dq as onErrorCaptured, B as normalizeProps, b as reactive, a as readonly, dr as tryOnScopeDispose, ds as createStaticVNode, dt as useTemplateRef, du as watchEffect, dv as marked, dw as purify, dx as _Renderer, dy as mapKeys, bt as h, dz as render$4, dA as qs, dB as c, dC as B, dD as h$1, dE as Le, dF as Ws, dG as yo, dH as Co, dI as $s, dJ as QuickLRU, dK as diff, dL as partition, dM as get$1, dN as pickBy, dO as v4, dP as groupBy, dQ as useStorage, dR as uniqBy, dS as isNil, dT as isEmpty, dU as find, g as getCurrentInstance, dV as Suspense, dW as useThrottleFn, dX as useCssVars, dY as useBreakpoints, dZ as breakpointsTailwind, d_ as toValue$1, d$ as useId, e0 as stubTrue, e1 as liteClient, e2 as omit, e3 as orderBy, e4 as flatMap, e5 as filter, e6 as map, e7 as some, e8 as useFavicon, e9 as initializeApp, ea as createApp } from "./vendor-other-DdEHkzYa.js";
import { d as defineStore, c as createI18n, u as useI18n, a as useFirebaseAuth, s as storeToRefs, b as createRouter, e as createWebHashHistory, f as createWebHistory, g as createPinia, i as init, V as VueFire, h as VueFireAuth } from "./vendor-vue-BIRc7P93.js";
import { a as addonFitExports, x as xtermExports } from "./vendor-xterm-BF8peZ5_.js";
import { A as AnimationMixer, L as LoopRepeat, P as PerspectiveCamera, O as OrthographicCamera, V as Vector3, a as OrbitControls, b as AmbientLight, D as DirectionalLight, B as BufferGeometry, c as BufferAttribute, G as GLTFLoader, d as OBJLoader, M as MTLLoader, F as FBXLoader, S as STLLoader, e as PLYLoader, f as Mesh, g as SkinnedMesh, h as Group, i as SplatMesh, j as PointsMaterial, k as Points, l as MeshStandardMaterial, m as DoubleSide, n as GLTFExporter, o as OBJExporter, p as STLExporter, T as TextureLoader, q as SpriteMaterial, r as Sprite, s as Scene, t as GridHelper, u as PlaneGeometry, v as MeshBasicMaterial, C as Color, w as Material, x as Texture, y as SRGBColorSpace, E as EquirectangularReflectionMapping, N as NoToneMapping, z as MeshNormalMaterial, H as Vector2, I as MeshDepthMaterial, J as BasicDepthPacking, K as Box3, Q as LinearSRGBColorSpace, R as Light, U as Camera, W as ViewHelper, X as Clock, Y as WebGLRenderer } from "./vendor-three-_gcZkhit.js";
import { M as Markdown, E as Editor, S as StarterKit, L as Link, T as Table, a as TableCell, b as TableHeader, c as TableRow } from "./vendor-tiptap-B76LFN34.js";
(/* @__PURE__ */ __name(function polyfill() {
  const relList = document.createElement("link").relList;
  if (relList && relList.supports && relList.supports("modulepreload")) return;
  for (const link of document.querySelectorAll('link[rel="modulepreload"]')) processPreload(link);
  new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      if (mutation.type !== "childList") continue;
      for (const node2 of mutation.addedNodes) if (node2.tagName === "LINK" && node2.rel === "modulepreload") processPreload(node2);
    }
  }).observe(document, {
    childList: true,
    subtree: true
  });
  function getFetchOpts(link) {
    const fetchOpts = {};
    if (link.integrity) fetchOpts.integrity = link.integrity;
    if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;
    if (link.crossOrigin === "use-credentials") fetchOpts.credentials = "include";
    else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";
    else fetchOpts.credentials = "same-origin";
    return fetchOpts;
  }
  __name(getFetchOpts, "getFetchOpts");
  function processPreload(link) {
    if (link.ep) return;
    link.ep = true;
    const fetchOpts = getFetchOpts(link);
    fetch(link.href, fetchOpts);
  }
  __name(processPreload, "processPreload");
}, "polyfill"))();
function isElectron() {
  return "electronAPI" in window && window.electronAPI !== void 0;
}
__name(isElectron, "isElectron");
function electronAPI() {
  return window.electronAPI;
}
__name(electronAPI, "electronAPI");
function showNativeSystemMenu() {
  electronAPI()?.showContextMenu();
}
__name(showNativeSystemMenu, "showNativeSystemMenu");
function isNativeWindow() {
  return isElectron() && !!window.navigator.windowControlsOverlay?.visible;
}
__name(isNativeWindow, "isNativeWindow");
const DISTRIBUTION = "localhost";
const isDesktop = isElectron();
const isCloud = DISTRIBUTION === "cloud";
const remoteConfig = ref({});
function configValueOrDefault(remoteConfig2, key, defaultValue) {
  const configValue = remoteConfig2[key];
  return configValue || defaultValue;
}
__name(configValueOrDefault, "configValueOrDefault");
const DEV_CONFIG = {
  apiKey: "AIzaSyDa_YMeyzV0SkVe92vBZ1tVikWBmOU5KVE",
  authDomain: "dreamboothy-dev.firebaseapp.com",
  databaseURL: "https://dreamboothy-dev-default-rtdb.firebaseio.com",
  projectId: "dreamboothy-dev",
  storageBucket: "dreamboothy-dev.appspot.com",
  messagingSenderId: "313257147182",
  appId: "1:313257147182:web:be38f6ebf74345fc7618bf",
  measurementId: "G-YEVSMYXSPY"
};
const BUILD_TIME_CONFIG = DEV_CONFIG;
function getFirebaseConfig() {
  {
    return BUILD_TIME_CONFIG;
  }
}
__name(getFirebaseConfig, "getFirebaseConfig");
const supports_preview_metadata = true;
const supports_manager_v4_ui = true;
const defaultClientFeatureFlags = {
  supports_preview_metadata,
  supports_manager_v4_ui
};
const useToastStore = defineStore("toast", () => {
  const messagesToAdd = ref([]);
  const messagesToRemove = ref([]);
  const removeAllRequested = ref(false);
  function add2(message) {
    messagesToAdd.value = [...messagesToAdd.value, message];
  }
  __name(add2, "add");
  function remove2(message) {
    messagesToRemove.value = [...messagesToRemove.value, message];
  }
  __name(remove2, "remove");
  function removeAll() {
    removeAllRequested.value = true;
  }
  __name(removeAll, "removeAll");
  function addAlert(message) {
    add2({ severity: "warn", summary: "Alert", detail: message });
  }
  __name(addAlert, "addAlert");
  return {
    messagesToAdd,
    messagesToRemove,
    removeAllRequested,
    add: add2,
    remove: remove2,
    removeAll,
    addAlert
  };
});
async function fetchHistoryV1(fetchApi, maxItems = 200, offset) {
  if (offset !== void 0 && (offset < 0 || !Number.isInteger(offset))) {
    throw new Error(
      `Invalid offset parameter: ${offset}. Must be a non-negative integer.`
    );
  }
  const params = new URLSearchParams({ max_items: maxItems.toString() });
  if (offset !== void 0) {
    params.set("offset", offset.toString());
  }
  const url = `/history?${params.toString()}`;
  const res = await fetchApi(url);
  const json = await res.json();
  return {
    History: Object.values(json).map((item) => ({
      ...item,
      taskType: "History"
    }))
  };
}
__name(fetchHistoryV1, "fetchHistoryV1");
const fetchHistory = fetchHistoryV1;
class UnauthorizedError extends Error {
  static {
    __name(this, "UnauthorizedError");
  }
}
function addHeaderEntry(headers, key, value) {
  if (Array.isArray(headers)) {
    headers.push([key, value]);
  } else if (headers instanceof Headers) {
    headers.set(key, value);
  } else {
    headers[key] = value;
  }
}
__name(addHeaderEntry, "addHeaderEntry");
class PromptExecutionError extends Error {
  static {
    __name(this, "PromptExecutionError");
  }
  response;
  constructor(response) {
    super("Prompt execution failed");
    this.response = response;
  }
  toString() {
    let message = "";
    if (typeof this.response.error === "string") {
      message += this.response.error;
    } else if (this.response.error) {
      message += this.response.error.message + ": " + this.response.error.details;
    }
    for (const [_, nodeError] of Object.entries(
      this.response.node_errors ?? []
    )) {
      message += "\n" + nodeError.class_type + ":";
      for (const errorReason of nodeError.errors) {
        message += "\n    - " + errorReason.message + ": " + errorReason.details;
      }
    }
    return message;
  }
}
class ComfyApi extends EventTarget {
  static {
    __name(this, "ComfyApi");
  }
  #registered = /* @__PURE__ */ new Set();
  api_host;
  api_base;
  /**
   * The client id from the initial session storage.
   */
  initialClientId;
  /**
   * The current client id from websocket status updates.
   */
  clientId;
  /**
   * The current user id.
   */
  user;
  socket = null;
  /**
   * Cache Firebase auth store composable function.
   */
  authStoreComposable;
  reportedUnknownMessageTypes = /* @__PURE__ */ new Set();
  /**
   * Get feature flags supported by this frontend client.
   * Returns a copy to prevent external modification.
   */
  getClientFeatureFlags() {
    return { ...defaultClientFeatureFlags };
  }
  /**
   * Feature flags received from the backend server.
   */
  serverFeatureFlags = {};
  /**
   * The auth token for the comfy org account if the user is logged in.
   * This is only used for {@link queuePrompt} now. It is not directly
   * passed as parameter to the function because some custom nodes are hijacking
   * {@link queuePrompt} improperly, which causes extra parameters to be lost
   * in the function call chain.
   *
   * Ref: https://cs.comfy.org/search?q=context:global+%22api.queuePrompt+%3D%22&patternType=keyword&sm=0
   *
   * TODO: Move this field to parameter of {@link queuePrompt} once all
   * custom nodes are patched.
   */
  authToken;
  /**
   * The API key for the comfy org account if the user logged in via API key.
   */
  apiKey;
  constructor() {
    super();
    this.user = "";
    this.api_host = location.host;
    this.api_base = location.pathname.split("/").slice(0, -1).join("/");
    this.initialClientId = sessionStorage.getItem("clientId");
  }
  internalURL(route) {
    return this.api_base + "/internal" + route;
  }
  apiURL(route) {
    return this.api_base + "/api" + route;
  }
  fileURL(route) {
    return this.api_base + route;
  }
  /**
   * Gets the Firebase auth store instance using cached composable function.
   * Caches the composable function on first call, then reuses it.
   * Returns null for non-cloud distributions.
   * @returns The Firebase auth store instance, or null if not in cloud
   */
  async getAuthStore() {
  }
  /**
   * Waits for Firebase auth to be initialized before proceeding.
   * Includes 10-second timeout to prevent infinite hanging.
   */
  async waitForAuthInitialization() {
  }
  async fetchApi(route, options) {
    const headers = options?.headers ?? {};
    addHeaderEntry(headers, "Comfy-User", this.user);
    return fetch(this.apiURL(route), {
      cache: "no-cache",
      ...options,
      headers
    });
  }
  addEventListener(type, callback, options) {
    super.addEventListener(type, callback, options);
    this.#registered.add(type);
  }
  removeEventListener(type, callback, options) {
    super.removeEventListener(type, callback, options);
  }
  dispatchCustomEvent(type, detail) {
    const event = detail === void 0 ? new CustomEvent(type) : new CustomEvent(type, { detail });
    return super.dispatchEvent(event);
  }
  /** @deprecated Use {@link dispatchCustomEvent}. */
  dispatchEvent(event) {
    return super.dispatchEvent(event);
  }
  /**
   * Poll status  for colab and other things that don't support websockets.
   */
  #pollQueue() {
    setInterval(async () => {
      try {
        const resp = await this.fetchApi("/prompt");
        const status = await resp.json();
        this.dispatchCustomEvent("status", status);
      } catch (error) {
        this.dispatchCustomEvent("status", null);
      }
    }, 1e3);
  }
  /**
   * Creates and connects a WebSocket for realtime updates
   * @param {boolean} isReconnect If the socket is connection is a reconnect attempt
   */
  async createSocket(isReconnect) {
    if (this.socket) {
      return;
    }
    let opened = false;
    let existingSession = window.name;
    const params = new URLSearchParams();
    if (existingSession) {
      params.set("clientId", existingSession);
    }
    const protocol = window.location.protocol === "https:" ? "wss" : "ws";
    const baseUrl = `${protocol}://${this.api_host}${this.api_base}/ws`;
    const query = params.toString();
    const wsUrl = query ? `${baseUrl}?${query}` : baseUrl;
    this.socket = new WebSocket(wsUrl);
    this.socket.binaryType = "arraybuffer";
    this.socket.addEventListener("open", () => {
      opened = true;
      this.socket.send(
        JSON.stringify({
          type: "feature_flags",
          data: this.getClientFeatureFlags()
        })
      );
      if (isReconnect) {
        this.dispatchCustomEvent("reconnected");
      }
    });
    this.socket.addEventListener("error", () => {
      if (this.socket) this.socket.close();
      if (!isReconnect && !opened) {
        this.#pollQueue();
      }
    });
    this.socket.addEventListener("close", () => {
      setTimeout(async () => {
        this.socket = null;
        await this.createSocket(true);
      }, 300);
      if (opened) {
        this.dispatchCustomEvent("status", null);
        this.dispatchCustomEvent("reconnecting");
      }
    });
    this.socket.addEventListener("message", (event) => {
      try {
        if (event.data instanceof ArrayBuffer) {
          const view = new DataView(event.data);
          const eventType = view.getUint32(0);
          let imageMime;
          switch (eventType) {
            case 3:
              const decoder = new TextDecoder();
              const data = event.data.slice(4);
              const nodeIdLength = view.getUint32(4);
              this.dispatchCustomEvent("progress_text", {
                nodeId: decoder.decode(data.slice(4, 4 + nodeIdLength)),
                text: decoder.decode(data.slice(4 + nodeIdLength))
              });
              break;
            case 1:
              const imageType = view.getUint32(4);
              const imageData = event.data.slice(8);
              switch (imageType) {
                case 2:
                  imageMime = "image/png";
                  break;
                case 1:
                default:
                  imageMime = "image/jpeg";
                  break;
              }
              const imageBlob = new Blob([imageData], {
                type: imageMime
              });
              this.dispatchCustomEvent("b_preview", imageBlob);
              break;
            case 4:
              const decoder4 = new TextDecoder();
              const metadataLength = view.getUint32(4);
              const metadataBytes = event.data.slice(8, 8 + metadataLength);
              const metadata = JSON.parse(decoder4.decode(metadataBytes));
              const imageData4 = event.data.slice(8 + metadataLength);
              let imageMime4 = metadata.image_type;
              const imageBlob4 = new Blob([imageData4], {
                type: imageMime4
              });
              this.dispatchCustomEvent("b_preview_with_metadata", {
                blob: imageBlob4,
                nodeId: metadata.node_id,
                displayNodeId: metadata.display_node_id,
                parentNodeId: metadata.parent_node_id,
                realNodeId: metadata.real_node_id,
                promptId: metadata.prompt_id
              });
              this.dispatchCustomEvent("b_preview", imageBlob4);
              break;
            default:
              throw new Error(
                `Unknown binary websocket message of type ${eventType}`
              );
          }
        } else {
          const msg = JSON.parse(event.data);
          switch (msg.type) {
            case "status":
              if (msg.data.sid) {
                const clientId = msg.data.sid;
                this.clientId = clientId;
                window.name = clientId;
                sessionStorage.setItem("clientId", clientId);
              }
              this.dispatchCustomEvent("status", msg.data.status ?? null);
              break;
            case "executing":
              this.dispatchCustomEvent(
                "executing",
                msg.data.display_node || msg.data.node
              );
              break;
            case "execution_start":
            case "execution_error":
            case "execution_interrupted":
            case "execution_cached":
            case "execution_success":
            case "progress":
            case "progress_state":
            case "executed":
            case "graphChanged":
            case "promptQueued":
            case "logs":
            case "b_preview":
            case "notification":
              this.dispatchCustomEvent(msg.type, msg.data);
              break;
            case "feature_flags":
              this.serverFeatureFlags = msg.data;
              console.log(
                "Server feature flags received:",
                this.serverFeatureFlags
              );
              break;
            default:
              if (this.#registered.has(msg.type)) {
                super.dispatchEvent(
                  new CustomEvent(msg.type, { detail: msg.data })
                );
              } else if (!this.reportedUnknownMessageTypes.has(msg.type)) {
                this.reportedUnknownMessageTypes.add(msg.type);
                throw new Error(`Unknown message type ${msg.type}`);
              }
          }
        }
      } catch (error) {
        console.warn("Unhandled message:", event.data, error);
      }
    });
  }
  /**
   * Initialises sockets and realtime updates
   */
  init() {
    this.createSocket();
  }
  /**
   * Gets a list of extension urls
   */
  async getExtensions() {
    const resp = await this.fetchApi("/extensions", { cache: "no-store" });
    return await resp.json();
  }
  /**
   * Gets the available workflow templates from custom nodes.
   * @returns A map of custom_node names and associated template workflow names.
   */
  async getWorkflowTemplates() {
    const res = await this.fetchApi("/workflow_templates");
    return await res.json();
  }
  /**
   * Gets the index of core workflow templates.
   * @param locale Optional locale code (e.g., 'en', 'fr', 'zh') to load localized templates
   */
  async getCoreWorkflowTemplates(locale) {
    const fileName = locale && locale !== "en" ? `index.${locale}.json` : "index.json";
    try {
      const res = await axios.get(this.fileURL(`/templates/${fileName}`));
      const contentType = res.headers["content-type"];
      return contentType?.includes("application/json") ? res.data : [];
    } catch (error) {
      if (locale && locale !== "en") {
        console.warn(
          `Localized templates for '${locale}' not found, falling back to English`
        );
        return this.getCoreWorkflowTemplates();
      }
      console.error("Error loading core workflow templates:", error);
      return [];
    }
  }
  /**
   * Gets a list of embedding names
   */
  async getEmbeddings() {
    const resp = await this.fetchApi("/embeddings", { cache: "no-store" });
    return await resp.json();
  }
  /**
   * Loads node object definitions for the graph
   * @returns The node definitions
   */
  async getNodeDefs() {
    const resp = await this.fetchApi("/object_info", { cache: "no-store" });
    return await resp.json();
  }
  /**
   * Queues a prompt to be executed
   * @param {number} number The index at which to queue the prompt, passing -1 will insert the prompt at the front of the queue
   * @param {object} data The prompt data to queue
   * @param {QueuePromptOptions} options Optional execution options
   * @throws {PromptExecutionError} If the prompt fails to execute
   */
  async queuePrompt(number, data, options) {
    const { output: prompt, workflow } = data;
    const body = {
      client_id: this.clientId ?? "",
      // TODO: Unify clientId access
      prompt,
      ...options?.partialExecutionTargets && {
        partial_execution_targets: options.partialExecutionTargets
      },
      extra_data: {
        auth_token_comfy_org: this.authToken,
        api_key_comfy_org: this.apiKey,
        extra_pnginfo: { workflow },
        ...options?.previewMethod && options.previewMethod !== "default" && {
          preview_method: options.previewMethod
        }
      }
    };
    if (number === -1) {
      body.front = true;
    } else if (number != 0) {
      body.number = number;
    }
    const res = await this.fetchApi("/prompt", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    });
    if (res.status !== 200) {
      throw new PromptExecutionError(await res.json());
    }
    return await res.json();
  }
  /**
   * Gets a list of model folder keys (eg ['checkpoints', 'loras', ...])
   * @returns The list of model folder keys
   */
  async getModelFolders() {
    const res = await this.fetchApi(`/experiment/models`);
    if (res.status === 404) {
      return [];
    }
    const folderBlacklist = ["configs", "custom_nodes"];
    return (await res.json()).filter(
      (folder) => !folderBlacklist.includes(folder.name)
    );
  }
  /**
   * Gets a list of models in the specified folder
   * @param {string} folder The folder to list models from, such as 'checkpoints'
   * @returns The list of model filenames within the specified folder
   */
  async getModels(folder) {
    const res = await this.fetchApi(`/experiment/models/${folder}`);
    if (res.status === 404) {
      return [];
    }
    return await res.json();
  }
  /**
   * Gets the metadata for a model
   * @param {string} folder The folder containing the model
   * @param {string} model The model to get metadata for
   * @returns The metadata for the model
   */
  async viewMetadata(folder, model) {
    const res = await this.fetchApi(
      `/view_metadata/${folder}?filename=${encodeURIComponent(model)}`
    );
    const rawResponse = await res.text();
    if (!rawResponse) {
      return null;
    }
    try {
      return JSON.parse(rawResponse);
    } catch (error) {
      console.error(
        "Error viewing metadata",
        res.status,
        res.statusText,
        rawResponse,
        error
      );
      return null;
    }
  }
  /**
   * Loads a list of items (queue or history)
   * @param {string} type The type of items to load, queue or history
   * @returns The items of the specified type grouped by their status
   */
  async getItems(type) {
    if (type === "queue") {
      return this.getQueue();
    }
    return this.getHistory();
  }
  /**
   * Gets the current state of the queue
   * @returns The currently running and queued items
   */
  async getQueue() {
    try {
      const res = await this.fetchApi("/queue");
      const data = await res.json();
      const normalizeQueuePrompt = /* @__PURE__ */ __name((prompt) => {
        if (!Array.isArray(prompt)) return prompt;
        const p = prompt.slice(0, 5);
        const fourth = p[3];
        const fifth = p[4];
        if (Array.isArray(fourth) && fifth && typeof fifth === "object" && !Array.isArray(fifth)) {
          const meta = fifth;
          const extraData = { ...meta };
          return [p[0], p[1], p[2], extraData, fourth];
        }
        return p;
      }, "normalizeQueuePrompt");
      return {
        // Running action uses a different endpoint for cancelling
        Running: data.queue_running.map((prompt) => {
          const np = normalizeQueuePrompt(prompt);
          return {
            taskType: "Running",
            prompt: np,
            // prompt[1] is the prompt id
            remove: { name: "Cancel", cb: /* @__PURE__ */ __name(() => api.interrupt(np[1]), "cb") }
          };
        }),
        Pending: data.queue_pending.map((prompt) => ({
          taskType: "Pending",
          prompt: normalizeQueuePrompt(prompt)
        }))
      };
    } catch (error) {
      console.error(error);
      return { Running: [], Pending: [] };
    }
  }
  /**
   * Gets the prompt execution history
   * @returns Prompt history including node outputs
   */
  async getHistory(max_items = 200, options) {
    try {
      return await fetchHistory(
        this.fetchApi.bind(this),
        max_items,
        options?.offset
      );
    } catch (error) {
      console.error(error);
      return { History: [] };
    }
  }
  /**
   * Gets system & device stats
   * @returns System stats such as python version, OS, per device info
   */
  async getSystemStats() {
    const res = await this.fetchApi("/system_stats");
    return await res.json();
  }
  /**
   * Sends a POST request to the API
   * @param {*} type The endpoint to post to
   * @param {*} body Optional POST data
   */
  async #postItem(type, body) {
    try {
      await this.fetchApi("/" + type, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: body ? JSON.stringify(body) : void 0
      });
    } catch (error) {
      console.error(error);
    }
  }
  /**
   * Deletes an item from the specified list
   * @param {string} type The type of item to delete, queue or history
   * @param {number} id The id of the item to delete
   */
  async deleteItem(type, id2) {
    await this.#postItem(type, { delete: [id2] });
  }
  /**
   * Clears the specified list
   * @param {string} type The type of list to clear, queue or history
   */
  async clearItems(type) {
    await this.#postItem(type, { clear: true });
  }
  /**
   * Interrupts the execution of the running prompt. If runningPromptId is provided,
   * it is included in the payload as a helpful hint to the backend.
   * @param {string | null} [runningPromptId] Optional Running Prompt ID to interrupt
   */
  async interrupt(runningPromptId) {
    await this.#postItem(
      "interrupt",
      runningPromptId ? { prompt_id: runningPromptId } : void 0
    );
  }
  /**
   * Gets user configuration data and where data should be stored
   */
  async getUserConfig() {
    return (await this.fetchApi("/users")).json();
  }
  /**
   * Creates a new user
   * @param { string } username
   * @returns The fetch response
   */
  createUser(username) {
    return this.fetchApi("/users", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ username })
    });
  }
  /**
   * Gets all setting values for the current user
   * @returns { Promise<string, unknown> } A dictionary of id -> value
   */
  async getSettings() {
    const resp = await this.fetchApi("/settings");
    if (resp.status == 401) {
      throw new UnauthorizedError(resp.statusText);
    }
    return await resp.json();
  }
  /**
   * Gets a setting for the current user
   * @param { string } id The id of the setting to fetch
   * @returns { Promise<unknown> } The setting value
   */
  async getSetting(id2) {
    return (await this.fetchApi(`/settings/${encodeURIComponent(id2)}`)).json();
  }
  /**
   * Stores a dictionary of settings for the current user
   */
  async storeSettings(settings) {
    return this.fetchApi(`/settings`, {
      method: "POST",
      body: JSON.stringify(settings)
    });
  }
  /**
   * Stores a setting for the current user
   */
  async storeSetting(id2, value) {
    return this.fetchApi(`/settings/${encodeURIComponent(id2)}`, {
      method: "POST",
      body: JSON.stringify(value)
    });
  }
  /**
   * Gets a user data file for the current user
   */
  async getUserData(file, options) {
    return this.fetchApi(`/userdata/${encodeURIComponent(file)}`, options);
  }
  /**
   * Stores a user data file for the current user
   * @param { string } file The name of the userdata file to save
   * @param { unknown } data The data to save to the file
   * @param { RequestInit & { stringify?: boolean, throwOnError?: boolean } } [options]
   * @returns { Promise<Response> }
   */
  async storeUserData(file, data, options = {
    overwrite: true,
    stringify: true,
    throwOnError: true,
    full_info: false
  }) {
    const resp = await this.fetchApi(
      `/userdata/${encodeURIComponent(file)}?overwrite=${options.overwrite}&full_info=${options.full_info}`,
      {
        method: "POST",
        body: options?.stringify ? JSON.stringify(data) : data,
        ...options
      }
    );
    if (resp.status !== 200 && options.throwOnError !== false) {
      throw new Error(
        `Error storing user data file '${file}': ${resp.status} ${(await resp).statusText}`
      );
    }
    return resp;
  }
  /**
   * Deletes a user data file for the current user
   * @param { string } file The name of the userdata file to delete
   */
  async deleteUserData(file) {
    const resp = await this.fetchApi(`/userdata/${encodeURIComponent(file)}`, {
      method: "DELETE"
    });
    return resp;
  }
  /**
   * Move a user data file for the current user
   * @param { string } source The userdata file to move
   * @param { string } dest The destination for the file
   */
  async moveUserData(source, dest, options = { overwrite: false }) {
    const resp = await this.fetchApi(
      `/userdata/${encodeURIComponent(source)}/move/${encodeURIComponent(dest)}?overwrite=${options?.overwrite}`,
      {
        method: "POST"
      }
    );
    return resp;
  }
  async listUserDataFullInfo(dir) {
    const trimmedDir = trimEnd(dir, "/");
    const resp = await this.fetchApi(
      `/userdata?dir=${encodeURIComponent(trimmedDir)}&recurse=true&split=false&full_info=true`
    );
    if (resp.status === 404) return [];
    if (resp.status !== 200) {
      throw new Error(
        `Error getting user data list '${trimmedDir}': ${resp.status} ${resp.statusText}`
      );
    }
    return resp.json();
  }
  async getGlobalSubgraphData(id2) {
    const resp = await api.fetchApi("/global_subgraphs/" + id2);
    if (resp.status !== 200) return "";
    const subgraph = await resp.json();
    return subgraph?.data ?? "";
  }
  async getGlobalSubgraphs() {
    const resp = await api.fetchApi("/global_subgraphs");
    if (resp.status !== 200) return {};
    const subgraphs = await resp.json();
    for (const [k, v2] of Object.entries(subgraphs)) {
      if (!v2.data) v2.data = this.getGlobalSubgraphData(k);
    }
    return subgraphs;
  }
  async getLogs() {
    const url = this.internalURL("/logs");
    return (await axios.get(url)).data;
  }
  async getRawLogs() {
    const url = this.internalURL("/logs/raw");
    return (await axios.get(url)).data;
  }
  async subscribeLogs(enabled) {
    const url = this.internalURL("/logs/subscribe");
    return await axios.patch(url, {
      enabled,
      clientId: this.clientId
    });
  }
  async getFolderPaths() {
    const response = await axios.get(this.internalURL("/folder_paths")).catch(() => null);
    if (!response) {
      return {};
    }
    return response.data;
  }
  /* Frees memory by unloading models and optionally freeing execution cache
   * @param {Object} options - The options object
   * @param {boolean} options.freeExecutionCache - If true, also frees execution cache
   */
  async freeMemory(options) {
    try {
      let mode = "";
      if (options.freeExecutionCache) {
        mode = '{"unload_models": true, "free_memory": true}';
      } else {
        mode = '{"unload_models": true}';
      }
      const res = await this.fetchApi(`/free`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: mode
      });
      if (res.status === 200) {
        if (options.freeExecutionCache) {
          useToastStore().add({
            severity: "success",
            summary: "Models and Execution Cache have been cleared.",
            life: 3e3
          });
        } else {
          useToastStore().add({
            severity: "success",
            summary: "Models have been unloaded.",
            life: 3e3
          });
        }
      } else {
        useToastStore().add({
          severity: "error",
          summary: "Unloading of models failed. Installed ComfyUI may be an outdated version.",
          life: 5e3
        });
      }
    } catch (error) {
      useToastStore().add({
        severity: "error",
        summary: "An error occurred while trying to unload models.",
        life: 5e3
      });
    }
  }
  /**
   * Gets the custom nodes i18n data from the server.
   *
   * @returns The custom nodes i18n data
   */
  async getCustomNodesI18n() {
    return (await axios.get(this.apiURL("/i18n"))).data;
  }
  /**
   * Checks if the server supports a specific feature.
   * @param featureName The name of the feature to check (supports dot notation for nested values)
   * @returns true if the feature is supported, false otherwise
   */
  serverSupportsFeature(featureName) {
    return get(this.serverFeatureFlags, featureName) === true;
  }
  /**
   * Gets a server feature flag value.
   * @param featureName The name of the feature to get (supports dot notation for nested values)
   * @param defaultValue The default value if the feature is not found
   * @returns The feature value or default
   */
  getServerFeature(featureName, defaultValue) {
    return get(this.serverFeatureFlags, featureName, defaultValue);
  }
  /**
   * Gets all server feature flags.
   * @returns Copy of all server feature flags
   */
  getServerFeatures() {
    return { ...this.serverFeatureFlags };
  }
}
const api = new ComfyApi();
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.api = window.comfyAPI.api || {};
window.comfyAPI.api.UnauthorizedError = UnauthorizedError;
window.comfyAPI.api.PromptExecutionError = PromptExecutionError;
window.comfyAPI.api.ComfyApi = ComfyApi;
window.comfyAPI.api.api = api;
function cn(...inputs) {
  return twMerge(clsx(inputs));
}
__name(cn, "cn");
const buttonVariants = cva({
  base: "relative inline-flex items-center justify-center gap-2 cursor-pointer whitespace-nowrap appearance-none border-none rounded-md text-sm font-medium font-inter transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  variants: {
    variant: {
      secondary: "bg-secondary-background text-secondary-foreground hover:bg-secondary-background-hover",
      primary: "bg-primary-background text-base-foreground hover:bg-primary-background-hover",
      inverted: "bg-base-foreground text-base-background hover:bg-base-foreground/80",
      destructive: "bg-destructive-background text-base-foreground hover:bg-destructive-background-hover",
      textonly: "text-base-foreground bg-transparent hover:bg-secondary-background-hover",
      "muted-textonly": "text-muted-foreground bg-transparent hover:bg-secondary-background-hover",
      "destructive-textonly": "text-destructive-background bg-transparent hover:bg-destructive-background/10"
    },
    size: {
      sm: "h-6 rounded-sm px-2 py-1 text-xs",
      md: "h-8 rounded-lg p-2 text-xs",
      lg: "h-10 rounded-lg px-4 py-2 text-sm",
      icon: "size-8",
      "icon-sm": "size-5 p-0"
    }
  },
  defaultVariants: {
    variant: "secondary",
    size: "md"
  }
});
const _hoisted_1$26 = {
  key: 0,
  class: "pi pi-spin pi-spinner"
};
const _sfc_main$2A = /* @__PURE__ */ defineComponent({
  __name: "Button",
  props: {
    variant: {},
    size: {},
    class: { default: "" },
    loading: { type: Boolean, default: false },
    disabled: { type: Boolean, default: false },
    asChild: { type: Boolean },
    as: { default: "button" }
  },
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(Primitive), {
        as: _ctx.as,
        "as-child": _ctx.asChild,
        disabled: _ctx.disabled || _ctx.loading,
        class: normalizeClass(unref(cn)(unref(buttonVariants)({ variant: _ctx.variant, size: _ctx.size }), __props.class))
      }, {
        default: withCtx(() => [
          _ctx.loading ? (openBlock(), createElementBlock("i", _hoisted_1$26)) : createCommentVNode("", true),
          !_ctx.loading ? renderSlot(_ctx.$slots, "default", { key: 1 }) : createCommentVNode("", true)
        ]),
        _: 3
      }, 8, ["as", "as-child", "disabled", "class"]);
    };
  }
});
const Comfy_3DViewer_Open3DViewer = { "label": "Open 3D Viewer (Beta) for Selected Node" };
const Comfy_BrowseModelAssets = { "label": "Experimental: Browse Model Assets" };
const Comfy_BrowseTemplates = { "label": "Browse Templates" };
const Comfy_Canvas_DeleteSelectedItems = { "label": "Delete Selected Items" };
const Comfy_Canvas_FitView = { "label": "Fit view to selected nodes" };
const Comfy_Canvas_Lock = { "label": "Lock Canvas" };
const Comfy_Canvas_MoveSelectedNodes_Down = { "label": "Move Selected Nodes Down" };
const Comfy_Canvas_MoveSelectedNodes_Left = { "label": "Move Selected Nodes Left" };
const Comfy_Canvas_MoveSelectedNodes_Right = { "label": "Move Selected Nodes Right" };
const Comfy_Canvas_MoveSelectedNodes_Up = { "label": "Move Selected Nodes Up" };
const Comfy_Canvas_ResetView = { "label": "Reset View" };
const Comfy_Canvas_Resize = { "label": "Resize Selected Nodes" };
const Comfy_Canvas_ToggleLinkVisibility = { "label": "Canvas Toggle Link Visibility" };
const Comfy_Canvas_ToggleLock = { "label": "Canvas Toggle Lock" };
const Comfy_Canvas_ToggleMinimap = { "label": "Canvas Toggle Minimap" };
const Comfy_Canvas_ToggleSelected_Pin = { "label": "Pin/Unpin Selected Items" };
const Comfy_Canvas_ToggleSelectedNodes_Bypass = { "label": "Bypass/Unbypass Selected Nodes" };
const Comfy_Canvas_ToggleSelectedNodes_Collapse = { "label": "Collapse/Expand Selected Nodes" };
const Comfy_Canvas_ToggleSelectedNodes_Mute = { "label": "Mute/Unmute Selected Nodes" };
const Comfy_Canvas_ToggleSelectedNodes_Pin = { "label": "Pin/Unpin Selected Nodes" };
const Comfy_Canvas_Unlock = { "label": "Unlock Canvas" };
const Comfy_Canvas_ZoomIn = { "label": "Zoom In" };
const Comfy_Canvas_ZoomOut = { "label": "Zoom Out" };
const Comfy_ClearPendingTasks = { "label": "Clear Pending Tasks" };
const Comfy_ClearWorkflow = { "label": "Clear Workflow" };
const Comfy_ContactSupport = { "label": "Contact Support" };
const Comfy_Dev_ShowModelSelector = { "label": "Show Model Selector (Dev)" };
const Comfy_DuplicateWorkflow = { "label": "Duplicate Current Workflow" };
const Comfy_ExportWorkflow = { "label": "Export Workflow" };
const Comfy_ExportWorkflowAPI = { "label": "Export Workflow (API Format)" };
const Comfy_Graph_ConvertToSubgraph = { "label": "Convert Selection to Subgraph" };
const Comfy_Graph_EditSubgraphWidgets = { "label": "Edit Subgraph Widgets" };
const Comfy_Graph_ExitSubgraph = { "label": "Exit Subgraph" };
const Comfy_Graph_FitGroupToContents = { "label": "Fit Group To Contents" };
const Comfy_Graph_GroupSelectedNodes = { "label": "Group Selected Nodes" };
const Comfy_Graph_ToggleWidgetPromotion = { "label": "Toggle promotion of hovered widget" };
const Comfy_Graph_UnpackSubgraph = { "label": "Unpack the selected Subgraph" };
const Comfy_GroupNode_ConvertSelectedNodesToGroupNode = { "label": "Convert selected nodes to group node" };
const Comfy_GroupNode_ManageGroupNodes = { "label": "Manage group nodes" };
const Comfy_GroupNode_UngroupSelectedGroupNodes = { "label": "Ungroup selected group nodes" };
const Comfy_Help_AboutComfyUI = { "label": "Open About ComfyUI" };
const Comfy_Help_OpenComfyOrgDiscord = { "label": "Open Comfy-Org Discord" };
const Comfy_Help_OpenComfyUIDocs = { "label": "Open ComfyUI Docs" };
const Comfy_Help_OpenComfyUIForum = { "label": "Open ComfyUI Forum" };
const Comfy_Help_OpenComfyUIIssues = { "label": "Open ComfyUI Issues" };
const Comfy_Interrupt = { "label": "Interrupt" };
const Comfy_LoadDefaultWorkflow = { "label": "Load Default Workflow" };
const Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu = { "label": "Custom Nodes Manager" };
const Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu = { "label": "Custom Nodes (Legacy)" };
const Comfy_Manager_ShowLegacyManagerMenu = { "label": "Manager Menu (Legacy)" };
const Comfy_Manager_ShowMissingPacks = { "label": "Install Missing Custom Nodes" };
const Comfy_Manager_ShowUpdateAvailablePacks = { "label": "Check for Custom Node Updates" };
const Comfy_Manager_ToggleManagerProgressDialog = { "label": "Toggle the Custom Nodes Manager Progress Bar" };
const Comfy_MaskEditor_BrushSize_Decrease = { "label": "Decrease Brush Size in MaskEditor" };
const Comfy_MaskEditor_BrushSize_Increase = { "label": "Increase Brush Size in MaskEditor" };
const Comfy_MaskEditor_OpenMaskEditor = { "label": "Open Mask Editor for Selected Node" };
const Comfy_Memory_UnloadModels = { "label": "Unload Models" };
const Comfy_Memory_UnloadModelsAndExecutionCache = { "label": "Unload Models and Execution Cache" };
const Comfy_NewBlankWorkflow = { "label": "New Blank Workflow" };
const Comfy_OpenClipspace = { "label": "Clipspace" };
const Comfy_OpenManagerDialog = { "label": "Manager" };
const Comfy_OpenWorkflow = { "label": "Open Workflow" };
const Comfy_PublishSubgraph = { "label": "Publish Subgraph" };
const Comfy_QueuePrompt = { "label": "Queue Prompt" };
const Comfy_QueuePromptFront = { "label": "Queue Prompt (Front)" };
const Comfy_QueueSelectedOutputNodes = { "label": "Queue Selected Output Nodes" };
const Comfy_Redo = { "label": "Redo" };
const Comfy_RefreshNodeDefinitions = { "label": "Refresh Node Definitions" };
const Comfy_SaveWorkflow = { "label": "Save Workflow" };
const Comfy_SaveWorkflowAs = { "label": "Save Workflow As" };
const Comfy_ShowSettingsDialog = { "label": "Show Settings Dialog" };
const Comfy_ToggleAssetAPI = { "label": "Experimental: Enable AssetAPI" };
const Comfy_ToggleCanvasInfo = { "label": "Canvas Performance" };
const Comfy_ToggleHelpCenter = { "label": "Help Center" };
const Comfy_ToggleLinear = { "label": "toggle linear mode" };
const Comfy_ToggleTheme = { "label": "Toggle Theme (Dark/Light)" };
const Comfy_Undo = { "label": "Undo" };
const Comfy_User_OpenSignInDialog = { "label": "Open Sign In Dialog" };
const Comfy_User_SignOut = { "label": "Sign Out" };
const Experimental_ToggleVueNodes = { "label": "Experimental: Enable Nodes 2.0" };
const Workspace_CloseWorkflow = { "label": "Close Current Workflow" };
const Workspace_NextOpenedWorkflow = { "label": "Next Opened Workflow" };
const Workspace_PreviousOpenedWorkflow = { "label": "Previous Opened Workflow" };
const Workspace_SearchBox_Toggle = { "label": "Toggle Search Box" };
const Workspace_ToggleBottomPanel = { "label": "Toggle Bottom Panel" };
const Workspace_ToggleBottomPanel_Shortcuts = { "label": "Show Keybindings Dialog" };
const Workspace_ToggleFocusMode = { "label": "Toggle Focus Mode" };
const Workspace_ToggleSidebarTab_assets = { "label": "Toggle Assets Sidebar", "tooltip": "Assets" };
const Workspace_ToggleSidebarTab_workflows = { "label": "Toggle Workflows Sidebar", "tooltip": "Workflows" };
const enCommands = {
  "Comfy-Desktop_CheckForUpdates": { "label": "Check for Updates" },
  "Comfy-Desktop_Folders_OpenCustomNodesFolder": { "label": "Open Custom Nodes Folder" },
  "Comfy-Desktop_Folders_OpenInputsFolder": { "label": "Open Inputs Folder" },
  "Comfy-Desktop_Folders_OpenLogsFolder": { "label": "Open Logs Folder" },
  "Comfy-Desktop_Folders_OpenModelConfig": { "label": "Open extra_model_paths.yaml" },
  "Comfy-Desktop_Folders_OpenModelsFolder": { "label": "Open Models Folder" },
  "Comfy-Desktop_Folders_OpenOutputsFolder": { "label": "Open Outputs Folder" },
  "Comfy-Desktop_OpenDevTools": { "label": "Open DevTools" },
  "Comfy-Desktop_OpenUserGuide": { "label": "Desktop User Guide" },
  "Comfy-Desktop_Quit": { "label": "Quit" },
  "Comfy-Desktop_Reinstall": { "label": "Reinstall" },
  "Comfy-Desktop_Restart": { "label": "Restart" },
  Comfy_3DViewer_Open3DViewer,
  Comfy_BrowseModelAssets,
  Comfy_BrowseTemplates,
  Comfy_Canvas_DeleteSelectedItems,
  Comfy_Canvas_FitView,
  Comfy_Canvas_Lock,
  Comfy_Canvas_MoveSelectedNodes_Down,
  Comfy_Canvas_MoveSelectedNodes_Left,
  Comfy_Canvas_MoveSelectedNodes_Right,
  Comfy_Canvas_MoveSelectedNodes_Up,
  Comfy_Canvas_ResetView,
  Comfy_Canvas_Resize,
  Comfy_Canvas_ToggleLinkVisibility,
  Comfy_Canvas_ToggleLock,
  Comfy_Canvas_ToggleMinimap,
  Comfy_Canvas_ToggleSelected_Pin,
  Comfy_Canvas_ToggleSelectedNodes_Bypass,
  Comfy_Canvas_ToggleSelectedNodes_Collapse,
  Comfy_Canvas_ToggleSelectedNodes_Mute,
  Comfy_Canvas_ToggleSelectedNodes_Pin,
  Comfy_Canvas_Unlock,
  Comfy_Canvas_ZoomIn,
  Comfy_Canvas_ZoomOut,
  Comfy_ClearPendingTasks,
  Comfy_ClearWorkflow,
  Comfy_ContactSupport,
  Comfy_Dev_ShowModelSelector,
  Comfy_DuplicateWorkflow,
  Comfy_ExportWorkflow,
  Comfy_ExportWorkflowAPI,
  Comfy_Graph_ConvertToSubgraph,
  Comfy_Graph_EditSubgraphWidgets,
  Comfy_Graph_ExitSubgraph,
  Comfy_Graph_FitGroupToContents,
  Comfy_Graph_GroupSelectedNodes,
  Comfy_Graph_ToggleWidgetPromotion,
  Comfy_Graph_UnpackSubgraph,
  Comfy_GroupNode_ConvertSelectedNodesToGroupNode,
  Comfy_GroupNode_ManageGroupNodes,
  Comfy_GroupNode_UngroupSelectedGroupNodes,
  Comfy_Help_AboutComfyUI,
  Comfy_Help_OpenComfyOrgDiscord,
  Comfy_Help_OpenComfyUIDocs,
  Comfy_Help_OpenComfyUIForum,
  Comfy_Help_OpenComfyUIIssues,
  Comfy_Interrupt,
  Comfy_LoadDefaultWorkflow,
  Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu,
  Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu,
  Comfy_Manager_ShowLegacyManagerMenu,
  Comfy_Manager_ShowMissingPacks,
  Comfy_Manager_ShowUpdateAvailablePacks,
  Comfy_Manager_ToggleManagerProgressDialog,
  Comfy_MaskEditor_BrushSize_Decrease,
  Comfy_MaskEditor_BrushSize_Increase,
  Comfy_MaskEditor_OpenMaskEditor,
  Comfy_Memory_UnloadModels,
  Comfy_Memory_UnloadModelsAndExecutionCache,
  Comfy_NewBlankWorkflow,
  Comfy_OpenClipspace,
  Comfy_OpenManagerDialog,
  Comfy_OpenWorkflow,
  Comfy_PublishSubgraph,
  Comfy_QueuePrompt,
  Comfy_QueuePromptFront,
  Comfy_QueueSelectedOutputNodes,
  Comfy_Redo,
  Comfy_RefreshNodeDefinitions,
  Comfy_SaveWorkflow,
  Comfy_SaveWorkflowAs,
  Comfy_ShowSettingsDialog,
  Comfy_ToggleAssetAPI,
  Comfy_ToggleCanvasInfo,
  Comfy_ToggleHelpCenter,
  Comfy_ToggleLinear,
  Comfy_ToggleTheme,
  Comfy_Undo,
  Comfy_User_OpenSignInDialog,
  Comfy_User_SignOut,
  Experimental_ToggleVueNodes,
  Workspace_CloseWorkflow,
  Workspace_NextOpenedWorkflow,
  Workspace_PreviousOpenedWorkflow,
  Workspace_SearchBox_Toggle,
  Workspace_ToggleBottomPanel,
  Workspace_ToggleBottomPanel_Shortcuts,
  "Workspace_ToggleBottomPanelTab_command-terminal": { "label": "Toggle Terminal Bottom Panel" },
  "Workspace_ToggleBottomPanelTab_logs-terminal": { "label": "Toggle Logs Bottom Panel" },
  "Workspace_ToggleBottomPanelTab_shortcuts-essentials": { "label": "Toggle Essential Bottom Panel" },
  "Workspace_ToggleBottomPanelTab_shortcuts-view-controls": { "label": "Toggle View Controls Bottom Panel" },
  Workspace_ToggleFocusMode,
  Workspace_ToggleSidebarTab_assets,
  "Workspace_ToggleSidebarTab_model-library": { "label": "Toggle Model Library Sidebar", "tooltip": "Model Library" },
  "Workspace_ToggleSidebarTab_node-library": { "label": "Toggle Node Library Sidebar", "tooltip": "Node Library" },
  Workspace_ToggleSidebarTab_workflows
};
const g = { "user": "User", "currentUser": "Current user", "empty": "Empty", "noWorkflowsFound": "No workflows found.", "comingSoon": "Coming Soon", "download": "Download", "downloadImage": "Download image", "downloadVideo": "Download video", "editOrMaskImage": "Edit or mask image", "editImage": "Edit image", "deleteImage": "Delete image", "deleteAudioFile": "Delete audio file", "removeImage": "Remove image", "removeVideo": "Remove video", "chart": "Chart", "chartLowercase": "chart", "file": "file", "selectedFile": "Selected file", "none": "None", "markdown": "markdown", "content": "content", "audioProgress": "Audio progress", "viewImageOfTotal": "View image {index} of {total}", "viewVideoOfTotal": "View video {index} of {total}", "imagePreview": "Image preview - Use arrow keys to navigate between images", "videoPreview": "Video preview - Use arrow keys to navigate between videos", "galleryImage": "Gallery image", "galleryThumbnail": "Gallery thumbnail", "errorLoadingImage": "Error loading image", "errorLoadingVideo": "Error loading video", "failedToDownloadImage": "Failed to download image", "failedToDownloadVideo": "Failed to download video", "calculatingDimensions": "Calculating dimensions", "import": "Import", "loadAllFolders": "Load All Folders", "logoAlt": "ComfyUI Logo", "comfyOrgLogoAlt": "ComfyOrg Logo", "comfy": "Comfy", "refresh": "Refresh", "refreshNode": "Refresh Node", "vitePreloadErrorTitle": "New Version Available", "vitePreloadErrorMessage": "A new version of the app has been released. Would you like to reload?\nIf not, some parts of the app might not work as expected.\nFeel free to decline and save your progress before reloading.", "terminal": "Terminal", "logs": "Logs", "videoFailedToLoad": "Video failed to load", "audioFailedToLoad": "Audio failed to load", "liveSamplingPreview": "Live sampling preview", "extensionName": "Extension Name", "reloadToApplyChanges": "Reload to apply changes", "insert": "Insert", "systemInfo": "System Info", "devices": "Devices", "about": "About", "add": "Add", "confirm": "Confirm", "confirmed": "Confirmed", "reset": "Reset", "resetAll": "Reset All", "clearFilters": "Clear Filters", "resetAllKeybindingsTooltip": "Reset all keybindings to default", "customizeFolder": "Customize Folder", "icon": "Icon", "color": "Color", "error": "Error", "resizeFromBottomRight": "Resize from bottom-right corner", "resizeFromTopRight": "Resize from top-right corner", "resizeFromBottomLeft": "Resize from bottom-left corner", "resizeFromTopLeft": "Resize from top-left corner", "info": "Node Info", "bookmark": "Save to Library", "moreOptions": "More Options", "more": "More", "loading": "Loading", "loadingPanel": "Loading {panel} panel...", "preview": "PREVIEW", "addNodeFilterCondition": "Add node filter condition", "architecture": "Architecture", "author": "Author", "usageHint": "Usage hint", "triggerPhrase": "Trigger phrase", "findIssues": "Find Issues", "reportIssue": "Send Report", "reportIssueTooltip": "Submit the error report to Comfy Org", "reportSent": "Report Submitted", "copyToClipboard": "Copy to Clipboard", "openNewIssue": "Open New Issue", "showReport": "Show Report", "imageFailedToLoad": "Image failed to load", "reconnecting": "Reconnecting", "reconnected": "Reconnected", "delete": "Delete", "rename": "Rename", "save": "Save", "saving": "Saving", "no": "No", "cancel": "Cancel", "close": "Close", "or": "or", "pressKeysForNewBinding": "Press keys for new binding", "defaultBanner": "default banner", "enableOrDisablePack": "Enable or disable pack", "openManager": "Open Manager", "graphNavigation": "Graph navigation", "dropYourFileOr": "Drop your file or", "back": "Back", "next": "Next", "submit": "Submit", "install": "Install", "installing": "Installing", "overwrite": "Overwrite", "customize": "Customize", "experimental": "BETA", "deprecated": "DEPR", "loadWorkflow": "Load Workflow", "goToNode": "Go to Node", "setAsBackground": "Set as Background", "customBackground": "Custom Background", "settings": "Settings", "searchWorkflows": "Search Workflows", "scrollLeft": "Scroll Left", "scrollRight": "Scroll Right", "searchSettings": "Search Settings", "searchNodes": "Search Nodes", "searchModels": "Search Models", "searchKeybindings": "Search Keybindings", "searchExtensions": "Search Extensions", "search": "Search", "searchPlaceholder": "Search...", "noResultsFound": "No Results Found", "noResults": "No Results", "searchFailedMessage": "We couldn't find any settings matching your search. Try adjusting your search terms.", "noTasksFound": "No Tasks Found", "noTasksFoundMessage": "There are no tasks in the queue.", "newFolder": "New Folder", "enableAll": "Enable All", "disableAll": "Disable All", "enableSelected": "Enable Selected", "disableSelected": "Disable Selected", "disableThirdParty": "Disable Third-Party", "core": "Core", "custom": "Custom", "command": "Command", "keybinding": "Keybinding", "upload": "Upload", "export": "Export", "workflow": "Workflow", "success": "Success", "ok": "OK", "feedback": "Feedback", "continue": "Continue", "control_after_generate": "control after generate", "control_before_generate": "control before generate", "choose_file_to_upload": "choose file to upload", "capture": "capture", "nodes": "Nodes", "community": "Community", "all": "All", "versionMismatchWarning": "Version Compatibility Warning", "versionMismatchWarningMessage": "{warning}: {detail} Visit https://docs.comfy.org/installation/update_comfyui#common-update-issues for update instructions.", "frontendOutdated": "Frontend version {frontendVersion} is outdated. Backend requires {requiredVersion} or higher.", "frontendNewer": "Frontend version {frontendVersion} may not be compatible with backend version {backendVersion}.", "updateFrontend": "Update Frontend", "dismiss": "Dismiss", "update": "Update", "updated": "Updated", "resultsCount": "Found {count} Results", "status": "Status", "description": "Description", "warning": "Warning", "name": "Name", "category": "Category", "sort": "Sort", "source": "Source", "filter": "Filter", "apply": "Apply", "enabled": "Enabled", "installed": "Installed", "restart": "Restart", "missing": "Missing", "inProgress": "In progress", "completed": "Completed", "interrupted": "Interrupted", "queued": "Queued", "running": "Running", "failed": "Failed", "cancelled": "Cancelled", "job": "Job", "untitled": "Untitled", "emDash": "—", "enabling": "Enabling {id}", "disabling": "Disabling {id}", "updating": "Updating {id}", "migrate": "Migrate", "updateAvailable": "Update Available", "login": "Login", "learnMore": "Learn more", "amount": "Amount", "unknownError": "Unknown error", "title": "Title", "edit": "Edit", "copy": "Copy", "copyJobId": "Copy Job ID", "copied": "Copied", "relativeTime": { "now": "now", "yearsAgo": "{count}y ago", "monthsAgo": "{count}mo ago", "weeksAgo": "{count}w ago", "daysAgo": "{count}d ago", "hoursAgo": "{count}h ago", "minutesAgo": "{count}min ago" }, "jobIdCopied": "Job ID copied to clipboard", "failedToCopyJobId": "Failed to copy job ID", "imageUrl": "Image URL", "clear": "Clear", "clearAll": "Clear all", "copyURL": "Copy URL", "releaseTitle": "{package} {version} Release", "itemSelected": "{selectedCount} item selected", "itemsSelected": "{selectedCount} items selected", "multiSelectDropdown": "Multi-select dropdown", "singleSelectDropdown": "Single-select dropdown", "progressCountOf": "of", "keybindingAlreadyExists": "Keybinding already exists on", "commandProhibited": "Command {command} is prohibited. Contact an administrator for more information.", "startRecording": "Start Recording", "stopRecording": "Stop Recording", "micPermissionDenied": "Microphone permission denied", "noAudioRecorded": "No audio recorded", "nodesRunning": "nodes running", "duplicate": "Duplicate", "itemsCopiedToClipboard": "Items copied to clipboard", "selectItemsToCopy": "Select items to copy", "nothingToCopy": "Nothing to copy", "selectItemsToDuplicate": "Select items to duplicate", "nothingToDuplicate": "Nothing to duplicate", "selectItemsToDelete": "Select items to delete", "nothingToDelete": "Nothing to delete", "batchRename": "Batch rename", "enterBaseName": "Enter base name", "enterNewName": "Enter new name", "selectItemsToRename": "Select items to rename", "nothingToRename": "Nothing to rename", "moreWorkflows": "More workflows", "seeTutorial": "See a tutorial", "nodeRenderError": "Node Render Error", "nodeContentError": "Node Content Error", "nodeHeaderError": "Node Header Error", "nodeSlotsError": "Node Slots Error", "nodeWidgetsError": "Node Widgets Error", "frameNodes": "Frame Nodes", "listening": "Listening...", "ready": "Ready", "playPause": "Play/Pause", "playRecording": "Play Recording", "playing": "Playing", "stopPlayback": "Stop Playback", "playbackSpeed": "Playback Speed", "volume": "Volume", "halfSpeed": "0.5x", "1x": "1x", "2x": "2x", "beta": "BETA", "profile": "Profile", "noItems": "No items" };
const manager = { "title": "Custom Nodes Manager", "legacyMenuNotAvailable": "Legacy manager menu is not available, defaulting to the new manager menu.", "legacyManagerUI": "Use Legacy UI", "legacyManagerUIDescription": "To use the legacy Manager UI, start ComfyUI with --enable-manager-legacy-ui", "failed": "Failed", "failedToInstall": "Failed to Install", "installError": "Install Error", "importFailedGenericError": "Package failed to import. Check the console for more details.", "noNodesFound": "No nodes found", "noNodesFoundDescription": "The pack's nodes either could not be parsed, or the pack is a frontend extension only and doesn't have any nodes.", "installationQueue": "Installation Queue", "changingVersion": "Changing version from {from} to {to}", "dependencies": "Dependencies", "inWorkflow": "In Workflow", "infoPanelEmpty": "Click an item to see the info", "applyChanges": "Apply Changes", "restartToApplyChanges": "To apply changes, please restart ComfyUI", "clickToFinishSetup": "Click", "toFinishSetup": "to finish setup", "restartingBackend": "Restarting backend to apply changes...", "extensionsSuccessfullyInstalled": "Extension(s) successfully installed and are ready to use!", "installingDependencies": "Installing dependencies...", "loadingVersions": "Loading versions...", "selectVersion": "Select Version", "downloads": "Downloads", "repository": "Repository", "uninstall": "Uninstall", "uninstalling": "Uninstalling {id}", "update": "Update", "tryUpdate": "Try Update", "tryUpdateTooltip": "Pull latest changes from repository. Nightly versions may have updates that cannot be detected automatically.", "uninstallSelected": "Uninstall Selected", "updateSelected": "Update Selected", "updateAll": "Update All", "updatingAllPacks": "Updating all packages", "disabledNodesWontUpdate": "Disabled nodes will not be updated", "enablePackToChangeVersion": "Enable this pack to change versions", "license": "License", "nightlyVersion": "Nightly", "latestVersion": "Latest", "createdBy": "Created By", "totalNodes": "Total Nodes", "discoverCommunityContent": "Discover community-made Node Packs, Extensions, and more...", "errorConnecting": "Error connecting to the Comfy Node Registry.", "noResultsFound": "No results found matching your search.", "tryDifferentSearch": "Please try a different search query.", "tryAgainLater": "Please try again later.", "gettingInfo": "Getting info...", "nodePack": "Node Pack", "searchPlaceholder": "Search", "version": "Version", "lastUpdated": "Last Updated", "noDescription": "No description available", "installSelected": "Install Selected", "installAllMissingNodes": "Install All", "allMissingNodesInstalled": "All missing nodes have been successfully installed", "packsSelected": "packs selected", "mixedSelectionMessage": "Cannot perform bulk action on mixed selection", "notAvailable": "Not Available", "status": { "active": "Active", "pending": "Pending", "flagged": "Flagged", "deleted": "Deleted", "banned": "Banned", "unknown": "Unknown", "conflicting": "Conflicting", "importFailed": "Install Error" }, "sort": { "downloads": "Most Popular", "publisher": "Publisher", "created": "Newest", "updated": "Updated Recently" }, "filter": { "nodePack": "Node Pack", "enabled": "Enabled", "disabled": "Disabled" }, "conflicts": { "title": "Node Pack Issues Detected!", "description": "We've detected conflicts between some of your extensions and the new version of ComfyUI. By updating you risk breaking workflows that rely on those extensions.", "info": "If you continue with the update, the conflicting extensions will be disabled automatically. You can review and manage them anytime in the ComfyUI Manager.", "extensionAtRisk": "Extension at Risk", "conflicts": "Conflicts", "importFailedExtensions": "Import Failed Extensions", "conflictInfoTitle": "Why is this happening?", "installAnyway": "Install Anyway", "enableAnyway": "Enable Anyway", "understood": "Understood", "warningBanner": { "title": "Some extensions are disabled due to incompatibility with your current setup", "message": "These extensions require versions of system packages  that differ from your current setup. Installing them may override core dependencies and affect other extensions or workflows.", "button": "Learn More..." }, "conflictMessages": { "comfyui_version": "ComfyUI version mismatch (current: {current}, required: {required})", "frontend_version": "Frontend version mismatch (current: {current}, required: {required})", "os": "Operating system not supported (current: {current}, required: {required})", "accelerator": "GPU/Accelerator not supported (available: {current}, required: {required})", "generic": "Compatibility issue (current: {current}, required: {required})", "banned": "This package is banned for security reasons", "pending": "Security verification pending - compatibility cannot be verified", "import_failed": "Import Failed" }, "warningTooltip": "This package may have compatibility issues with your current environment" } };
const issueReport = { "helpFix": "Help Fix This" };
const color = { "noColor": "No Color", "default": "Default", "blue": "Blue", "green": "Green", "red": "Red", "pink": "Pink", "yellow": "Yellow", "brown": "Brown", "pale_blue": "Pale Blue", "cyan": "Cyan", "purple": "Purple", "black": "Black", "custom": "Custom" };
const contextMenu = { "Inputs": "Inputs", "Outputs": "Outputs", "Properties": "Properties", "Properties Panel": "Properties Panel", "Title": "Title", "Mode": "Mode", "Resize": "Resize", "Collapse": "Collapse", "Expand": "Expand", "Pin": "Pin", "Unpin": "Unpin", "Clone": "Clone", "Remove": "Remove", "Colors": "Colors", "Shapes": "Shapes", "Bypass": "Bypass", "Copy (Clipspace)": "Copy (Clipspace)", "Add Node": "Add Node", "Add Group": "Add Group", "Convert to Group Node": "Convert to Group Node", "Manage Group Nodes": "Manage Group Nodes", "Add Group For Selected Nodes": "Add Group For Selected Nodes", "Save Selected as Template": "Save Selected as Template", "Node Templates": "Node Templates", "Manage": "Manage", "Search": "Search", "Open in Mask Editor": "Open in Mask Editor", "Open Image": "Open Image", "Copy Image": "Copy Image", "Save Image": "Save Image", "Rename": "Rename", "RenameWidget": "Rename Widget", "Copy": "Copy", "Duplicate": "Duplicate", "Paste": "Paste", "Node Info": "Node Info", "Adjust Size": "Adjust Size", "Minimize Node": "Minimize Node", "Expand Node": "Expand Node", "Shape": "Shape", "Color": "Color", "Add Subgraph to Library": "Add Subgraph to Library", "Unpack Subgraph": "Unpack Subgraph", "Edit Subgraph Widgets": "Edit Subgraph Widgets", "Convert to Subgraph": "Convert to Subgraph", "Align Selected To": "Align Selected To", "Distribute Nodes": "Distribute Nodes", "Remove Bypass": "Remove Bypass", "Run Branch": "Run Branch", "Delete": "Delete", "Top": "Top", "Bottom": "Bottom", "Left": "Left", "Right": "Right", "Horizontal": "Horizontal", "Vertical": "Vertical", "new": "new", "deprecated": "deprecated", "Extensions": "Extensions" };
const icon = { "bookmark": "Bookmark", "folder": "Folder", "star": "Star", "heart": "Heart", "file": "File", "inbox": "Inbox", "box": "Box", "briefcase": "Briefcase", "exclamation-triangle": "Warning" };
const welcome = { "title": "Welcome to ComfyUI", "getStarted": "Get Started" };
const userSelect = { "newUser": "New user", "enterUsername": "Enter a username", "existingUser": "Existing user", "selectUser": "Select a user", "next": "Next" };
const notSupported = { "title": "Your device is not supported", "message": "Only following devices are supported:", "illustrationAlt": "Sad girl illustration", "learnMore": "Learn More", "reportIssue": "Report Issue", "supportedDevices": { "macos": "MacOS (M1 or later)", "windows": "Windows (Nvidia GPU with CUDA support)" }, "continue": "Continue", "continueTooltip": "I am sure my device is supported" };
const downloadGit = { "title": "Download git", "message": "Unable to locate git.  A working copy of git is required for normal operation.", "instructions": "Please download and install the latest version for your operating system.  The Download git button below opens the git-scm.com downloads page.", "warning": "If you are sure you do not need git installed, or there has been a mistake, you may click Skip to byapss this check.  Attempting to run ComfyUI without a working copy of git is not currently supported.", "gitWebsite": "Download git", "skip": "Skip" };
const install = { "installLocation": "Install Location", "migration": "Migration", "desktopSettings": "Desktop Settings", "chooseInstallationLocation": "Choose Installation Location", "gpuPicker": { "title": "Choose your hardware setup", "recommended": "RECOMMENDED", "nvidiaSubtitle": "NVIDIA CUDA", "cpuSubtitle": "CPU Mode", "manualSubtitle": "Manual Setup", "appleMetalDescription": "Leverages your Mac's GPU for faster speed and a better overall experience", "nvidiaDescription": "Use your NVIDIA GPU with CUDA acceleration for the best performance.", "cpuDescription": "Use CPU mode for compatibility when GPU acceleration is not available", "manualDescription": "Configure ComfyUI manually for advanced setups or unsupported hardware" }, "locationPicker": { "title": "Choose where to install ComfyUI", "subtitle": "Pick a folder for ComfyUI's files. We'll also set up Python there automatically.", "pathPlaceholder": "/Users/username/Documents/ComfyUI", "migrationPathPlaceholder": "Select existing ComfyUI installation (optional)", "migrateFromExisting": "Migrate from existing installation", "migrateDescription": "Copy or link your existing models, custom nodes, and configurations from a previous ComfyUI installation.", "chooseDownloadServers": "Choose download servers manually", "downloadServersDescription": "Select specific mirror servers for downloading Python, PyPI packages, and PyTorch based on your location." }, "systemLocations": "System Locations", "failedToSelectDirectory": "Failed to select directory", "pathValidationFailed": "Failed to validate path", "pathExists": "Directory already exists - please ensure you have backed up all data", "cannotWrite": "Unable to write to the selected path", "insufficientFreeSpace": "Insufficient space - minimum free space", "isOneDrive": "OneDrive is not supported. Please install ComfyUI in another location.", "insideAppInstallDir": "This folder is inside the ComfyUI Desktop application bundle and will be deleted during updates. Choose a directory outside the install folder, such as Documents/ComfyUI.", "insideUpdaterCache": "This folder is inside the ComfyUI updater cache, which is cleared on every update. Select a different location for your data.", "nonDefaultDrive": "Please install ComfyUI on your system drive (eg. C:\\). Drives with different file systems may cause unpredicable issues. Models and other files can be stored on other drives after installation.", "parentMissing": "Path does not exist - create the containing directory first", "unhandledError": "Unknown error", "installLocationDescription": "Select the directory for ComfyUI's user data. A python environment will be installed to the selected location.", "installLocationTooltip": "ComfyUI's user data directory. Stores:\n- Python Environment\n- Models\n- Custom nodes\n", "appDataLocationTooltip": "ComfyUI's app data directory. Stores:\n- Logs\n- Server configs", "appPathLocationTooltip": "ComfyUI's app asset directory. Stores the ComfyUI code and assets", "migrateFromExistingInstallation": "Migrate from Existing Installation", "migrationSourcePathDescription": "If you have an existing ComfyUI installation, we can copy/link your existing user files and models to the new installation. Your existing ComfyUI installation will not be affected.", "selectItemsToMigrate": "Select Items to Migrate", "migrationOptional": "Migration is optional. If you don't have an existing installation, you can skip this step.", "desktopAppSettings": "Desktop App Settings", "desktopAppSettingsDescription": "Configure how ComfyUI behaves on your desktop. You can change these settings later.", "gpu": "GPU", "gpuSelection": { "selectGpu": "Select GPU", "selectGpuDescription": "Select the type of GPU you have", "cpuMode": "CPU Mode", "cpuModeDescription": "CPU mode is intended for developers and rare edge cases only.", "cpuModeDescription2": "If you are not absolutely certain you need this, please ignore this box and select your GPU above.", "enableCpuMode": "Enable CPU Mode", "nvidiaDescription": "NVIDIA devices are directly supported using pytorch CUDA builds.", "mpsDescription": "Apple Metal Performance Shaders are supported using pytorch nightly.", "customSkipsPython": "This option skips the normal python setup.", "customComfyNeedsPython": "ComfyUI will not work until python is setup", "customManualVenv": "Manually configure python venv", "customInstallRequirements": "Install all requirements and dependencies (e.g. custom torch)", "customMayNotWork": "This is entirely unsupported, and may simply not work" }, "manualConfiguration": { "title": "Manual Configuration", "requirements": "Requirements", "createVenv": "You will need to create a virtual environment in the following directory", "virtualEnvironmentPath": "Virtual environment path", "restartWhenFinished": "When you have finished configuring the virtual environment, please restart ComfyUI." }, "settings": { "autoUpdate": "Automatic Updates", "allowMetrics": "Usage Metrics", "errorUpdatingConsent": "Error Updating Consent", "errorUpdatingConsentDetail": "Failed to update metrics consent settings", "autoUpdateDescription": "Automatically download updates when they become available. You will be notified before updates are installed.", "allowMetricsDescription": "Help improve ComfyUI by sending anonymous usage metrics. No personal information or workflow content will be collected.", "learnMoreAboutData": "Learn more about data collection", "dataCollectionDialog": { "title": "About Data Collection", "whatWeCollect": "What we collect:", "whatWeDoNotCollect": "What we don't collect:", "collect": { "errorReports": "Error message and stack trace", "systemInfo": "Hardware, OS type, and app version", "userJourneyEvents": "User journey events" }, "doNotCollect": { "personalInformation": "Personal information", "fileSystemInformation": "File system information", "workflowContents": "Workflow contents", "customNodeConfigurations": "Custom node configurations" }, "viewFullPolicy": "View full policy" }, "pythonMirrorPlaceholder": "Enter Python mirror URL", "pypiMirrorPlaceholder": "Enter PyPI mirror URL", "checkingMirrors": "Checking network access to python mirrors...", "mirrorsReachable": "Network access to python mirrors is good", "mirrorsUnreachable": "Network access to some python mirrors is bad", "mirrorSettings": "Mirror Settings" }, "customNodes": "Custom Nodes", "customNodesDescription": "Reinstall custom nodes from existing ComfyUI installations.", "helpImprove": "Please help improve ComfyUI", "moreInfo": "For more info, please read our", "privacyPolicy": "privacy policy", "metricsEnabled": "Metrics Enabled", "metricsDisabled": "Metrics Disabled", "updateConsent": "You previously opted in to reporting crashes. We are now tracking event-based metrics to help identify bugs and improve the app. No personal identifiable information is collected." };
const desktopStart = { "initialising": "Initialising..." };
const serverStart = { "title": "Starting ComfyUI", "troubleshoot": "Troubleshoot", "reportIssue": "Report Issue", "openLogs": "Open Logs", "showTerminal": "Show Terminal", "copySelectionTooltip": "Copy selection", "copyAllTooltip": "Copy all", "errorMessage": "Unable to start ComfyUI Desktop", "installation": { "title": "Installing ComfyUI" }, "process": { "initial-state": "Loading...", "python-setup": "Setting up Python Environment...", "starting-server": "Starting ComfyUI server...", "ready": "Loading Human Interface", "error": "Unable to start ComfyUI Desktop" } };
const serverConfig = { "modifiedConfigs": "You have modified the following server configurations. Restart to apply changes.", "revertChanges": "Revert Changes", "restart": "Restart", "restartRequiredToastSummary": "Restart required", "restartRequiredToastDetail": "Restart the app to apply server configuration changes." };
const shape = { "default": "Default", "round": "Round", "CARD": "Card", "circle": "Circle", "arrow": "Arrow", "box": "Box" };
const sideToolbar = { "sidebar": "Sidebar", "themeToggle": "Toggle Theme", "helpCenter": "Help Center", "logout": "Logout", "queue": "Queue", "nodeLibrary": "Node Library", "workflows": "Workflows", "templates": "Templates", "assets": "Assets", "mediaAssets": { "title": "Media Assets", "sortNewestFirst": "Newest first", "sortOldestFirst": "Oldest first", "sortLongestFirst": "Generation time (longest first)", "sortFastestFirst": "Generation time (fastest first)", "filterImage": "Image", "filterVideo": "Video", "filterAudio": "Audio", "filter3D": "3D" }, "backToAssets": "Back to all assets", "searchAssets": "Search Assets", "labels": { "queue": "Queue", "nodes": "Nodes", "models": "Models", "assets": "Assets", "workflows": "Workflows", "templates": "Templates", "console": "Console", "menu": "Menu", "imported": "Imported", "generated": "Generated" }, "noFilesFound": "No files found", "noImportedFiles": "No imported files found", "noGeneratedFiles": "No generated files found", "noFilesFoundMessage": "Upload files or generate content to see them here", "browseTemplates": "Browse example templates", "openWorkflow": "Open workflow in local file system", "newBlankWorkflow": "Create a new blank workflow", "nodeLibraryTab": { "groupBy": "Group By", "sortMode": "Sort Mode", "resetView": "Reset View to Default", "groupStrategies": { "category": "Category", "categoryDesc": "Group by node category", "module": "Module", "moduleDesc": "Group by module source", "source": "Source", "sourceDesc": "Group by source type (Core, Custom, API)" }, "sortBy": { "original": "Original", "originalDesc": "Keep original order", "alphabetical": "Alphabetical", "alphabeticalDesc": "Sort alphabetically within groups" } }, "modelLibrary": "Model Library", "downloads": "Downloads", "queueProgressOverlay": { "title": "Queue Progress", "total": "Total: {percent}", "colonPercent": ": {percent}", "currentNode": "Current node:", "viewAllJobs": "View all jobs", "running": "running", "preview": "Preview", "interruptAll": "Interrupt all running jobs", "moreOptions": "More options", "showAssets": "Show assets", "showAssetsPanel": "Show assets panel", "queuedSuffix": "queued", "clearQueued": "Clear queued", "clearHistory": "Clear job queue history", "filterJobs": "Filter jobs", "filterBy": "Filter by", "filterAllWorkflows": "All workflows", "filterCurrentWorkflow": "Current workflow", "sortJobs": "Sort jobs", "sortBy": "Sort by", "activeJobsSuffix": "active jobs", "jobQueue": "Job Queue", "expandCollapsedQueue": "Expand job queue", "viewJobHistory": "View job history", "noActiveJobs": "No active jobs", "stubClipTextEncode": "CLIP Text Encode:", "jobsCompleted": "{count} job completed | {count} jobs completed", "jobsFailed": "{count} job failed | {count} jobs failed", "cancelJobTooltip": "Cancel job", "clearQueueTooltip": "Clear queue", "clearHistoryDialogTitle": "Clear your job queue history?", "clearHistoryDialogDescription": "All the finished or failed jobs below will be removed from this Job queue panel.", "clearHistoryDialogAssetsNote": "Assets generated by these jobs won’t be deleted and can always be viewed from the assets panel." }, "workflowTab": { "confirmDeleteTitle": "Delete workflow?", "confirmDelete": "Are you sure you want to delete this workflow?", "deleted": "Workflow deleted", "deleteFailedTitle": "Delete failed", "deleteFailed": "Attempt to delete the workflow failed.", "dirtyCloseTitle": "Save Changes?", "dirtyClose": "The files below have been changed.  Would you like to save them before closing?", "dirtyCloseHint": "Hold Shift to close without prompt", "confirmOverwriteTitle": "Overwrite existing file?", "confirmOverwrite": "The file below already exists. Would you like to overwrite it?", "workflowTreeType": { "browse": "Browse", "bookmarks": "Bookmarks", "open": "Open" } } };
const helpCenter = { "feedback": "Give Feedback", "docs": "Docs", "github": "Github", "help": "Help & Support", "managerExtension": "Manager Extension", "more": "More...", "whatsNew": "What's New?", "clickToLearnMore": "Click to learn more →", "loadingReleases": "Loading releases...", "recentReleases": "Recent releases", "noRecentReleases": "No recent releases", "updateAvailable": "Update", "desktopUserGuide": "Desktop User Guide", "openDevTools": "Open Dev Tools", "reinstall": "Re-Install", "updateComfyUI": "Update ComfyUI", "updateComfyUIStarted": "Update Started", "updateComfyUIStartedDetail": "ComfyUI update has been queued. Please wait...", "updateComfyUISuccess": "Update Complete", "updateComfyUISuccessDetail": "ComfyUI has been updated. Rebooting...", "updateComfyUIFailed": "Failed to update ComfyUI. Please try again." };
const releaseToast = { "newVersionAvailable": "New update is out!", "whatsNew": "See what's new", "skip": "Skip", "update": "Update", "description": "Check out the latest improvements and features in this update." };
const menu = { "hideMenu": "Hide Menu", "showMenu": "Show Menu", "batchCount": "Batch Count", "batchCountTooltip": "The number of times the workflow generation should be queued", "autoQueue": "Auto Queue", "disabled": "Disabled", "disabledTooltip": "The workflow will not be automatically queued", "instant": "Instant", "instantTooltip": "The workflow will be queued instantly after a generation finishes", "onChange": "On Change", "onChangeTooltip": "The workflow will be queued once a change is made", "runWorkflow": "Run workflow (Shift to queue at front)", "runWorkflowFront": "Run workflow (Queue at front)", "runWorkflowDisabled": "Workflow contains unsupported nodes (highlighted red). Remove these to run the workflow.", "run": "Run", "execute": "Execute", "interrupt": "Cancel current run", "refresh": "Refresh node definitions", "clipspace": "Open Clipspace", "resetView": "Reset canvas view", "clear": "Clear workflow", "toggleBottomPanel": "Toggle Bottom Panel", "theme": "Theme", "dark": "Dark", "light": "Light", "manageExtensions": "Manage Extensions", "customNodesManager": "Custom Nodes Manager", "settings": "Settings", "help": "Help", "queue": "Queue Panel" };
const tabMenu = { "duplicateTab": "Duplicate Tab", "closeTab": "Close Tab", "closeTabsToLeft": "Close Tabs to Left", "closeTabsToRight": "Close Tabs to Right", "closeOtherTabs": "Close Other Tabs", "addToBookmarks": "Add to Bookmarks", "removeFromBookmarks": "Remove from Bookmarks" };
const templateWorkflows = { "title": "Get Started with a Template", "loadingMore": "Loading more templates...", "searchPlaceholder": "Search templates...", "category": { "ComfyUI Examples": "ComfyUI Examples", "Custom Nodes": "Custom Nodes", "Basics": "Basics", "GettingStarted": "Getting Started", "Flux": "Flux", "ControlNet": "ControlNet", "Upscaling": "Upscaling", "Video": "Video", "Image": "Image", "Area Composition": "Area Composition", "3D": "3D", "Audio": "Audio", "LLMs": "LLMs", "Image API": "Image API", "Video API": "Video API", "LLM API": "LLM API", "All": "All Templates", "Extensions": "Extensions", "Partner Nodes": "Partner Nodes", "Generation Type": "Generation Type" }, "categories": "Categories", "resetFilters": "Clear Filters", "sorting": "Sort by", "activeFilters": "Filters:", "loading": "Loading templates...", "noResults": "No templates found", "noResultsHint": "Try adjusting your search or filters", "allTemplates": "All Templates", "modelFilter": "Model Filter", "useCaseFilter": "Use Case", "licenseFilter": "License", "modelsSelected": "{count} Models", "useCasesSelected": "{count} Use Cases", "runsOnSelected": "{count} Runs On", "runsOnFilter": "Runs on", "resultsCount": "Showing {count} of {total} templates", "sort": { "recommended": "Recommended", "alphabetical": "A → Z", "newest": "Newest", "searchPlaceholder": "Search...", "vramLowToHigh": "VRAM Usage (Low to High)", "modelSizeLowToHigh": "Model Size (Low to High)", "default": "Default" }, "error": { "templateNotFound": 'Template "{templateName}" not found' } };
const templateWidgets = { "sort": { "searchPlaceholder": "Search..." } };
const graphCanvasMenu = { "zoomIn": "Zoom In", "zoomOut": "Zoom Out", "resetView": "Reset View", "fitView": "Fit View", "selectMode": "Select Mode", "panMode": "Pan Mode", "toggleMinimap": "Toggle Minimap", "select": "Select", "hand": "Hand", "zoomOptions": "Zoom Options", "focusMode": "Focus Mode", "hideLinks": "Hide Links", "showLinks": "Show Links", "toggleLinkVisibility": "Toggle Link Visibility" };
const zoomControls = { "label": "Zoom Controls", "zoomToFit": "Zoom To Fit", "showMinimap": "Show Minimap", "hideMinimap": "Hide Minimap" };
const groupNode = { "create": "Create group node", "enterName": "Enter name" };
const nodeTemplates = { "saveAsTemplate": "Save as template", "enterName": "Enter name" };
const workflowService = { "exportWorkflow": "Export Workflow", "enterFilename": "Enter the filename", "saveWorkflow": "Save workflow" };
const subgraphStore = { "confirmDeleteTitle": "Delete blueprint?", "confirmDelete": "This action will permanently remove the blueprint from your library", "saveBlueprint": "Save Subgraph to Library", "overwriteBlueprintTitle": "Overwrite existing blueprint?", "overwriteBlueprint": "Saving will overwrite the current blueprint with your changes", "blueprintName": "Subgraph name", "promoteOutsideSubgraph": "Can't promote widget when not in subgraph", "publish": "Publish Subgraph", "publishSuccess": "Saved to Nodes Library", "publishSuccessMessage": 'You can find your subgraph blueprint in the nodes library under "Subgraph Blueprints"', "loadFailure": "Failed to load subgraph blueprints", "shown": "Shown on node", "showAll": "Show all", "hidden": "Hidden / nested parameters", "hideAll": "Hide all", "showRecommended": "Show recommended widgets" };
const electronFileDownload = { "inProgress": "In Progress", "pause": "Pause Download", "paused": "Paused", "resume": "Resume Download", "cancel": "Cancel Download", "cancelled": "Cancelled" };
const maskEditor = { "title": "Mask Editor", "invert": "Invert", "clear": "Clear", "undo": "Undo", "redo": "Redo", "clickToResetZoom": "Click to reset zoom", "brushSettings": "Brush Settings", "brushShape": "Brush Shape", "colorSelector": "Color Selector", "thickness": "Thickness", "opacity": "Opacity", "hardness": "Hardness", "stepSize": "Step Size", "smoothingPrecision": "Smoothing Precision", "resetToDefault": "Reset to Default", "paintBucketSettings": "Paint Bucket Settings", "tolerance": "Tolerance", "fillOpacity": "Fill Opacity", "colorSelectSettings": "Color Select Settings", "selectionOpacity": "Selection Opacity", "livePreview": "Live Preview", "applyToWholeImage": "Apply to Whole Image", "method": "Method", "stopAtMask": "Stop at mask", "maskTolerance": "Mask Tolerance", "layers": "Layers", "maskLayer": "Mask Layer", "maskOpacity": "Mask Opacity", "imageLayer": "Image Layer", "maskBlendingOptions": "Mask Blending Options", "paintLayer": "Paint Layer", "baseImageLayer": "Base Image Layer", "activateLayer": "Activate Layer", "baseLayerPreview": "Base layer preview", "black": "Black", "white": "White", "negative": "Negative" };
const commands = { "runWorkflow": "Run workflow", "runWorkflowFront": "Run workflow (Queue at front)", "run": "Run", "execute": "Execute", "interrupt": "Cancel current run", "refresh": "Refresh node definitions", "clipspace": "Open Clipspace", "resetView": "Reset canvas view", "clear": "Clear workflow", "toggleBottomPanel": "Toggle Bottom Panel", "theme": "Theme", "dark": "Dark", "light": "Light", "manageExtensions": "Manage Extensions", "settings": "Settings", "help": "Help", "queue": "Queue Panel" };
const queue = { "initializingAlmostReady": "Initializing - Almost ready", "inQueue": "In queue...", "jobAddedToQueue": "Job added to queue", "completedIn": "Finished in {duration}", "jobMenu": { "openAsWorkflowNewTab": "Open as workflow in new tab", "openWorkflowNewTab": "Open workflow in new tab", "copyJobId": "Copy job ID", "cancelJob": "Cancel job", "inspectAsset": "Inspect asset", "addToCurrentWorkflow": "Add to current workflow", "download": "Download", "exportWorkflow": "Export workflow", "delete": "Delete", "deleteAsset": "Delete asset", "removeJob": "Remove job", "copyErrorMessage": "Copy error message", "reportError": "Report error" }, "jobList": { "undated": "Undated", "sortMostRecent": "Most recent", "sortTotalGenerationTime": "Total generation time (longest first)", "sortComputeHoursUsed": "Compute hours used (most first)" }, "jobDetails": { "header": "Job Details", "workflow": "Workflow", "jobId": "Job ID", "queuedAt": "Queued at", "queuePosition": "Queue position", "timeElapsed": "Time elapsed", "estimatedStartIn": "Estimated to start in", "estimatedFinishIn": "Estimated to finish in", "generatedOn": "Generated on", "totalGenerationTime": "Total generation time", "computeHoursUsed": "Compute hours used", "failedAfter": "Failed after", "errorMessage": "Error message", "report": "Report", "queuePositionValue": "~{count} job ahead of yours | ~{count} jobs ahead of yours", "eta": { "seconds": "~{count} second | ~{count} seconds", "secondsRange": "~{lo}-{hi} seconds", "minutes": "~{count} minute | ~{count} minutes", "minutesRange": "~{lo}-{hi} minutes" } } };
const menuLabels = { "File": "File", "Edit": "Edit", "View": "View", "Help": "Help", "Check for Updates": "Check for Updates", "Open Custom Nodes Folder": "Open Custom Nodes Folder", "Open Inputs Folder": "Open Inputs Folder", "Open Logs Folder": "Open Logs Folder", "Open extra_model_paths_yaml": "Open extra_model_paths.yaml", "Open Models Folder": "Open Models Folder", "Open Outputs Folder": "Open Outputs Folder", "Open DevTools": "Open DevTools", "Desktop User Guide": "Desktop User Guide", "Quit": "Quit", "Reinstall": "Reinstall", "Restart": "Restart", "Open 3D Viewer (Beta) for Selected Node": "Open 3D Viewer (Beta) for Selected Node", "Experimental: Browse Model Assets": "Experimental: Browse Model Assets", "Browse Templates": "Browse Templates", "Delete Selected Items": "Delete Selected Items", "Zoom to fit": "Zoom to fit", "Lock Canvas": "Lock Canvas", "Move Selected Nodes Down": "Move Selected Nodes Down", "Move Selected Nodes Left": "Move Selected Nodes Left", "Move Selected Nodes Right": "Move Selected Nodes Right", "Move Selected Nodes Up": "Move Selected Nodes Up", "Reset View": "Reset View", "Resize Selected Nodes": "Resize Selected Nodes", "Node Links": "Node Links", "Canvas Toggle Lock": "Canvas Toggle Lock", "Minimap": "Minimap", "Pin/Unpin Selected Items": "Pin/Unpin Selected Items", "Bypass/Unbypass Selected Nodes": "Bypass/Unbypass Selected Nodes", "Collapse/Expand Selected Nodes": "Collapse/Expand Selected Nodes", "Mute/Unmute Selected Nodes": "Mute/Unmute Selected Nodes", "Pin/Unpin Selected Nodes": "Pin/Unpin Selected Nodes", "Unlock Canvas": "Unlock Canvas", "Zoom In": "Zoom In", "Zoom Out": "Zoom Out", "Clear Pending Tasks": "Clear Pending Tasks", "Clear Workflow": "Clear Workflow", "Contact Support": "Contact Support", "Show Model Selector (Dev)": "Show Model Selector (Dev)", "Duplicate Current Workflow": "Duplicate Current Workflow", "Export": "Export", "Export (API)": "Export (API)", "Convert Selection to Subgraph": "Convert Selection to Subgraph", "Edit Subgraph Widgets": "Edit Subgraph Widgets", "Exit Subgraph": "Exit Subgraph", "Fit Group To Contents": "Fit Group To Contents", "Group Selected Nodes": "Group Selected Nodes", "Toggle promotion of hovered widget": "Toggle promotion of hovered widget", "Unpack the selected Subgraph": "Unpack the selected Subgraph", "Convert selected nodes to group node": "Convert selected nodes to group node", "Manage group nodes": "Manage group nodes", "Ungroup selected group nodes": "Ungroup selected group nodes", "About ComfyUI": "About ComfyUI", "Comfy-Org Discord": "Comfy-Org Discord", "ComfyUI Docs": "ComfyUI Docs", "ComfyUI Forum": "ComfyUI Forum", "ComfyUI Issues": "ComfyUI Issues", "Interrupt": "Interrupt", "Load Default Workflow": "Load Default Workflow", "Custom Nodes Manager": "Custom Nodes Manager", "Custom Nodes (Legacy)": "Custom Nodes (Legacy)", "Manager Menu (Legacy)": "Manager Menu (Legacy)", "Install Missing Custom Nodes": "Install Missing Custom Nodes", "Check for Custom Node Updates": "Check for Custom Node Updates", "Toggle the Custom Nodes Manager Progress Bar": "Toggle the Custom Nodes Manager Progress Bar", "Decrease Brush Size in MaskEditor": "Decrease Brush Size in MaskEditor", "Increase Brush Size in MaskEditor": "Increase Brush Size in MaskEditor", "Open Mask Editor for Selected Node": "Open Mask Editor for Selected Node", "Unload Models": "Unload Models", "Unload Models and Execution Cache": "Unload Models and Execution Cache", "New": "New", "Clipspace": "Clipspace", "Manager": "Manager", "Open": "Open", "Publish": "Publish", "Queue Prompt": "Queue Prompt", "Queue Prompt (Front)": "Queue Prompt (Front)", "Queue Selected Output Nodes": "Queue Selected Output Nodes", "Redo": "Redo", "Refresh Node Definitions": "Refresh Node Definitions", "Save": "Save", "Save As": "Save As", "Show Settings Dialog": "Show Settings Dialog", "Experimental: Enable AssetAPI": "Experimental: Enable AssetAPI", "Canvas Performance": "Canvas Performance", "Help Center": "Help Center", "toggle linear mode": "toggle linear mode", "Toggle Theme (Dark/Light)": "Toggle Theme (Dark/Light)", "Undo": "Undo", "Open Sign In Dialog": "Open Sign In Dialog", "Sign Out": "Sign Out", "Experimental: Enable Nodes 2_0": "Experimental: Enable Nodes 2.0", "Close Current Workflow": "Close Current Workflow", "Next Opened Workflow": "Next Opened Workflow", "Previous Opened Workflow": "Previous Opened Workflow", "Toggle Search Box": "Toggle Search Box", "Bottom Panel": "Bottom Panel", "Show Keybindings Dialog": "Show Keybindings Dialog", "Toggle Terminal Bottom Panel": "Toggle Terminal Bottom Panel", "Toggle Logs Bottom Panel": "Toggle Logs Bottom Panel", "Toggle Essential Bottom Panel": "Toggle Essential Bottom Panel", "Toggle View Controls Bottom Panel": "Toggle View Controls Bottom Panel", "Focus Mode": "Focus Mode", "Assets": "Assets", "Model Library": "Model Library", "Node Library": "Node Library", "Workflows": "Workflows" };
const desktopMenu = { "reinstall": "Reinstall", "confirmReinstall": "This will clear your extra_models_config.yaml file,\nand begin installation again.\n\nAre you sure?", "quit": "Quit", "confirmQuit": "There are unsaved workflows open; any unsaved changes will be lost.  Ignore this and quit?" };
const settingsCategories = { "Comfy-Desktop": "Comfy-Desktop", "General": "General", "Appearance": "Appearance", "ColorPalette": "Color Palette", "Comfy": "Comfy", "Workflow": "Workflow", "DevMode": "Dev Mode", "LiteGraph": "Lite Graph", "Node Widget": "Node Widget", "Node": "Node", "Extension": "Extension", "Canvas": "Canvas", "Link": "Link", "Group": "Group", "Menu": "Menu", "Keybinding": "Keybinding", "LinkRelease": "Link Release", "Graph": "Graph", "Locale": "Locale", "Mask Editor": "Mask Editor", "BrushAdjustment": "Brush Adjustment", "ModelLibrary": "Model Library", "NodeLibrary": "Node Library", "Node Search Box": "Node Search Box", "Pointer": "Pointer", "Queue": "Queue", "QueueButton": "Queue Button", "RerouteBeta": "Reroute Beta", "Server": "Server", "Settings": "Settings", "Sidebar": "Sidebar", "Tree Explorer": "Tree Explorer", "Validation": "Validation", "Window": "Window", "Server-Config": "Server-Config", "About": "About", "EditTokenWeight": "Edit Token Weight", "CustomColorPalettes": "Custom Color Palettes", "UV": "UV", "ContextMenu": "Context Menu", "Reroute": "Reroute", "Load 3D": "Load 3D & Animation", "Camera": "Camera", "Scene": "Scene", "3D": "3D", "Light": "Light", "User": "User", "Credits": "Credits", "API Nodes": "API Nodes", "Notification Preferences": "Notification Preferences", "3DViewer": "3DViewer", "Canvas Navigation": "Canvas Navigation", "PlanCredits": "Plan & Credits", "Vue Nodes": "Nodes 2.0", "VueNodes": "Nodes 2.0", "Nodes 2_0": "Nodes 2.0", "Execution": "Execution", "PLY": "PLY" };
const serverConfigItems = { "listen": { "name": "Host: The IP address to listen on" }, "port": { "name": "Port: The port to listen on" }, "tls-keyfile": { "name": "TLS Key File: Path to TLS key file for HTTPS" }, "tls-certfile": { "name": "TLS Certificate File: Path to TLS certificate file for HTTPS" }, "enable-cors-header": { "name": 'Enable CORS header: Use "*" for all origins or specify domain' }, "max-upload-size": { "name": "Maximum upload size (MB)" }, "cuda-device": { "name": "CUDA device index to use" }, "cuda-malloc": { "name": "Use CUDA malloc for memory allocation" }, "global-precision": { "name": "Global floating point precision", "tooltip": "Global floating point precision" }, "unet-precision": { "name": "UNET precision", "tooltip": "UNET precision" }, "vae-precision": { "name": "VAE precision", "tooltip": "VAE precision" }, "cpu-vae": { "name": "Run VAE on CPU" }, "text-encoder-precision": { "name": "Text Encoder precision", "tooltip": "Text Encoder precision" }, "force-channels-last": { "name": "Force channels-last memory format" }, "directml": { "name": "DirectML device index" }, "disable-ipex-optimize": { "name": "Disable IPEX optimization" }, "preview-method": { "name": "Method used for latent previews" }, "preview-size": { "name": "Size of preview images" }, "cache-classic": { "name": "Use classic cache system" }, "cache-lru": { "name": "Use LRU caching with a maximum of N node results cached.", "tooltip": "May use more RAM/VRAM." }, "cross-attention-method": { "name": "Cross attention method" }, "disable-xformers": { "name": "Disable xFormers optimization" }, "force-upcast-attention": { "name": "Force attention upcast" }, "dont-upcast-attention": { "name": "Prevent attention upcast" }, "vram-management": { "name": "VRAM management mode" }, "reserve-vram": { "name": "Reserved VRAM (GB)", "tooltip": "Set the amount of vram in GB you want to reserve for use by your OS/other software. By default some amount is reserved depending on your OS." }, "default-hashing-function": { "name": "Default hashing function for model files" }, "disable-smart-memory": { "name": "Disable smart memory management", "tooltip": "Force ComfyUI to aggressively offload to regular ram instead of keeping models in vram when it can." }, "deterministic": { "name": "Make pytorch use slower deterministic algorithms when it can.", "tooltip": "Note that this might not make images deterministic in all cases." }, "fast": { "name": "Enable some untested and potentially quality deteriorating optimizations." }, "dont-print-server": { "name": "Don't print server output to console." }, "disable-metadata": { "name": "Disable saving prompt metadata in files." }, "enable-manager-legacy-ui": { "name": "Use legacy Manager UI", "tooltip": "Uses the legacy ComfyUI-Manager UI instead of the new UI." }, "disable-all-custom-nodes": { "name": "Disable loading all custom nodes." }, "log-level": { "name": "Logging verbosity level" }, "input-directory": { "name": "Input directory" }, "output-directory": { "name": "Output directory" } };
const serverConfigCategories = { "Network": "Network", "CUDA": "CUDA", "Inference": "Inference", "Memory": "Memory", "Preview": "Preview", "Cache": "Cache", "Attention": "Attention", "General": "General", "Directories": "Directories" };
const nodeCategories = { "_for_testing": "_for_testing", "custom_sampling": "custom_sampling", "noise": "noise", "dataset": "dataset", "text": "text", "image": "image", "sampling": "sampling", "schedulers": "schedulers", "audio": "audio", "conditioning": "conditioning", "loaders": "loaders", "guiders": "guiders", "api node": "api node", "video": "video", "ByteDance": "ByteDance", "preprocessors": "preprocessors", "utils": "utils", "string": "string", "advanced": "advanced", "guidance": "guidance", "model_merging": "model_merging", "model_patches": "model_patches", "chroma_radiance": "chroma_radiance", "attention_experiments": "attention_experiments", "flux": "flux", "kandinsky5": "kandinsky5", "hooks": "hooks", "combine": "combine", "cond single": "cond single", "context": "context", "controlnet": "controlnet", "inpaint": "inpaint", "scheduling": "scheduling", "create": "create", "mask": "mask", "deprecated": "deprecated", "debug": "debug", "model": "model", "latent": "latent", "3d": "3d", "ltxv": "ltxv", "qwen": "qwen", "sd3": "sd3", "unet": "unet", "sigmas": "sigmas", "BFL": "BFL", "Gemini": "Gemini", "video_models": "video_models", "gligen": "gligen", "sd": "sd", "Ideogram": "Ideogram", "postprocessing": "postprocessing", "transform": "transform", "batch": "batch", "upscaling": "upscaling", "instructpix2pix": "instructpix2pix", "compositing": "compositing", "Kling": "Kling", "samplers": "samplers", "operations": "operations", "training": "training", "lotus": "lotus", "LTXV": "LTXV", "Luma": "Luma", "MiniMax": "MiniMax", "model_specific": "model_specific", "Moonvalley Marey": "Moonvalley Marey", "OpenAI": "OpenAI", "Sora": "Sora", "cond pair": "cond pair", "photomaker": "photomaker", "PixVerse": "PixVerse", "primitive": "primitive", "Recraft": "Recraft", "edit_models": "edit_models", "Rodin": "Rodin", "Runway": "Runway", "animation": "animation", "api": "api", "save": "save", "upscale_diffusion": "upscale_diffusion", "clip": "clip", "Stability AI": "Stability AI", "stable_cascade": "stable_cascade", "3d_models": "3d_models", "style_model": "style_model", "Topaz": "Topaz", "Tripo": "Tripo", "Veo": "Veo", "Vidu": "Vidu", "": "", "camera": "camera", "Wan": "Wan", "zimage": "zimage" };
const dataTypes = { "*": "*", "AUDIO": "AUDIO", "AUDIO_ENCODER": "AUDIO_ENCODER", "AUDIO_ENCODER_OUTPUT": "AUDIO_ENCODER_OUTPUT", "AUDIO_RECORD": "AUDIO_RECORD", "BOOLEAN": "BOOLEAN", "CAMERA_CONTROL": "CAMERA_CONTROL", "CLIP": "CLIP", "CLIP_VISION": "CLIP_VISION", "CLIP_VISION_OUTPUT": "CLIP_VISION_OUTPUT", "COMBO": "COMBO", "CONDITIONING": "CONDITIONING", "CONTROL_NET": "CONTROL_NET", "FLOAT": "FLOAT", "FLOATS": "FLOATS", "GEMINI_INPUT_FILES": "GEMINI_INPUT_FILES", "GLIGEN": "GLIGEN", "GUIDER": "GUIDER", "HOOK_KEYFRAMES": "HOOK_KEYFRAMES", "HOOKS": "HOOKS", "IMAGE": "IMAGE", "INT": "INT", "LATENT": "LATENT", "LATENT_OPERATION": "LATENT_OPERATION", "LATENT_UPSCALE_MODEL": "LATENT_UPSCALE_MODEL", "LOAD_3D": "LOAD_3D", "LOAD3D_CAMERA": "LOAD3D_CAMERA", "LORA_MODEL": "LORA_MODEL", "LOSS_MAP": "LOSS_MAP", "LUMA_CONCEPTS": "LUMA_CONCEPTS", "LUMA_REF": "LUMA_REF", "MASK": "MASK", "MESH": "MESH", "MODEL": "MODEL", "MODEL_PATCH": "MODEL_PATCH", "MODEL_TASK_ID": "MODEL_TASK_ID", "NOISE": "NOISE", "OPENAI_CHAT_CONFIG": "OPENAI_CHAT_CONFIG", "OPENAI_INPUT_FILES": "OPENAI_INPUT_FILES", "PHOTOMAKER": "PHOTOMAKER", "PIXVERSE_TEMPLATE": "PIXVERSE_TEMPLATE", "RECRAFT_COLOR": "RECRAFT_COLOR", "RECRAFT_CONTROLS": "RECRAFT_CONTROLS", "RECRAFT_V3_STYLE": "RECRAFT_V3_STYLE", "RETARGET_TASK_ID": "RETARGET_TASK_ID", "RIG_TASK_ID": "RIG_TASK_ID", "SAMPLER": "SAMPLER", "SIGMAS": "SIGMAS", "STRING": "STRING", "STYLE_MODEL": "STYLE_MODEL", "SVG": "SVG", "TIMESTEPS_RANGE": "TIMESTEPS_RANGE", "TRACKS": "TRACKS", "UPSCALE_MODEL": "UPSCALE_MODEL", "VAE": "VAE", "VIDEO": "VIDEO", "VOXEL": "VOXEL", "WAN_CAMERA_EMBEDDING": "WAN_CAMERA_EMBEDDING", "WEBCAM": "WEBCAM" };
const maintenance = { "title": "Maintenance", "allOk": "No issues were detected.", "status": "Status", "detected": "Detected", "refreshing": "Refreshing", "None": "None", "OK": "OK", "Skipped": "Skipped", "showManual": "Show maintenance tasks", "confirmTitle": "Are you sure?", "terminalDefaultMessage": "When you run a troubleshooting command, any output will be shown here.", "consoleLogs": "Console Logs", "error": { "toastTitle": "Task error", "taskFailed": "Task failed to run.", "cannotContinue": "Unable to continue - errors remain", "defaultDescription": "An error occurred while running a maintenance task." }, "unsafeMigration": { "title": "Unsafe install location detected", "generic": "Your current ComfyUI base path is in a location that may be deleted or modified during updates. To avoid data loss, move it to a safe folder.", "appInstallDir": "Your base path is inside the ComfyUI Desktop application bundle. This folder may be deleted or overwritten during updates. Choose a directory outside the install folder, such as Documents/ComfyUI.", "updaterCache": "Your base path is inside the ComfyUI updater cache, which is cleared on each update. Choose a different location for your data.", "oneDrive": "Your base path is on OneDrive, which can cause sync issues and accidental data loss. Choose a local folder that is not managed by OneDrive.", "action": 'Use the "Base path" maintenance task below to move ComfyUI to a safe location.' } };
const missingModelsDialog = { "doNotAskAgain": "Don't show this again", "missingModels": "Missing Models", "missingModelsMessage": "When loading the graph, the following models were not found" };
const versionMismatchWarning = { "title": "Version Compatibility Warning", "frontendOutdated": "Frontend version {frontendVersion} is outdated. Backend requires version {requiredVersion} or higher.", "frontendNewer": "Frontend version {frontendVersion} may not be compatible with backend version {backendVersion}.", "updateFrontend": "Update Frontend", "dismiss": "Dismiss" };
const loadWorkflowWarning = { "outdatedVersion": "This workflow was created with a newer version of ComfyUI ({version}). Some nodes may not work correctly.", "outdatedVersionGeneric": "This workflow was created with a newer version of ComfyUI. Some nodes may not work correctly.", "coreNodesFromVersion": "Core nodes from version {version}:" };
const errorDialog = { "defaultTitle": "An error occurred", "loadWorkflowTitle": "Loading aborted due to error reloading workflow data", "noStackTrace": "No stacktrace available", "extensionFileHint": "This may be due to the following script", "promptExecutionError": "Prompt execution failed" };
const apiNodesSignInDialog = { "title": "Sign In Required to Use API Nodes", "message": "This workflow contains API Nodes, which require you to be signed in to your account in order to run." };
const apiNodesCostBreakdown = { "title": "API Node(s)", "costPerRun": "Cost per run", "totalCost": "Total Cost" };
const desktopUpdate = { "title": "Updating ComfyUI Desktop", "description": "ComfyUI Desktop is installing new dependencies.  This may take a few minutes.", "terminalDefaultMessage": "Any console output from the update will be shown here.", "updateFoundTitle": "Update Found (v{version})", "updateAvailableMessage": "An update is available. Do you want to restart and update now?", "noUpdateFound": "No update found", "errorCheckingUpdate": "Error checking for updates", "errorInstallingUpdate": "Error installing update" };
const clipboard = { "successMessage": "Copied to clipboard", "errorMessage": "Failed to copy to clipboard", "errorNotSupported": "Clipboard API not supported in your browser" };
const imageCompare = { "noImages": "No images to compare" };
const load3d = { "switchCamera": "Switch Camera", "showGrid": "Show Grid", "backgroundColor": "Background Color", "lightIntensity": "Light Intensity", "fov": "FOV", "previewOutput": "Preview Output", "uploadBackgroundImage": "Upload Background Image", "removeBackgroundImage": "Remove Background Image", "tiledMode": "Tiled", "panoramaMode": "Panorama", "loadingModel": "Loading 3D Model...", "upDirection": "Up Direction", "materialMode": "Material Mode", "scene": "Scene", "model": "Model", "camera": "Camera", "light": "Light", "switchingMaterialMode": "Switching Material Mode...", "edgeThreshold": "Edge Threshold", "export": "Export", "exportModel": "Export Model", "exportingModel": "Exporting model...", "reloadingModel": "Reloading model...", "uploadTexture": "Upload Texture", "applyingTexture": "Applying Texture...", "materialModes": { "normal": "Normal", "wireframe": "Wireframe", "original": "Original", "pointCloud": "Point Cloud", "depth": "Depth", "lineart": "Lineart" }, "upDirections": { "original": "Original" }, "startRecording": "Start Recording", "stopRecording": "Stop Recording", "exportRecording": "Export Recording", "clearRecording": "Clear Recording", "resizeNodeMatchOutput": "Resize Node to match output", "loadingBackgroundImage": "Loading Background Image", "cameraType": { "perspective": "Perspective", "orthographic": "Orthographic" }, "viewer": { "title": "3D Viewer (Beta)", "apply": "Apply", "cancel": "Cancel", "cameraType": "Camera Type", "sceneSettings": "Scene Settings", "cameraSettings": "Camera Settings", "lightSettings": "Light Settings", "exportSettings": "Export Settings", "modelSettings": "Model Settings" }, "openIn3DViewer": "Open in 3D Viewer", "dropToLoad": "Drop 3D model to load", "unsupportedFileType": "Unsupported file type (supports .gltf, .glb, .obj, .fbx, .stl)", "uploadingModel": "Uploading 3D model..." };
const toastMessages = { "nothingToQueue": "Nothing to queue", "pleaseSelectOutputNodes": "Please select output nodes", "failedToQueue": "Failed to queue", "failedExecutionPathResolution": "Could not resolve path to selected nodes", "no3dScene": "No 3D scene to apply texture", "failedToApplyTexture": "Failed to apply texture", "no3dSceneToExport": "No 3D scene to export", "failedToExportModel": "Failed to export model as {format}", "exportSuccess": "Successfully exported model as {format}", "fileLoadError": "Unable to find workflow in {fileName}", "dropFileError": "Unable to process dropped item: {error}", "interrupted": "Execution has been interrupted", "pendingTasksDeleted": "Pending tasks deleted", "nothingToGroup": "Nothing to group", "pleaseSelectNodesToGroup": "Please select the nodes (or other groups) to create a group for", "emptyCanvas": "Empty canvas", "fileUploadFailed": "File upload failed", "unableToGetModelFilePath": "Unable to get model file path", "couldNotDetermineFileType": "Could not determine file type", "errorLoadingModel": "Error loading model", "failedToDownloadFile": "Failed to download file", "updateRequested": "Update requested", "nodeDefinitionsUpdated": "Node definitions updated", "errorSaveSetting": "Error saving setting {id}: {err}", "errorCopyImage": "Error copying image: {error}", "noTemplatesToExport": "No templates to export", "failedToFetchLogs": "Failed to fetch server logs", "migrateToLitegraphReroute": "Reroute nodes will be removed in future versions. Click to migrate to litegraph-native reroute.", "legacyMaskEditorDeprecated": "The legacy mask editor is deprecated and will be removed soon.", "userNotAuthenticated": "User not authenticated", "failedToFetchBalance": "Failed to fetch balance: {error}", "failedToCreateCustomer": "Failed to create customer: {error}", "failedToInitiateCreditPurchase": "Failed to initiate credit purchase: {error}", "failedToAccessBillingPortal": "Failed to access billing portal: {error}", "failedToPurchaseCredits": "Failed to purchase credits: {error}", "failedToFetchSubscription": "Failed to fetch subscription status: {error}", "failedToInitiateSubscription": "Failed to initiate subscription: {error}", "unauthorizedDomain": "Your domain {domain} is not authorized to use this service. Please contact {email} to add your domain to the whitelist.", "useApiKeyTip": "Tip: Can't access normal login? Use the Comfy API Key option.", "nothingSelected": "Nothing selected", "cannotCreateSubgraph": "Cannot create subgraph", "failedToConvertToSubgraph": "Failed to convert items to subgraph", "failedToInitializeLoad3dViewer": "Failed to initialize 3D Viewer", "failedToLoadBackgroundImage": "Failed to load background image", "failedToLoadModel": "Failed to load 3D model", "modelLoadedSuccessfully": "3D model loaded successfully", "failedToUpdateBackgroundColor": "Failed to update background color", "failedToToggleGrid": "Failed to toggle grid", "failedToToggleCamera": "Failed to toggle camera", "failedToUpdateFOV": "Failed to update field of view", "failedToUpdateLightIntensity": "Failed to update light intensity", "failedToUpdateBackgroundImage": "Failed to update background image", "failedToUpdateUpDirection": "Failed to update up direction", "failedToUpdateMaterialMode": "Failed to update material mode", "failedToUpdateEdgeThreshold": "Failed to update edge threshold", "failedToUploadBackgroundImage": "Failed to upload background image", "failedToUpdateBackgroundRenderMode": "Failed to update background render mode to {mode}" };
const nodeErrors = { "render": "Node Render Error", "content": "Node Content Error", "header": "Node Header Error", "slots": "Node Slots Error", "widgets": "Node Widgets Error" };
const auth = { "apiKey": { "title": "API Key", "label": "API Key", "description": "Use your Comfy API key to enable API Nodes", "placeholder": "Enter your API Key", "error": "Invalid API Key", "storageFailed": "Failed to store API Key", "storageFailedDetail": "Please try again.", "stored": "API Key stored", "storedDetail": "Your API Key has been stored successfully", "cleared": "API Key cleared", "clearedDetail": "Your API Key has been cleared successfully", "invalid": "Invalid API Key", "invalidDetail": "Please enter a valid API Key", "helpText": "Need an API key?", "generateKey": "Get one here", "whitelistInfo": "About non-whitelisted sites" }, "login": { "title": "Log in to your account", "useApiKey": "Comfy API Key", "signInOrSignUp": "Sign In / Sign Up", "forgotPasswordError": "Failed to send password reset email", "passwordResetSent": "Password reset email sent", "passwordResetSentDetail": "Please check your email for a link to reset your password.", "newUser": "New here?", "userAvatar": "User Avatar", "signUp": "Sign up", "emailLabel": "Email", "emailPlaceholder": "Enter your email", "passwordLabel": "Password", "passwordPlaceholder": "Enter your password", "confirmPasswordLabel": "Confirm Password", "confirmPasswordPlaceholder": "Enter the same password again", "forgotPassword": "Forgot password?", "passwordResetInstructions": "Enter your email address and we'll send you a link to reset your password.", "sendResetLink": "Send reset link", "backToLogin": "Back to login", "didntReceiveEmail": "Didn't receive an email? Contact us at", "passwordResetError": "Failed to send password reset email. Please try again.", "loginButton": "Sign in", "orContinueWith": "Or continue with", "loginWithGoogle": "Log in with Google", "loginWithGithub": "Log in with Github", "termsText": 'By clicking "Next" or "Sign Up", you agree to our', "termsLink": "Terms of Use", "andText": "and", "privacyLink": "Privacy Policy", "success": "Login successful", "failed": "Login failed", "insecureContextWarning": "This connection is insecure (HTTP) - your credentials may be intercepted by attackers if you proceed to login.", "questionsContactPrefix": "Questions? Contact us at", "noAssociatedUser": "There is no Comfy user associated with the provided API key" }, "signup": { "title": "Create an account", "alreadyHaveAccount": "Already have an account?", "emailLabel": "Email", "emailPlaceholder": "Enter your email", "passwordLabel": "Password", "passwordPlaceholder": "Enter new password", "signUpButton": "Sign up", "signIn": "Sign in", "signUpWithGoogle": "Sign up with Google", "signUpWithGithub": "Sign up with Github", "regionRestrictionChina": "In accordance with local regulatory requirements, our services are temporarily unavailable to users located in China.", "personalDataConsentLabel": "I agree to the processing of my personal data." }, "signOut": { "signOut": "Log Out", "success": "Signed out successfully", "successDetail": "You have been signed out of your account." }, "passwordUpdate": { "success": "Password Updated", "successDetail": "Your password has been updated successfully" }, "errors": { "auth/invalid-email": "Please enter a valid email address.", "auth/user-disabled": "This account has been disabled. Please contact support.", "auth/user-not-found": "No account found with this email. Would you like to create a new account?", "auth/wrong-password": "The password you entered is incorrect. Please try again.", "auth/email-already-in-use": "An account with this email already exists. Try signing in instead.", "auth/weak-password": "Password is too weak. Please use a stronger password with at least 6 characters.", "auth/too-many-requests": "Too many login attempts. Please wait a moment and try again.", "auth/operation-not-allowed": "This sign-in method is not currently supported.", "auth/invalid-credential": "Invalid login credentials. Please check your email and password.", "auth/network-request-failed": "Network error. Please check your connection and try again.", "auth/popup-closed-by-user": "Sign-in was cancelled. Please try again.", "auth/cancelled-popup-request": "Sign-in was cancelled. Please try again." }, "deleteAccount": { "deleteAccount": "Delete Account", "confirmTitle": "Delete Account", "confirmMessage": "Are you sure you want to delete your account? This action cannot be undone and will permanently remove all your data.", "confirm": "Delete Account", "cancel": "Cancel", "success": "Account Deleted", "successDetail": "Your account has been successfully deleted." }, "reauthRequired": { "title": "Re-authentication Required", "message": "For security reasons, this action requires you to sign in again. Would you like to proceed?", "confirm": "Sign In Again", "cancel": "Cancel" }, "loginButton": { "tooltipHelp": 'Login to be able to use "API Nodes"', "tooltipLearnMore": "Learn more..." } };
const validation = { "invalidEmail": "Invalid email address", "required": "Required", "minLength": "Must be at least {length} characters", "maxLength": "Must be no more than {length} characters", "prefix": "Must start with {prefix}", "descriptionRequired": "Description is required", "length": "Must be {length} characters", "password": { "requirements": "Password requirements", "minLength": "Must be between 8 and 32 characters", "uppercase": "Must contain at least one uppercase letter", "lowercase": "Must contain at least one lowercase letter", "number": "Must contain at least one number", "special": "Must contain at least one special character", "match": "Passwords must match" }, "personalDataConsentRequired": "You must agree to the processing of your personal data." };
const credits = { "activity": "Activity", "credits": "Credits", "yourCreditBalance": "Your credit balance", "purchaseCredits": "Purchase Credits", "invoiceHistory": "Invoice History", "apiPricing": "API Pricing", "faqs": "FAQs", "messageSupport": "Message Support", "lastUpdated": "Last updated", "topUp": { "insufficientTitle": "Insufficient Credits", "insufficientMessage": "You don't have enough credits to run this workflow.", "quickPurchase": "Quick Purchase", "maxAmount": "(Max. $1,000 USD)", "buyNow": "Buy now", "seeDetails": "See details", "topUp": "Top Up", "addMoreCredits": "Add more credits", "addMoreCreditsToRun": "Add more credits to run", "insufficientWorkflowMessage": "You don't have enough credits to run this workflow.", "creditsDescription": "Credits are used to run workflows or partner nodes.", "howManyCredits": "How many credits would you like to add?", "videosEstimate": "~{count} videos*", "templateNote": "*Generated with Wan Fun Control template", "buy": "Buy", "purchaseError": "Purchase Failed", "purchaseErrorDetail": "Failed to purchase credits: {error}", "unknownError": "An unknown error occurred" }, "creditsAvailable": "Credits available", "refreshes": "Refreshes {date}", "eventType": "Event Type", "details": "Details", "time": "Time", "additionalInfo": "Additional Info", "model": "Model", "added": "Added", "accountInitialized": "Account initialized", "unified": { "message": "Credits have been unified", "tooltip": "We've unified payments across Comfy. Everything now runs on Comfy Credits:\n- Partner Nodes (formerly API nodes)\n- Cloud workflows\n\nYour existing Partner node balance has been converted into credits." } };
const subscription = { "title": "Subscription", "titleUnsubscribed": "Subscribe to Comfy Cloud", "comfyCloud": "Comfy Cloud", "comfyCloudLogo": "Comfy Cloud Logo", "beta": "BETA", "perMonth": "/ month", "usdPerMonth": "USD / mo", "renewsDate": "Renews {date}", "expiresDate": "Expires {date}", "manageSubscription": "Manage subscription", "partnerNodesBalance": '"Partner Nodes" Credit Balance', "partnerNodesDescription": "For running commercial/proprietary models", "totalCredits": "Total credits", "viewUsageHistory": "View usage history", "addApiCredits": "Add API credits", "addCredits": "Add credits", "monthlyCreditsRollover": "These credits will rollover to the next month", "monthlyBonusDescription": "Monthly credit bonus", "prepaidDescription": "Pre-paid credits", "prepaidCreditsInfo": "Pre-paid credits expire after 1 year from purchase date.", "creditsRemainingThisMonth": "Credits remaining this month", "creditsRemainingThisYear": "Credits remaining this year", "creditsYouveAdded": "Credits you've added", "monthlyCreditsInfo": "These credits refresh monthly and don't roll over", "viewMoreDetailsPlans": "View more details about plans & pricing", "nextBillingCycle": "next billing cycle", "yourPlanIncludes": "Your plan includes:", "viewMoreDetails": "View more details", "learnMore": "Learn more", "billedMonthly": "Billed monthly", "billedYearly": "{total} Billed yearly", "monthly": "Monthly", "yearly": "Yearly", "tierNameYearly": "{name} Yearly", "messageSupport": "Message support", "invoiceHistory": "Invoice history", "benefits": { "benefit1": "$10 in monthly credits for Partner Nodes — top up when needed", "benefit2": "Up to 30 min runtime per job" }, "yearlyDiscount": "20% DISCOUNT", "tiers": { "founder": { "name": "Founder's Edition" }, "standard": { "name": "Standard" }, "creator": { "name": "Creator" }, "pro": { "name": "Pro" } }, "required": { "title": "Subscribe to", "waitingForSubscription": "Complete your subscription in the new tab. We'll automatically detect when you're done!", "subscribe": "Subscribe" }, "subscribeToRun": "Subscribe", "subscribeToRunFull": "Subscribe to Run", "subscribeNow": "Subscribe Now", "subscribeToComfyCloud": "Subscribe to Comfy Cloud", "description": "Choose the best plan for you", "haveQuestions": "Have questions or wondering about enterprise?", "contactUs": "Contact us", "viewEnterprise": "View enterprise", "partnerNodesCredits": "Partner nodes pricing", "plansAndPricing": "Plans & pricing", "managePlan": "Manage plan", "upgrade": "UPGRADE", "mostPopular": "Most popular", "currentPlan": "Current Plan", "subscribeTo": "Subscribe to {plan}", "monthlyCreditsLabel": "Monthly credits", "yearlyCreditsLabel": "Total yearly credits", "maxDurationLabel": "Max duration of each workflow run", "gpuLabel": "RTX 6000 Pro (96GB VRAM)", "addCreditsLabel": "Add more credits whenever", "customLoRAsLabel": "Import your own LoRAs", "videoEstimateLabel": "Number of 5s videos generated with Wan Fun Control template", "videoEstimateHelp": "What is this?", "videoEstimateExplanation": "These estimates are based on the Wan Fun Control template for 5-second videos.", "videoEstimateTryTemplate": "Try the Wan Fun Control template →", "upgradePlan": "Upgrade Plan", "upgradeTo": "Upgrade to {plan}", "changeTo": "Change to {plan}", "maxDuration": { "standard": "30 min", "creator": "30 min", "pro": "1 hr", "founder": "30 min" } };
const userSettings = { "title": "My Account Settings", "accountSettings": "Account settings", "name": "Name", "email": "Email", "provider": "Sign-in Provider", "notSet": "Not set", "updatePassword": "Update Password" };
const selectionToolbox = { "executeButton": { "tooltip": "Execute to selected output nodes (Highlighted with orange border)", "disabledTooltip": "No output nodes selected" }, "Set Group Nodes to Never": "Set Group Nodes to Never", "Bypass Group Nodes": "Bypass Group Nodes", "Set Group Nodes to Always": "Set Group Nodes to Always" };
const widgets$1 = { "selectModel": "Select model", "uploadSelect": { "placeholder": "Select...", "placeholderImage": "Select image...", "placeholderAudio": "Select audio...", "placeholderVideo": "Select video...", "placeholderModel": "Select model...", "placeholderUnknown": "Select media..." }, "valueControl": { "header": { "prefix": "Automatically update the value", "after": "AFTER", "before": "BEFORE", "postfix": "running the workflow:" }, "linkToGlobal": "Link to", "linkToGlobalSeed": "Global Value", "linkToGlobalDesc": "Unique value linked to the Global Value's control setting", "randomize": "Randomize Value", "randomizeDesc": "Shuffles the value randomly after each generation", "increment": "Increment Value", "incrementDesc": "Adds 1 to value or selects the next option", "decrement": "Decrement Value", "decrementDesc": "Subtracts 1 from value or selects the previous option", "fixed": "Fixed Value", "fixedDesc": "Leaves value unchanged", "editSettings": "Edit control settings" } };
const widgetFileUpload = { "dropPrompt": "Drop your file or", "browseFiles": "Browse Files" };
const nodeHelpPage = { "inputs": "Inputs", "outputs": "Outputs", "type": "Type", "moreHelp": "For more help, visit the", "documentationPage": "documentation page", "loadError": "Failed to load help: {error}" };
const whatsNewPopup = { "learnMore": "Learn more", "later": "Later", "noReleaseNotes": "No release notes available." };
const breadcrumbsMenu = { "duplicate": "Duplicate", "clearWorkflow": "Clear Workflow", "deleteWorkflow": "Delete Workflow", "deleteBlueprint": "Delete Blueprint", "enterNewName": "Enter new name", "missingNodesWarning": "Workflow contains unsupported nodes (highlighted red)." };
const shortcuts = { "shortcuts": "Shortcuts", "essentials": "Essential", "viewControls": "View Controls", "manageShortcuts": "Manage Shortcuts", "noKeybinding": "No keybinding", "keyboardShortcuts": "Keyboard Shortcuts", "subcategories": { "workflow": "Workflow", "node": "Node", "queue": "Queue", "view": "View", "panelControls": "Panel Controls" } };
const minimap = { "nodeColors": "Node Colors", "showLinks": "Show Links", "showGroups": "Show Frames/Groups", "renderBypassState": "Render Bypass State", "renderErrorState": "Render Error State" };
const cloudOnboarding = { "survey": { "title": "Cloud Survey", "placeholder": "Survey questions placeholder", "steps": { "familiarity": "How familiar are you with ComfyUI?", "purpose": "What will you primarily use ComfyUI for?", "industry": "What's your primary industry?", "making": "What do you plan on making?" }, "questions": { "familiarity": "How familiar are you with ComfyUI?", "purpose": "What will you primarily use ComfyUI for?", "industry": "What's your primary industry?", "making": "What do you plan on making?" }, "options": { "familiarity": { "new": "New to ComfyUI (never used it before)", "starting": "Just getting started (following tutorials)", "basics": "Comfortable with basics", "advanced": "Advanced user (custom workflows)", "expert": "Expert (help others)" }, "purpose": { "personal": "Personal projects / hobby", "community": "Community contributions (nodes, workflows, etc.)", "client": "Client work (freelance)", "inhouse": "My own workplace (in-house)", "research": "Academic research" }, "industry": { "film_tv_animation": "Film, TV, & animation", "gaming": "Gaming", "marketing": "Marketing & advertising", "architecture": "Architecture", "product_design": "Product & graphic design", "fine_art": "Fine art & illustration", "software": "Software & technology", "education": "Education", "other": "Other", "otherPlaceholder": "Please specify" }, "making": { "images": "Images", "video": "Video & animation", "3d": "3D assets", "audio": "Audio / music", "custom_nodes": "Custom nodes & workflows" } } }, "forgotPassword": { "title": "Forgot Password", "instructions": "Enter your email address and we'll send you a link to reset your password.", "emailLabel": "Email", "emailPlaceholder": "Enter your email", "sendResetLink": "Send reset link", "backToLogin": "Back to login", "didntReceiveEmail": "Didn't receive an email? Contact us at", "passwordResetSent": "Password reset email sent", "passwordResetError": "Failed to send password reset email. Please try again.", "emailRequired": "Email is required" }, "privateBeta": { "title": "Cloud is currently in private beta", "desc": "Sign in to join the waitlist. We’ll notify you when it’s your turn. Already been notified? Sign in start using Cloud." }, "start": { "title": "start creating in seconds", "desc": "Zero setup required. Works on any device.", "explain": "Generate multiple outputs at once. Share workflows with ease.", "learnAboutButton": "Learn about Cloud", "wantToRun": "Want to run ComfyUI locally instead?", "download": "Download ComfyUI" }, "checkingStatus": "Checking your account status...", "retrying": "Retrying...", "retry": "Try Again", "authTimeout": { "title": "Connection Taking Too Long", "message": "We're having trouble connecting to ComfyUI Cloud. This could be due to a slow connection or temporary service issue.", "restart": "Sign Out & Try Again", "troubleshooting": "Common causes:", "causes": ["Corporate firewall or proxy blocking authentication services", "VPN or network restrictions", "Browser extensions interfering with requests", "Regional network limitations", "Try a different browser or network"], "technicalDetails": "Technical Details", "helpText": "Need help? Contact", "supportLink": "support" } };
const cloudFooter_needHelp = "Need Help?";
const cloudStart_title = "start creating in seconds";
const cloudStart_desc = "Zero setup required. Works on any device.";
const cloudStart_explain = "Generate multiple outputs at once. Share workflows with ease.";
const cloudStart_learnAboutButton = "Learn about Cloud";
const cloudStart_wantToRun = "Want to run ComfyUI locally instead?";
const cloudStart_download = "Download ComfyUI";
const cloudWaitlist_questionsText = "Questions? Contact us";
const cloudWaitlist_contactLink = "here";
const cloudSorryContactSupport_title = "Sorry, contact support";
const cloudPrivateBeta_title = "Cloud is currently in private beta";
const cloudPrivateBeta_desc = "Sign in to join the waitlist. We'll notify you when it's your turn. Already been notified? Sign in start using Cloud.";
const cloudForgotPassword_title = "Forgot Password";
const cloudForgotPassword_instructions = "Enter your email address and we'll send you a link to reset your password.";
const cloudForgotPassword_emailLabel = "Email";
const cloudForgotPassword_emailPlaceholder = "Enter your email";
const cloudForgotPassword_sendResetLink = "Send reset link";
const cloudForgotPassword_backToLogin = "Back to login";
const cloudForgotPassword_didntReceiveEmail = "Didn't receive an email?";
const cloudForgotPassword_emailRequired = "Email is required";
const cloudForgotPassword_passwordResetSent = "Password reset sent";
const cloudForgotPassword_passwordResetError = "Failed to send password reset email";
const cloudSurvey_steps_familiarity = "How familiar are you with ComfyUI?";
const cloudSurvey_steps_purpose = "What will you primarily use ComfyUI for?";
const cloudSurvey_steps_industry = "What's your primary industry?";
const cloudSurvey_steps_making = "What do you plan on making?";
const assetBrowser = { "allCategory": "All {category}", "allModels": "All Models", "assetCollection": "Asset collection", "assets": "Assets", "baseModels": "Base models", "browseAssets": "Browse Assets", "checkpoints": "Checkpoints", "civitaiLinkExample": "{example} {link}", "civitaiLinkExampleStrong": "Example:", "civitaiLinkExampleUrl": "https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295", "civitaiLinkLabel": "Civitai model {download} link", "civitaiLinkLabelDownload": "download", "civitaiLinkPlaceholder": "Paste link here", "confirmModelDetails": "Confirm Model Details", "connectionError": "Please check your connection and try again", "errorFileTooLarge": "File exceeds the maximum allowed size limit", "errorFormatNotAllowed": "Only SafeTensor format is allowed", "errorModelTypeNotSupported": "This model type is not supported", "errorUnknown": "An unexpected error occurred", "errorUnsafePickleScan": "CivitAI detected potentially unsafe code in this file", "errorUnsafeVirusScan": "CivitAI detected malware or suspicious content in this file", "errorUploadFailed": "Failed to import asset. Please try again.", "failedToCreateNode": "Failed to create node. Please try again or check console for details.", "fileFormats": "File formats", "fileName": "File Name", "fileSize": "File Size", "filterBy": "Filter by", "findInLibrary": "Find it in the {type} section of the models library.", "finish": "Finish", "genericLinkPlaceholder": "Paste link here", "jobId": "Job ID", "loadingModels": "Loading {type}...", "maxFileSize": "Max file size: {size}", "maxFileSizeValue": "1 GB", "modelAssociatedWithLink": "The model associated with the link you provided:", "modelName": "Model Name", "modelNamePlaceholder": "Enter a name for this model", "modelTypeSelectorLabel": "What type of model is this?", "modelTypeSelectorPlaceholder": "Select model type", "modelUploaded": "Model successfully imported.", "noAssetsFound": "No assets found", "noModelsInFolder": "No {type} available in this folder", "notSureLeaveAsIs": "Not sure? Just leave this as is", "onlyCivitaiUrlsSupported": "Only Civitai URLs are supported", "ownership": "Ownership", "ownershipAll": "All", "ownershipMyModels": "My models", "ownershipPublicModels": "Public models", "providerCivitai": "Civitai", "providerHuggingFace": "Hugging Face", "noValidSourceDetected": "No valid import source detected", "selectFrameworks": "Select Frameworks", "selectModelType": "Select model type", "selectProjects": "Select Projects", "sortAZ": "A-Z", "sortBy": "Sort by", "sortingType": "Sorting Type", "sortPopular": "Popular", "sortRecent": "Recent", "sortZA": "Z-A", "tags": "Tags", "tagsHelp": "Separate tags with commas", "tagsPlaceholder": "e.g., models, checkpoint", "tryAdjustingFilters": "Try adjusting your search or filters", "unknown": "Unknown", "unsupportedUrlSource": "Only URLs from {sources} are supported", "upgradeFeatureDescription": "This feature is only available with Creator or Pro plans.", "upgradeToUnlockFeature": "Upgrade to unlock this feature", "upload": "Import", "uploadFailed": "Import failed", "uploadingModel": "Importing model...", "uploadModel": "Import", "uploadModelDescription1": "Paste a Civitai model download link to add it to your library.", "uploadModelDescription1Generic": "Paste a model download link to add it to your library.", "uploadModelDescription2": "Only links from {link} are supported at the moment", "uploadModelDescription2Link": "https://civitai.com/models", "uploadModelDescription2Generic": "Only URLs from the following providers are supported:", "uploadModelDescription3": "Max file size: {size}", "uploadModelFailedToRetrieveMetadata": "Failed to retrieve metadata. Please check the link and try again.", "uploadModelFromCivitai": "Import a model from Civitai", "uploadModelGeneric": "Import a model", "uploadModelHelpFooterText": "Need help finding the URLs? Click on a provider below to see a how-to video.", "uploadModelHelpVideo": "Upload Model Help Video", "uploadModelHowDoIFindThis": "How do I find this?", "uploadSuccess": "Model imported successfully!", "ariaLabel": { "assetCard": "{name} - {type} asset", "loadingAsset": "Loading asset" }, "media": { "threeDModelPlaceholder": "3D Model", "audioPlaceholder": "Audio" }, "deletion": { "header": "Delete this model?", "body": "This model will be permanently removed from your library.", "inProgress": "Deleting {assetName}...", "complete": "{assetName} has been deleted.", "failed": "{assetName} could not be deleted." }, "rename": { "failed": "Could not rename asset." } };
const mediaAsset = { "deleteAssetTitle": "Delete this asset?", "deleteAssetDescription": "This asset will be permanently removed.", "deleteSelectedTitle": "Delete selected assets?", "deleteSelectedDescription": "{count} asset(s) will be permanently removed.", "assetDeletedSuccessfully": "Asset deleted successfully", "deletingImportedFilesCloudOnly": "Deleting imported files is only supported in cloud version", "failedToDeleteAsset": "Failed to delete asset", "actions": { "inspect": "Inspect asset", "more": "More options", "seeMoreOutputs": "See more outputs", "addToWorkflow": "Add to current workflow", "download": "Download", "openWorkflow": "Open as workflow in new tab", "exportWorkflow": "Export workflow", "copyJobId": "Copy job ID", "delete": "Delete" }, "jobIdToast": { "jobIdCopied": "Job ID copied to clipboard", "jobIdCopyFailed": "Failed to copy Job ID", "copied": "Copied", "error": "Error" }, "selection": { "selectedCount": "Assets Selected: {count}", "deselectAll": "Deselect all", "downloadSelected": "Download", "deleteSelected": "Delete", "downloadStarted": "Downloading {count} files...", "downloadsStarted": "Started downloading {count} file(s)", "assetsDeletedSuccessfully": "{count} asset(s) deleted successfully", "failedToDeleteAssets": "Failed to delete selected assets", "partialDeleteSuccess": "{succeeded} deleted successfully, {failed} failed" }, "noJobIdFound": "No job ID found for this asset", "unsupportedFileType": "Unsupported file type for loader node", "nodeTypeNotFound": "Node type {nodeType} not found", "failedToCreateNode": "Failed to create node", "nodeAddedToWorkflow": "{nodeType} node added to workflow", "noWorkflowDataFound": "No workflow data found in this asset", "workflowOpenedInNewTab": "Workflow opened in new tab", "failedToExportWorkflow": "Failed to export workflow", "workflowExportedSuccessfully": "Workflow exported successfully" };
const actionbar = { "dockToTop": "Dock to top", "feedback": "Feedback", "feedbackTooltip": "Feedback" };
const desktopDialogs = { "": { "title": "Invalid Dialog", "message": "Invalid dialog ID was provided.", "buttons": { "Close": "Close" } } };
const vueNodesBanner = { "title": "Introducing Nodes 2.0", "desc": "– More flexible workflows, powerful new widgets, built for extensibility", "tryItOut": "Try it out" };
const vueNodesMigration = { "message": "Prefer the legacy design?", "button": "Switch back" };
const vueNodesMigrationMainMenu = { "message": "Switch back to Nodes 2.0 anytime from the main menu." };
const linearMode = { "share": "Share", "openWorkflow": "Open Workflow" };
const missingNodes = { "cloud": { "title": "These nodes aren't available on Comfy Cloud yet", "description": "This workflow uses custom nodes that aren't supported in the Cloud version yet.", "priorityMessage": "We've automatically flagged these nodes so we can prioritize adding them.", "replacementInstruction": "In the meantime, replace these nodes (highlighted red on the canvas) with supported ones if possible, or try a different workflow.", "learnMore": "Learn more", "gotIt": "Ok, got it" }, "oss": { "title": "This workflow has missing nodes", "description": "This workflow uses custom nodes you haven't installed yet.", "replacementInstruction": "Install these nodes to run this workflow, or replace them with installed alternatives. Missing nodes are highlighted in red on the canvas." } };
const rightSidePanel = { "togglePanel": "Toggle properties panel", "noSelection": "Select a node to see its properties and info.", "title": "No node(s) selected | 1 node selected | {count} nodes selected", "parameters": "Parameters", "info": "Info", "color": "Node color", "pinned": "Pinned", "bypass": "Bypass", "normal": "Normal", "mute": "Mute", "inputs": "INPUTS", "inputsNone": "NO INPUTS", "inputsNoneTooltip": "Node has no inputs", "properties": "Properties", "nodeState": "Node state", "settings": "Settings" };
const help = { "recentReleases": "Recent releases", "helpCenterMenu": "Help Center Menu" };
const en = {
  g,
  manager,
  issueReport,
  color,
  contextMenu,
  icon,
  welcome,
  userSelect,
  notSupported,
  downloadGit,
  install,
  desktopStart,
  serverStart,
  serverConfig,
  shape,
  sideToolbar,
  helpCenter,
  releaseToast,
  menu,
  tabMenu,
  templateWorkflows,
  templateWidgets,
  graphCanvasMenu,
  zoomControls,
  groupNode,
  nodeTemplates,
  workflowService,
  subgraphStore,
  electronFileDownload,
  maskEditor,
  commands,
  queue,
  menuLabels,
  desktopMenu,
  settingsCategories,
  serverConfigItems,
  serverConfigCategories,
  nodeCategories,
  dataTypes,
  maintenance,
  missingModelsDialog,
  versionMismatchWarning,
  loadWorkflowWarning,
  errorDialog,
  apiNodesSignInDialog,
  apiNodesCostBreakdown,
  desktopUpdate,
  clipboard,
  imageCompare,
  load3d,
  toastMessages,
  nodeErrors,
  auth,
  validation,
  credits,
  subscription,
  userSettings,
  selectionToolbox,
  widgets: widgets$1,
  widgetFileUpload,
  nodeHelpPage,
  whatsNewPopup,
  breadcrumbsMenu,
  shortcuts,
  minimap,
  cloudOnboarding,
  cloudFooter_needHelp,
  cloudStart_title,
  cloudStart_desc,
  cloudStart_explain,
  cloudStart_learnAboutButton,
  cloudStart_wantToRun,
  cloudStart_download,
  cloudWaitlist_questionsText,
  cloudWaitlist_contactLink,
  cloudSorryContactSupport_title,
  cloudPrivateBeta_title,
  cloudPrivateBeta_desc,
  cloudForgotPassword_title,
  cloudForgotPassword_instructions,
  cloudForgotPassword_emailLabel,
  cloudForgotPassword_emailPlaceholder,
  cloudForgotPassword_sendResetLink,
  cloudForgotPassword_backToLogin,
  cloudForgotPassword_didntReceiveEmail,
  cloudForgotPassword_emailRequired,
  cloudForgotPassword_passwordResetSent,
  cloudForgotPassword_passwordResetError,
  cloudSurvey_steps_familiarity,
  cloudSurvey_steps_purpose,
  cloudSurvey_steps_industry,
  cloudSurvey_steps_making,
  assetBrowser,
  mediaAsset,
  actionbar,
  desktopDialogs,
  vueNodesBanner,
  vueNodesMigration,
  vueNodesMigrationMainMenu,
  linearMode,
  missingNodes,
  rightSidePanel,
  help
};
const AddNoise = { "display_name": "AddNoise", "inputs": { "model": { "name": "model" }, "noise": { "name": "noise" }, "sigmas": { "name": "sigmas" }, "latent_image": { "name": "latent_image" } }, "outputs": { "0": { "tooltip": null } } };
const AddTextPrefix = { "display_name": "Add Text Prefix", "inputs": { "texts": { "name": "texts", "tooltip": "Text to process." }, "prefix": { "name": "prefix", "tooltip": "Prefix to add." } }, "outputs": { "0": { "name": "texts", "tooltip": "Processed texts" } } };
const AddTextSuffix = { "display_name": "Add Text Suffix", "inputs": { "texts": { "name": "texts", "tooltip": "Text to process." }, "suffix": { "name": "suffix", "tooltip": "Suffix to add." } }, "outputs": { "0": { "name": "texts", "tooltip": "Processed texts" } } };
const AdjustBrightness = { "display_name": "Adjust Brightness", "inputs": { "images": { "name": "images", "tooltip": "Image to process." }, "factor": { "name": "factor", "tooltip": "Brightness factor. 1.0 = no change, <1.0 = darker, >1.0 = brighter." } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const AdjustContrast = { "display_name": "Adjust Contrast", "inputs": { "images": { "name": "images", "tooltip": "Image to process." }, "factor": { "name": "factor", "tooltip": "Contrast factor. 1.0 = no change, <1.0 = less contrast, >1.0 = more contrast." } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const AlignYourStepsScheduler = { "display_name": "AlignYourStepsScheduler", "inputs": { "model_type": { "name": "model_type" }, "steps": { "name": "steps" }, "denoise": { "name": "denoise" } }, "outputs": { "0": { "tooltip": null } } };
const APG = { "display_name": "Adaptive Projected Guidance", "inputs": { "model": { "name": "model" }, "eta": { "name": "eta", "tooltip": "Controls the scale of the parallel guidance vector. Default CFG behavior at a setting of 1." }, "norm_threshold": { "name": "norm_threshold", "tooltip": "Normalize guidance vector to this value, normalization disable at a setting of 0." }, "momentum": { "name": "momentum", "tooltip": "Controls a running average of guidance during diffusion, disabled at a setting of 0." } }, "outputs": { "0": { "tooltip": null } } };
const AudioAdjustVolume = { "display_name": "Audio Adjust Volume", "inputs": { "audio": { "name": "audio" }, "volume": { "name": "volume", "tooltip": "Volume adjustment in decibels (dB). 0 = no change, +6 = double, -6 = half, etc" } }, "outputs": { "0": { "tooltip": null } } };
const AudioConcat = { "display_name": "Audio Concat", "description": "Concatenates the audio1 to audio2 in the specified direction.", "inputs": { "audio1": { "name": "audio1" }, "audio2": { "name": "audio2" }, "direction": { "name": "direction", "tooltip": "Whether to append audio2 after or before audio1." } }, "outputs": { "0": { "tooltip": null } } };
const AudioEncoderEncode = { "display_name": "AudioEncoderEncode", "inputs": { "audio_encoder": { "name": "audio_encoder" }, "audio": { "name": "audio" } }, "outputs": { "0": { "tooltip": null } } };
const AudioEncoderLoader = { "display_name": "AudioEncoderLoader", "inputs": { "audio_encoder_name": { "name": "audio_encoder_name" } }, "outputs": { "0": { "tooltip": null } } };
const AudioMerge = { "display_name": "Audio Merge", "description": "Combine two audio tracks by overlaying their waveforms.", "inputs": { "audio1": { "name": "audio1" }, "audio2": { "name": "audio2" }, "merge_method": { "name": "merge_method", "tooltip": "The method used to combine the audio waveforms." } }, "outputs": { "0": { "tooltip": null } } };
const BasicGuider = { "display_name": "BasicGuider", "inputs": { "model": { "name": "model" }, "conditioning": { "name": "conditioning" } }, "outputs": { "0": { "tooltip": null } } };
const BasicScheduler = { "display_name": "BasicScheduler", "inputs": { "model": { "name": "model" }, "scheduler": { "name": "scheduler" }, "steps": { "name": "steps" }, "denoise": { "name": "denoise" } }, "outputs": { "0": { "tooltip": null } } };
const BetaSamplingScheduler = { "display_name": "BetaSamplingScheduler", "inputs": { "model": { "name": "model" }, "steps": { "name": "steps" }, "alpha": { "name": "alpha" }, "beta": { "name": "beta" } }, "outputs": { "0": { "tooltip": null } } };
const ByteDanceFirstLastFrameNode = { "display_name": "ByteDance First-Last-Frame to Video", "description": "Generate video using prompt and first and last frames.", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt", "tooltip": "The text prompt used to generate the video." }, "first_frame": { "name": "first_frame", "tooltip": "First frame to be used for the video." }, "last_frame": { "name": "last_frame", "tooltip": "Last frame to be used for the video." }, "resolution": { "name": "resolution", "tooltip": "The resolution of the output video." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "The aspect ratio of the output video." }, "duration": { "name": "duration", "tooltip": "The duration of the output video in seconds." }, "seed": { "name": "seed", "tooltip": "Seed to use for generation." }, "camera_fixed": { "name": "camera_fixed", "tooltip": "Specifies whether to fix the camera. The platform appends an instruction to fix the camera to your prompt, but does not guarantee the actual effect." }, "watermark": { "name": "watermark", "tooltip": 'Whether to add an "AI generated" watermark to the video.' }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ByteDanceImageEditNode = { "display_name": "ByteDance Image Edit", "description": "Edit images using ByteDance models via api based on prompt", "inputs": { "model": { "name": "model" }, "image": { "name": "image", "tooltip": "The base image to edit" }, "prompt": { "name": "prompt", "tooltip": "Instruction to edit image" }, "seed": { "name": "seed", "tooltip": "Seed to use for generation" }, "guidance_scale": { "name": "guidance_scale", "tooltip": "Higher value makes the image follow the prompt more closely" }, "watermark": { "name": "watermark", "tooltip": 'Whether to add an "AI generated" watermark to the image' }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ByteDanceImageNode = { "display_name": "ByteDance Image", "description": "Generate images using ByteDance models via api based on prompt", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt", "tooltip": "The text prompt used to generate the image" }, "size_preset": { "name": "size_preset", "tooltip": "Pick a recommended size. Select Custom to use the width and height below" }, "width": { "name": "width", "tooltip": "Custom width for image. Value is working only if `size_preset` is set to `Custom`" }, "height": { "name": "height", "tooltip": "Custom height for image. Value is working only if `size_preset` is set to `Custom`" }, "seed": { "name": "seed", "tooltip": "Seed to use for generation" }, "guidance_scale": { "name": "guidance_scale", "tooltip": "Higher value makes the image follow the prompt more closely" }, "watermark": { "name": "watermark", "tooltip": 'Whether to add an "AI generated" watermark to the image' }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ByteDanceImageReferenceNode = { "display_name": "ByteDance Reference Images to Video", "description": "Generate video using prompt and reference images.", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt", "tooltip": "The text prompt used to generate the video." }, "images": { "name": "images", "tooltip": "One to four images." }, "resolution": { "name": "resolution", "tooltip": "The resolution of the output video." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "The aspect ratio of the output video." }, "duration": { "name": "duration", "tooltip": "The duration of the output video in seconds." }, "seed": { "name": "seed", "tooltip": "Seed to use for generation." }, "watermark": { "name": "watermark", "tooltip": 'Whether to add an "AI generated" watermark to the video.' }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ByteDanceImageToVideoNode = { "display_name": "ByteDance Image to Video", "description": "Generate video using ByteDance models via api based on image and prompt", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt", "tooltip": "The text prompt used to generate the video." }, "image": { "name": "image", "tooltip": "First frame to be used for the video." }, "resolution": { "name": "resolution", "tooltip": "The resolution of the output video." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "The aspect ratio of the output video." }, "duration": { "name": "duration", "tooltip": "The duration of the output video in seconds." }, "seed": { "name": "seed", "tooltip": "Seed to use for generation." }, "camera_fixed": { "name": "camera_fixed", "tooltip": "Specifies whether to fix the camera. The platform appends an instruction to fix the camera to your prompt, but does not guarantee the actual effect." }, "watermark": { "name": "watermark", "tooltip": 'Whether to add an "AI generated" watermark to the video.' }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ByteDanceSeedreamNode = { "display_name": "ByteDance Seedream 4", "description": "Unified text-to-image generation and precise single-sentence editing at up to 4K resolution.", "inputs": { "model": { "name": "model", "tooltip": "Model name" }, "prompt": { "name": "prompt", "tooltip": "Text prompt for creating or editing an image." }, "size_preset": { "name": "size_preset", "tooltip": "Pick a recommended size. Select Custom to use the width and height below." }, "image": { "name": "image", "tooltip": "Input image(s) for image-to-image generation. List of 1-10 images for single or multi-reference generation." }, "width": { "name": "width", "tooltip": "Custom width for image. Value is working only if `size_preset` is set to `Custom`" }, "height": { "name": "height", "tooltip": "Custom height for image. Value is working only if `size_preset` is set to `Custom`" }, "sequential_image_generation": { "name": "sequential_image_generation", "tooltip": "Group image generation mode. 'disabled' generates a single image. 'auto' lets the model decide whether to generate multiple related images (e.g., story scenes, character variations)." }, "max_images": { "name": "max_images", "tooltip": "Maximum number of images to generate when sequential_image_generation='auto'. Total images (input + generated) cannot exceed 15." }, "seed": { "name": "seed", "tooltip": "Seed to use for generation." }, "watermark": { "name": "watermark", "tooltip": 'Whether to add an "AI generated" watermark to the image.' }, "fail_on_partial": { "name": "fail_on_partial", "tooltip": "If enabled, abort execution if any requested images are missing or return an error." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ByteDanceTextToVideoNode = { "display_name": "ByteDance Text to Video", "description": "Generate video using ByteDance models via api based on prompt", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt", "tooltip": "The text prompt used to generate the video." }, "resolution": { "name": "resolution", "tooltip": "The resolution of the output video." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "The aspect ratio of the output video." }, "duration": { "name": "duration", "tooltip": "The duration of the output video in seconds." }, "seed": { "name": "seed", "tooltip": "Seed to use for generation." }, "camera_fixed": { "name": "camera_fixed", "tooltip": "Specifies whether to fix the camera. The platform appends an instruction to fix the camera to your prompt, but does not guarantee the actual effect." }, "watermark": { "name": "watermark", "tooltip": 'Whether to add an "AI generated" watermark to the video.' }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const Canny = { "display_name": "Canny", "inputs": { "image": { "name": "image" }, "low_threshold": { "name": "low_threshold" }, "high_threshold": { "name": "high_threshold" } }, "outputs": { "0": { "tooltip": null } } };
const CaseConverter = { "display_name": "Case Converter", "inputs": { "string": { "name": "string" }, "mode": { "name": "mode" } }, "outputs": { "0": { "tooltip": null } } };
const CenterCropImages = { "display_name": "Center Crop Images", "inputs": { "images": { "name": "images", "tooltip": "Image to process." }, "width": { "name": "width", "tooltip": "Crop width." }, "height": { "name": "height", "tooltip": "Crop height." } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const CFGGuider = { "display_name": "CFGGuider", "inputs": { "model": { "name": "model" }, "positive": { "name": "positive" }, "negative": { "name": "negative" }, "cfg": { "name": "cfg" } }, "outputs": { "0": { "tooltip": null } } };
const CFGNorm = { "display_name": "CFGNorm", "inputs": { "model": { "name": "model" }, "strength": { "name": "strength" } }, "outputs": { "0": { "name": "patched_model", "tooltip": null } } };
const CFGZeroStar = { "display_name": "CFGZeroStar", "inputs": { "model": { "name": "model" } }, "outputs": { "0": { "name": "patched_model", "tooltip": null } } };
const CheckpointLoader = { "display_name": "Load Checkpoint With Config (DEPRECATED)", "inputs": { "config_name": { "name": "config_name" }, "ckpt_name": { "name": "ckpt_name" } } };
const CheckpointLoaderSimple = { "display_name": "Load Checkpoint", "description": "Loads a diffusion model checkpoint, diffusion models are used to denoise latents.", "inputs": { "ckpt_name": { "name": "ckpt_name", "tooltip": "The name of the checkpoint (model) to load." } }, "outputs": { "0": { "tooltip": "The model used for denoising latents." }, "1": { "tooltip": "The CLIP model used for encoding text prompts." }, "2": { "tooltip": "The VAE model used for encoding and decoding images to and from latent space." } } };
const CheckpointSave = { "display_name": "Save Checkpoint", "inputs": { "model": { "name": "model" }, "clip": { "name": "clip" }, "vae": { "name": "vae" }, "filename_prefix": { "name": "filename_prefix" } } };
const ChromaRadianceOptions = { "display_name": "ChromaRadianceOptions", "description": "Allows setting advanced options for the Chroma Radiance model.", "inputs": { "model": { "name": "model" }, "preserve_wrapper": { "name": "preserve_wrapper", "tooltip": "When enabled, will delegate to an existing model function wrapper if it exists. Generally should be left enabled." }, "start_sigma": { "name": "start_sigma", "tooltip": "First sigma that these options will be in effect." }, "end_sigma": { "name": "end_sigma", "tooltip": "Last sigma that these options will be in effect." }, "nerf_tile_size": { "name": "nerf_tile_size", "tooltip": "Allows overriding the default NeRF tile size. -1 means use the default (32). 0 means use non-tiling mode (may require a lot of VRAM)." } }, "outputs": { "0": { "tooltip": null } } };
const CLIPAttentionMultiply = { "display_name": "CLIPAttentionMultiply", "inputs": { "clip": { "name": "clip" }, "q": { "name": "q" }, "k": { "name": "k" }, "v": { "name": "v" }, "out": { "name": "out" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPLoader = { "display_name": "Load CLIP", "description": "[Recipes]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 xxl/ clip-g / clip-l\nstable_audio: t5 base\nmochi: t5 xxl\ncosmos: old t5 xxl\nlumina2: gemma 2 2B\nwan: umt5 xxl\n hidream: llama-3.1 (Recommend) or t5\nomnigen2: qwen vl 2.5 3B", "inputs": { "clip_name": { "name": "clip_name" }, "type": { "name": "type" }, "device": { "name": "device" } } };
const CLIPMergeAdd = { "display_name": "CLIPMergeAdd", "inputs": { "clip1": { "name": "clip1" }, "clip2": { "name": "clip2" } } };
const CLIPMergeSimple = { "display_name": "CLIPMergeSimple", "inputs": { "clip1": { "name": "clip1" }, "clip2": { "name": "clip2" }, "ratio": { "name": "ratio" } } };
const CLIPMergeSubtract = { "display_name": "CLIPMergeSubtract", "inputs": { "clip1": { "name": "clip1" }, "clip2": { "name": "clip2" }, "multiplier": { "name": "multiplier" } } };
const CLIPSave = { "display_name": "CLIPSave", "inputs": { "clip": { "name": "clip" }, "filename_prefix": { "name": "filename_prefix" } } };
const CLIPSetLastLayer = { "display_name": "CLIP Set Last Layer", "inputs": { "clip": { "name": "clip" }, "stop_at_clip_layer": { "name": "stop_at_clip_layer" } } };
const CLIPTextEncode = { "display_name": "CLIP Text Encode (Prompt)", "description": "Encodes a text prompt using a CLIP model into an embedding that can be used to guide the diffusion model towards generating specific images.", "inputs": { "text": { "name": "text", "tooltip": "The text to be encoded." }, "clip": { "name": "clip", "tooltip": "The CLIP model used for encoding the text." } }, "outputs": { "0": { "tooltip": "A conditioning containing the embedded text used to guide the diffusion model." } } };
const CLIPTextEncodeControlnet = { "display_name": "CLIPTextEncodeControlnet", "inputs": { "clip": { "name": "clip" }, "conditioning": { "name": "conditioning" }, "text": { "name": "text" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPTextEncodeFlux = { "display_name": "CLIPTextEncodeFlux", "inputs": { "clip": { "name": "clip" }, "clip_l": { "name": "clip_l" }, "t5xxl": { "name": "t5xxl" }, "guidance": { "name": "guidance" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPTextEncodeHiDream = { "display_name": "CLIPTextEncodeHiDream", "inputs": { "clip": { "name": "clip" }, "clip_l": { "name": "clip_l" }, "clip_g": { "name": "clip_g" }, "t5xxl": { "name": "t5xxl" }, "llama": { "name": "llama" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPTextEncodeHunyuanDiT = { "display_name": "CLIPTextEncodeHunyuanDiT", "inputs": { "clip": { "name": "clip" }, "bert": { "name": "bert" }, "mt5xl": { "name": "mt5xl" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPTextEncodeKandinsky5 = { "display_name": "CLIPTextEncodeKandinsky5", "inputs": { "clip": { "name": "clip" }, "clip_l": { "name": "clip_l" }, "qwen25_7b": { "name": "qwen25_7b" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPTextEncodeLumina2 = { "display_name": "CLIP Text Encode for Lumina2", "description": "Encodes a system prompt and a user prompt using a CLIP model into an embedding that can be used to guide the diffusion model towards generating specific images.", "inputs": { "system_prompt": { "name": "system_prompt", "tooltip": "Lumina2 provide two types of system prompts:Superior: You are an assistant designed to generate superior images with the superior degree of image-text alignment based on textual prompts or user prompts. Alignment: You are an assistant designed to generate high-quality images with the highest degree of image-text alignment based on textual prompts." }, "user_prompt": { "name": "user_prompt", "tooltip": "The text to be encoded." }, "clip": { "name": "clip", "tooltip": "The CLIP model used for encoding the text." } }, "outputs": { "0": { "tooltip": "A conditioning containing the embedded text used to guide the diffusion model." } } };
const CLIPTextEncodePixArtAlpha = { "display_name": "CLIPTextEncodePixArtAlpha", "description": "Encodes text and sets the resolution conditioning for PixArt Alpha. Does not apply to PixArt Sigma.", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "text": { "name": "text" }, "clip": { "name": "clip" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPTextEncodeSD3 = { "display_name": "CLIPTextEncodeSD3", "inputs": { "clip": { "name": "clip" }, "clip_l": { "name": "clip_l" }, "clip_g": { "name": "clip_g" }, "t5xxl": { "name": "t5xxl" }, "empty_padding": { "name": "empty_padding" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPTextEncodeSDXL = { "display_name": "CLIPTextEncodeSDXL", "inputs": { "clip": { "name": "clip" }, "width": { "name": "width" }, "height": { "name": "height" }, "crop_w": { "name": "crop_w" }, "crop_h": { "name": "crop_h" }, "target_width": { "name": "target_width" }, "target_height": { "name": "target_height" }, "text_g": { "name": "text_g" }, "text_l": { "name": "text_l" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPTextEncodeSDXLRefiner = { "display_name": "CLIPTextEncodeSDXLRefiner", "inputs": { "ascore": { "name": "ascore" }, "width": { "name": "width" }, "height": { "name": "height" }, "text": { "name": "text" }, "clip": { "name": "clip" } }, "outputs": { "0": { "tooltip": null } } };
const CLIPVisionEncode = { "display_name": "CLIP Vision Encode", "inputs": { "clip_vision": { "name": "clip_vision" }, "image": { "name": "image" }, "crop": { "name": "crop" } } };
const CLIPVisionLoader = { "display_name": "Load CLIP Vision", "inputs": { "clip_name": { "name": "clip_name" } } };
const CombineHooks2 = { "display_name": "Combine Hooks [2]", "inputs": { "hooks_A": { "name": "hooks_A" }, "hooks_B": { "name": "hooks_B" } } };
const CombineHooks4 = { "display_name": "Combine Hooks [4]", "inputs": { "hooks_A": { "name": "hooks_A" }, "hooks_B": { "name": "hooks_B" }, "hooks_C": { "name": "hooks_C" }, "hooks_D": { "name": "hooks_D" } } };
const CombineHooks8 = { "display_name": "Combine Hooks [8]", "inputs": { "hooks_A": { "name": "hooks_A" }, "hooks_B": { "name": "hooks_B" }, "hooks_C": { "name": "hooks_C" }, "hooks_D": { "name": "hooks_D" }, "hooks_E": { "name": "hooks_E" }, "hooks_F": { "name": "hooks_F" }, "hooks_G": { "name": "hooks_G" }, "hooks_H": { "name": "hooks_H" } } };
const ConditioningAverage = { "display_name": "ConditioningAverage", "inputs": { "conditioning_to": { "name": "conditioning_to" }, "conditioning_from": { "name": "conditioning_from" }, "conditioning_to_strength": { "name": "conditioning_to_strength" } } };
const ConditioningCombine = { "display_name": "Conditioning (Combine)", "inputs": { "conditioning_1": { "name": "conditioning_1" }, "conditioning_2": { "name": "conditioning_2" } } };
const ConditioningConcat = { "display_name": "Conditioning (Concat)", "inputs": { "conditioning_to": { "name": "conditioning_to" }, "conditioning_from": { "name": "conditioning_from" } } };
const ConditioningSetArea = { "display_name": "Conditioning (Set Area)", "inputs": { "conditioning": { "name": "conditioning" }, "width": { "name": "width" }, "height": { "name": "height" }, "x": { "name": "x" }, "y": { "name": "y" }, "strength": { "name": "strength" } } };
const ConditioningSetAreaPercentage = { "display_name": "Conditioning (Set Area with Percentage)", "inputs": { "conditioning": { "name": "conditioning" }, "width": { "name": "width" }, "height": { "name": "height" }, "x": { "name": "x" }, "y": { "name": "y" }, "strength": { "name": "strength" } } };
const ConditioningSetAreaPercentageVideo = { "display_name": "ConditioningSetAreaPercentageVideo", "inputs": { "conditioning": { "name": "conditioning" }, "width": { "name": "width" }, "height": { "name": "height" }, "temporal": { "name": "temporal" }, "x": { "name": "x" }, "y": { "name": "y" }, "z": { "name": "z" }, "strength": { "name": "strength" } } };
const ConditioningSetAreaStrength = { "display_name": "ConditioningSetAreaStrength", "inputs": { "conditioning": { "name": "conditioning" }, "strength": { "name": "strength" } } };
const ConditioningSetDefaultCombine = { "display_name": "Cond Set Default Combine", "inputs": { "cond": { "name": "cond" }, "cond_DEFAULT": { "name": "cond_DEFAULT" }, "hooks": { "name": "hooks" } } };
const ConditioningSetMask = { "display_name": "Conditioning (Set Mask)", "inputs": { "conditioning": { "name": "conditioning" }, "mask": { "name": "mask" }, "strength": { "name": "strength" }, "set_cond_area": { "name": "set_cond_area" } } };
const ConditioningSetProperties = { "display_name": "Cond Set Props", "inputs": { "cond_NEW": { "name": "cond_NEW" }, "strength": { "name": "strength" }, "set_cond_area": { "name": "set_cond_area" }, "mask": { "name": "mask" }, "hooks": { "name": "hooks" }, "timesteps": { "name": "timesteps" } } };
const ConditioningSetPropertiesAndCombine = { "display_name": "Cond Set Props Combine", "inputs": { "cond": { "name": "cond" }, "cond_NEW": { "name": "cond_NEW" }, "strength": { "name": "strength" }, "set_cond_area": { "name": "set_cond_area" }, "mask": { "name": "mask" }, "hooks": { "name": "hooks" }, "timesteps": { "name": "timesteps" } } };
const ConditioningSetTimestepRange = { "display_name": "ConditioningSetTimestepRange", "inputs": { "conditioning": { "name": "conditioning" }, "start": { "name": "start" }, "end": { "name": "end" } } };
const ConditioningStableAudio = { "display_name": "ConditioningStableAudio", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "seconds_start": { "name": "seconds_start" }, "seconds_total": { "name": "seconds_total" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null } } };
const ConditioningTimestepsRange = { "display_name": "Timesteps Range", "inputs": { "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" } }, "outputs": { "1": { "name": "BEFORE_RANGE" }, "2": { "name": "AFTER_RANGE" } } };
const ConditioningZeroOut = { "display_name": "ConditioningZeroOut", "inputs": { "conditioning": { "name": "conditioning" } } };
const ContextWindowsManual = { "display_name": "Context Windows (Manual)", "description": "Manually set context windows.", "inputs": { "model": { "name": "model", "tooltip": "The model to apply context windows to during sampling." }, "context_length": { "name": "context_length", "tooltip": "The length of the context window." }, "context_overlap": { "name": "context_overlap", "tooltip": "The overlap of the context window." }, "context_schedule": { "name": "context_schedule", "tooltip": "The stride of the context window." }, "context_stride": { "name": "context_stride", "tooltip": "The stride of the context window; only applicable to uniform schedules." }, "closed_loop": { "name": "closed_loop", "tooltip": "Whether to close the context window loop; only applicable to looped schedules." }, "fuse_method": { "name": "fuse_method", "tooltip": "The method to use to fuse the context windows." }, "dim": { "name": "dim", "tooltip": "The dimension to apply the context windows to." }, "freenoise": { "name": "freenoise", "tooltip": "Whether to apply FreeNoise noise shuffling, improves window blending." } }, "outputs": { "0": { "tooltip": "The model with context windows applied during sampling." } } };
const ControlNetApply = { "display_name": "Apply ControlNet (OLD)", "inputs": { "conditioning": { "name": "conditioning" }, "control_net": { "name": "control_net" }, "image": { "name": "image" }, "strength": { "name": "strength" } } };
const ControlNetApplyAdvanced = { "display_name": "Apply ControlNet", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "control_net": { "name": "control_net" }, "image": { "name": "image" }, "strength": { "name": "strength" }, "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" }, "vae": { "name": "vae" } }, "outputs": { "0": { "name": "positive" }, "1": { "name": "negative" } } };
const ControlNetApplySD3 = { "display_name": "Apply Controlnet with VAE", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "control_net": { "name": "control_net" }, "vae": { "name": "vae" }, "image": { "name": "image" }, "strength": { "name": "strength" }, "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null } } };
const ControlNetInpaintingAliMamaApply = { "display_name": "ControlNetInpaintingAliMamaApply", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "control_net": { "name": "control_net" }, "vae": { "name": "vae" }, "image": { "name": "image" }, "mask": { "name": "mask" }, "strength": { "name": "strength" }, "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null } } };
const ControlNetLoader = { "display_name": "Load ControlNet Model", "inputs": { "control_net_name": { "name": "control_net_name" } } };
const CosmosImageToVideoLatent = { "display_name": "CosmosImageToVideoLatent", "inputs": { "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "start_image": { "name": "start_image" }, "end_image": { "name": "end_image" } }, "outputs": { "0": { "tooltip": null } } };
const CosmosPredict2ImageToVideoLatent = { "display_name": "CosmosPredict2ImageToVideoLatent", "inputs": { "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "start_image": { "name": "start_image" }, "end_image": { "name": "end_image" } }, "outputs": { "0": { "tooltip": null } } };
const CreateHookKeyframe = { "display_name": "Create Hook Keyframe", "inputs": { "strength_mult": { "name": "strength_mult" }, "start_percent": { "name": "start_percent" }, "prev_hook_kf": { "name": "prev_hook_kf" } }, "outputs": { "0": { "name": "HOOK_KF" } } };
const CreateHookKeyframesFromFloats = { "display_name": "Create Hook Keyframes From Floats", "inputs": { "floats_strength": { "name": "floats_strength" }, "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" }, "print_keyframes": { "name": "print_keyframes" }, "prev_hook_kf": { "name": "prev_hook_kf" } }, "outputs": { "0": { "name": "HOOK_KF" } } };
const CreateHookKeyframesInterpolated = { "display_name": "Create Hook Keyframes Interp.", "inputs": { "strength_start": { "name": "strength_start" }, "strength_end": { "name": "strength_end" }, "interpolation": { "name": "interpolation" }, "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" }, "keyframes_count": { "name": "keyframes_count" }, "print_keyframes": { "name": "print_keyframes" }, "prev_hook_kf": { "name": "prev_hook_kf" } }, "outputs": { "0": { "name": "HOOK_KF" } } };
const CreateHookLora = { "display_name": "Create Hook LoRA", "inputs": { "lora_name": { "name": "lora_name" }, "strength_model": { "name": "strength_model" }, "strength_clip": { "name": "strength_clip" }, "prev_hooks": { "name": "prev_hooks" } } };
const CreateHookLoraModelOnly = { "display_name": "Create Hook LoRA (MO)", "inputs": { "lora_name": { "name": "lora_name" }, "strength_model": { "name": "strength_model" }, "prev_hooks": { "name": "prev_hooks" } } };
const CreateHookModelAsLora = { "display_name": "Create Hook Model as LoRA", "inputs": { "ckpt_name": { "name": "ckpt_name" }, "strength_model": { "name": "strength_model" }, "strength_clip": { "name": "strength_clip" }, "prev_hooks": { "name": "prev_hooks" } } };
const CreateHookModelAsLoraModelOnly = { "display_name": "Create Hook Model as LoRA (MO)", "inputs": { "ckpt_name": { "name": "ckpt_name" }, "strength_model": { "name": "strength_model" }, "prev_hooks": { "name": "prev_hooks" } } };
const CreateVideo = { "display_name": "Create Video", "description": "Create a video from images.", "inputs": { "images": { "name": "images", "tooltip": "The images to create a video from." }, "fps": { "name": "fps" }, "audio": { "name": "audio", "tooltip": "The audio to add to the video." } }, "outputs": { "0": { "tooltip": null } } };
const CropMask = { "display_name": "CropMask", "inputs": { "mask": { "name": "mask" }, "x": { "name": "x" }, "y": { "name": "y" }, "width": { "name": "width" }, "height": { "name": "height" } }, "outputs": { "0": { "tooltip": null } } };
const DiffControlNetLoader = { "display_name": "Load ControlNet Model (diff)", "inputs": { "model": { "name": "model" }, "control_net_name": { "name": "control_net_name" } } };
const DifferentialDiffusion = { "display_name": "Differential Diffusion", "inputs": { "model": { "name": "model" }, "strength": { "name": "strength" } }, "outputs": { "0": { "tooltip": null } } };
const DiffusersLoader = { "display_name": "DiffusersLoader", "inputs": { "model_path": { "name": "model_path" } } };
const DisableNoise = { "display_name": "DisableNoise", "outputs": { "0": { "tooltip": null } } };
const DualCFGGuider = { "display_name": "DualCFGGuider", "inputs": { "model": { "name": "model" }, "cond1": { "name": "cond1" }, "cond2": { "name": "cond2" }, "negative": { "name": "negative" }, "cfg_conds": { "name": "cfg_conds" }, "cfg_cond2_negative": { "name": "cfg_cond2_negative" }, "style": { "name": "style" } }, "outputs": { "0": { "tooltip": null } } };
const DualCLIPLoader = { "display_name": "DualCLIPLoader", "description": "[Recipes]\n\nsdxl: clip-l, clip-g\nsd3: clip-l, clip-g / clip-l, t5 / clip-g, t5\nflux: clip-l, t5\nhidream: at least one of t5 or llama, recommended t5 and llama\nhunyuan_image: qwen2.5vl 7b and byt5 small\nnewbie: gemma-3-4b-it, jina clip v2", "inputs": { "clip_name1": { "name": "clip_name1" }, "clip_name2": { "name": "clip_name2" }, "type": { "name": "type" }, "device": { "name": "device" } } };
const EasyCache = { "display_name": "EasyCache", "description": "Native EasyCache implementation.", "inputs": { "model": { "name": "model", "tooltip": "The model to add EasyCache to." }, "reuse_threshold": { "name": "reuse_threshold", "tooltip": "The threshold for reusing cached steps." }, "start_percent": { "name": "start_percent", "tooltip": "The relative sampling step to begin use of EasyCache." }, "end_percent": { "name": "end_percent", "tooltip": "The relative sampling step to end use of EasyCache." }, "verbose": { "name": "verbose", "tooltip": "Whether to log verbose information." } }, "outputs": { "0": { "tooltip": "The model with EasyCache." } } };
const EmptyAceStepLatentAudio = { "display_name": "EmptyAceStepLatentAudio", "inputs": { "seconds": { "name": "seconds" }, "batch_size": { "name": "batch_size", "tooltip": "The number of latent images in the batch." } }, "outputs": { "0": { "tooltip": null } } };
const EmptyAudio = { "display_name": "Empty Audio", "inputs": { "duration": { "name": "duration", "tooltip": "Duration of the empty audio clip in seconds" }, "sample_rate": { "name": "sample_rate", "tooltip": "Sample rate of the empty audio clip." }, "channels": { "name": "channels", "tooltip": "Number of audio channels (1 for mono, 2 for stereo)." } }, "outputs": { "0": { "tooltip": null } } };
const EmptyChromaRadianceLatentImage = { "display_name": "EmptyChromaRadianceLatentImage", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const EmptyCosmosLatentVideo = { "display_name": "EmptyCosmosLatentVideo", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const EmptyFlux2LatentImage = { "display_name": "Empty Flux 2 Latent", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const EmptyHunyuanImageLatent = { "display_name": "EmptyHunyuanImageLatent", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const EmptyHunyuanLatentVideo = { "display_name": "Empty HunyuanVideo 1.0 Latent", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const EmptyHunyuanVideo15Latent = { "display_name": "Empty HunyuanVideo 1.5 Latent", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const EmptyImage = { "display_name": "EmptyImage", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "batch_size": { "name": "batch_size" }, "color": { "name": "color" } } };
const EmptyLatentAudio = { "display_name": "Empty Latent Audio", "inputs": { "seconds": { "name": "seconds" }, "batch_size": { "name": "batch_size", "tooltip": "The number of latent images in the batch." } }, "outputs": { "0": { "tooltip": null } } };
const EmptyLatentHunyuan3Dv2 = { "display_name": "EmptyLatentHunyuan3Dv2", "inputs": { "resolution": { "name": "resolution" }, "batch_size": { "name": "batch_size", "tooltip": "The number of latent images in the batch." } }, "outputs": { "0": { "tooltip": null } } };
const EmptyLatentImage = { "display_name": "Empty Latent Image", "description": "Create a new batch of empty latent images to be denoised via sampling.", "inputs": { "width": { "name": "width", "tooltip": "The width of the latent images in pixels." }, "height": { "name": "height", "tooltip": "The height of the latent images in pixels." }, "batch_size": { "name": "batch_size", "tooltip": "The number of latent images in the batch." } }, "outputs": { "0": { "tooltip": "The empty latent image batch." } } };
const EmptyLTXVLatentVideo = { "display_name": "EmptyLTXVLatentVideo", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const EmptyMochiLatentVideo = { "display_name": "EmptyMochiLatentVideo", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const EmptyQwenImageLayeredLatentImage = { "display_name": "Empty Qwen Image Layered Latent", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "layers": { "name": "layers" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const EmptySD3LatentImage = { "display_name": "EmptySD3LatentImage", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const ExponentialScheduler = { "display_name": "ExponentialScheduler", "inputs": { "steps": { "name": "steps" }, "sigma_max": { "name": "sigma_max" }, "sigma_min": { "name": "sigma_min" } }, "outputs": { "0": { "tooltip": null } } };
const ExtendIntermediateSigmas = { "display_name": "ExtendIntermediateSigmas", "inputs": { "sigmas": { "name": "sigmas" }, "steps": { "name": "steps" }, "start_at_sigma": { "name": "start_at_sigma" }, "end_at_sigma": { "name": "end_at_sigma" }, "spacing": { "name": "spacing" } }, "outputs": { "0": { "tooltip": null } } };
const FeatherMask = { "display_name": "FeatherMask", "inputs": { "mask": { "name": "mask" }, "left": { "name": "left" }, "top": { "name": "top" }, "right": { "name": "right" }, "bottom": { "name": "bottom" } }, "outputs": { "0": { "tooltip": null } } };
const FlipSigmas = { "display_name": "FlipSigmas", "inputs": { "sigmas": { "name": "sigmas" } }, "outputs": { "0": { "tooltip": null } } };
const Flux2MaxImageNode = { "display_name": "Flux.2 [max] Image", "description": "Generates images synchronously based on prompt and resolution.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation or edit" }, "width": { "name": "width" }, "height": { "name": "height" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "prompt_upsampling": { "name": "prompt_upsampling", "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation." }, "images": { "name": "images", "tooltip": "Up to 9 images to be used as references." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const Flux2ProImageNode = { "display_name": "Flux.2 [pro] Image", "description": "Generates images synchronously based on prompt and resolution.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation or edit" }, "width": { "name": "width" }, "height": { "name": "height" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "prompt_upsampling": { "name": "prompt_upsampling", "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation." }, "images": { "name": "images", "tooltip": "Up to 9 images to be used as references." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const Flux2Scheduler = { "display_name": "Flux2Scheduler", "inputs": { "steps": { "name": "steps" }, "width": { "name": "width" }, "height": { "name": "height" } }, "outputs": { "0": { "tooltip": null } } };
const FluxDisableGuidance = { "display_name": "FluxDisableGuidance", "description": "This node completely disables the guidance embed on Flux and Flux like models", "inputs": { "conditioning": { "name": "conditioning" } }, "outputs": { "0": { "tooltip": null } } };
const FluxGuidance = { "display_name": "FluxGuidance", "inputs": { "conditioning": { "name": "conditioning" }, "guidance": { "name": "guidance" } }, "outputs": { "0": { "tooltip": null } } };
const FluxKontextImageScale = { "display_name": "FluxKontextImageScale", "description": "This node resizes the image to one that is more optimal for flux kontext.", "inputs": { "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null } } };
const FluxKontextMaxImageNode = { "display_name": "Flux.1 Kontext [max] Image", "description": "Edits images using Flux.1 Kontext [pro] via api based on prompt and aspect ratio.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation - specify what and how to edit." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "Aspect ratio of image; must be between 1:4 and 4:1." }, "guidance": { "name": "guidance", "tooltip": "Guidance strength for the image generation process" }, "steps": { "name": "steps", "tooltip": "Number of steps for the image generation process" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "prompt_upsampling": { "name": "prompt_upsampling", "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation, but results are nondeterministic (same seed will not produce exactly the same result)." }, "input_image": { "name": "input_image" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const FluxKontextMultiReferenceLatentMethod = { "display_name": "Edit Model Reference Method", "inputs": { "conditioning": { "name": "conditioning" }, "reference_latents_method": { "name": "reference_latents_method" } }, "outputs": { "0": { "tooltip": null } } };
const FluxKontextProImageNode = { "display_name": "Flux.1 Kontext [pro] Image", "description": "Edits images using Flux.1 Kontext [pro] via api based on prompt and aspect ratio.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation - specify what and how to edit." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "Aspect ratio of image; must be between 1:4 and 4:1." }, "guidance": { "name": "guidance", "tooltip": "Guidance strength for the image generation process" }, "steps": { "name": "steps", "tooltip": "Number of steps for the image generation process" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "prompt_upsampling": { "name": "prompt_upsampling", "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation, but results are nondeterministic (same seed will not produce exactly the same result)." }, "input_image": { "name": "input_image" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const FluxProExpandNode = { "display_name": "Flux.1 Expand Image", "description": "Outpaints image based on prompt.", "inputs": { "image": { "name": "image" }, "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation" }, "prompt_upsampling": { "name": "prompt_upsampling", "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation, but results are nondeterministic (same seed will not produce exactly the same result)." }, "top": { "name": "top", "tooltip": "Number of pixels to expand at the top of the image" }, "bottom": { "name": "bottom", "tooltip": "Number of pixels to expand at the bottom of the image" }, "left": { "name": "left", "tooltip": "Number of pixels to expand at the left of the image" }, "right": { "name": "right", "tooltip": "Number of pixels to expand at the right of the image" }, "guidance": { "name": "guidance", "tooltip": "Guidance strength for the image generation process" }, "steps": { "name": "steps", "tooltip": "Number of steps for the image generation process" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const FluxProFillNode = { "display_name": "Flux.1 Fill Image", "description": "Inpaints image based on mask and prompt.", "inputs": { "image": { "name": "image" }, "mask": { "name": "mask" }, "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation" }, "prompt_upsampling": { "name": "prompt_upsampling", "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation, but results are nondeterministic (same seed will not produce exactly the same result)." }, "guidance": { "name": "guidance", "tooltip": "Guidance strength for the image generation process" }, "steps": { "name": "steps", "tooltip": "Number of steps for the image generation process" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const FluxProUltraImageNode = { "display_name": "Flux 1.1 [pro] Ultra Image", "description": "Generates images using Flux Pro 1.1 Ultra via api based on prompt and resolution.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation" }, "prompt_upsampling": { "name": "prompt_upsampling", "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation, but results are nondeterministic (same seed will not produce exactly the same result)." }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "Aspect ratio of image; must be between 1:4 and 4:1." }, "raw": { "name": "raw", "tooltip": "When True, generate less processed, more natural-looking images." }, "image_prompt": { "name": "image_prompt" }, "image_prompt_strength": { "name": "image_prompt_strength", "tooltip": "Blend between the prompt and the image prompt." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const FreeU = { "display_name": "FreeU", "inputs": { "model": { "name": "model" }, "b1": { "name": "b1" }, "b2": { "name": "b2" }, "s1": { "name": "s1" }, "s2": { "name": "s2" } }, "outputs": { "0": { "tooltip": null } } };
const FreeU_V2 = { "display_name": "FreeU_V2", "inputs": { "model": { "name": "model" }, "b1": { "name": "b1" }, "b2": { "name": "b2" }, "s1": { "name": "s1" }, "s2": { "name": "s2" } }, "outputs": { "0": { "tooltip": null } } };
const FreSca = { "display_name": "FreSca", "description": "Applies frequency-dependent scaling to the guidance", "inputs": { "model": { "name": "model" }, "scale_low": { "name": "scale_low", "tooltip": "Scaling factor for low-frequency components" }, "scale_high": { "name": "scale_high", "tooltip": "Scaling factor for high-frequency components" }, "freq_cutoff": { "name": "freq_cutoff", "tooltip": "Number of frequency indices around center to consider as low-frequency" } }, "outputs": { "0": { "tooltip": null } } };
const GeminiImage2Node = { "display_name": "Nano Banana Pro (Google Gemini Image)", "description": "Generate or edit images synchronously via Google Vertex API.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text prompt describing the image to generate or the edits to apply. Include any constraints, styles, or details the model should follow." }, "model": { "name": "model" }, "seed": { "name": "seed", "tooltip": "When the seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "If set to 'auto', matches your input image's aspect ratio; if no image is provided, a 16:9 square is usually generated." }, "resolution": { "name": "resolution", "tooltip": "Target output resolution. For 2K/4K the native Gemini upscaler is used." }, "response_modalities": { "name": "response_modalities", "tooltip": "Choose 'IMAGE' for image-only output, or 'IMAGE+TEXT' to return both the generated image and a text response." }, "images": { "name": "images", "tooltip": "Optional reference image(s). To include multiple images, use the Batch Images node (up to 14)." }, "files": { "name": "files", "tooltip": "Optional file(s) to use as context for the model. Accepts inputs from the Gemini Generate Content Input Files node." }, "system_prompt": { "name": "system_prompt", "tooltip": "Foundational instructions that dictate an AI's behavior." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null }, "1": { "tooltip": null } } };
const GeminiImageNode = { "display_name": "Nano Banana (Google Gemini Image)", "description": "Edit images synchronously via Google API.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text prompt for generation" }, "model": { "name": "model", "tooltip": "The Gemini model to use for generating responses." }, "seed": { "name": "seed", "tooltip": "When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used." }, "images": { "name": "images", "tooltip": "Optional image(s) to use as context for the model. To include multiple images, you can use the Batch Images node." }, "files": { "name": "files", "tooltip": "Optional file(s) to use as context for the model. Accepts inputs from the Gemini Generate Content Input Files node." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "Defaults to matching the output image size to that of your input image, or otherwise generates 1:1 squares." }, "response_modalities": { "name": "response_modalities", "tooltip": "Choose 'IMAGE' for image-only output, or 'IMAGE+TEXT' to return both the generated image and a text response." }, "system_prompt": { "name": "system_prompt", "tooltip": "Foundational instructions that dictate an AI's behavior." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null }, "1": { "tooltip": null } } };
const GeminiInputFiles = { "display_name": "Gemini Input Files", "description": "Loads and prepares input files to include as inputs for Gemini LLM nodes. The files will be read by the Gemini model when generating a response. The contents of the text file count toward the token limit. 🛈 TIP: Can be chained together with other Gemini Input File nodes.", "inputs": { "file": { "name": "file", "tooltip": "Input files to include as context for the model. Only accepts text (.txt) and PDF (.pdf) files for now." }, "GEMINI_INPUT_FILES": { "name": "GEMINI_INPUT_FILES", "tooltip": "An optional additional file(s) to batch together with the file loaded from this node. Allows chaining of input files so that a single message can include multiple input files." } }, "outputs": { "0": { "tooltip": null } } };
const GeminiNode = { "display_name": "Google Gemini", "description": "Generate text responses with Google's Gemini AI model. You can provide multiple types of inputs (text, images, audio, video) as context for generating more relevant and meaningful responses.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text inputs to the model, used to generate a response. You can include detailed instructions, questions, or context for the model." }, "model": { "name": "model", "tooltip": "The Gemini model to use for generating responses." }, "seed": { "name": "seed", "tooltip": "When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used." }, "images": { "name": "images", "tooltip": "Optional image(s) to use as context for the model. To include multiple images, you can use the Batch Images node." }, "audio": { "name": "audio", "tooltip": "Optional audio to use as context for the model." }, "video": { "name": "video", "tooltip": "Optional video to use as context for the model." }, "files": { "name": "files", "tooltip": "Optional file(s) to use as context for the model. Accepts inputs from the Gemini Generate Content Input Files node." }, "system_prompt": { "name": "system_prompt", "tooltip": "Foundational instructions that dictate an AI's behavior." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const GenerateTracks = { "display_name": "GenerateTracks", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "start_x": { "name": "start_x", "tooltip": "Normalized X coordinate (0-1) for start position." }, "start_y": { "name": "start_y", "tooltip": "Normalized Y coordinate (0-1) for start position." }, "end_x": { "name": "end_x", "tooltip": "Normalized X coordinate (0-1) for end position." }, "end_y": { "name": "end_y", "tooltip": "Normalized Y coordinate (0-1) for end position." }, "num_frames": { "name": "num_frames" }, "num_tracks": { "name": "num_tracks" }, "track_spread": { "name": "track_spread", "tooltip": "Normalized distance between tracks. Tracks are spread perpendicular to the motion direction." }, "bezier": { "name": "bezier", "tooltip": "Enable Bezier curve path using the mid point as control point." }, "mid_x": { "name": "mid_x", "tooltip": "Normalized X control point for Bezier curve. Only used when 'bezier' is enabled." }, "mid_y": { "name": "mid_y", "tooltip": "Normalized Y control point for Bezier curve. Only used when 'bezier' is enabled." }, "interpolation": { "name": "interpolation", "tooltip": "Controls the timing/speed of movement along the path." }, "track_mask": { "name": "track_mask", "tooltip": "Optional mask to indicate visible frames." } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "track_length", "tooltip": null } } };
const GetImageSize = { "display_name": "Get Image Size", "description": "Returns width and height of the image, and passes it through unchanged.", "inputs": { "image": { "name": "image" } }, "outputs": { "0": { "name": "width", "tooltip": null }, "1": { "name": "height", "tooltip": null }, "2": { "name": "batch_size", "tooltip": null } } };
const GetVideoComponents = { "display_name": "Get Video Components", "description": "Extracts all components from a video: frames, audio, and framerate.", "inputs": { "video": { "name": "video", "tooltip": "The video to extract components from." } }, "outputs": { "0": { "name": "images", "tooltip": null }, "1": { "name": "audio", "tooltip": null }, "2": { "name": "fps", "tooltip": null } } };
const GITSScheduler = { "display_name": "GITSScheduler", "inputs": { "coeff": { "name": "coeff" }, "steps": { "name": "steps" }, "denoise": { "name": "denoise" } }, "outputs": { "0": { "tooltip": null } } };
const GLIGENLoader = { "display_name": "GLIGENLoader", "inputs": { "gligen_name": { "name": "gligen_name" } } };
const GLIGENTextBoxApply = { "display_name": "GLIGENTextBoxApply", "inputs": { "conditioning_to": { "name": "conditioning_to" }, "clip": { "name": "clip" }, "gligen_textbox_model": { "name": "gligen_textbox_model" }, "text": { "name": "text" }, "width": { "name": "width" }, "height": { "name": "height" }, "x": { "name": "x" }, "y": { "name": "y" } } };
const GrowMask = { "display_name": "Grow Mask", "inputs": { "mask": { "name": "mask" }, "expand": { "name": "expand" }, "tapered_corners": { "name": "tapered_corners" } }, "outputs": { "0": { "tooltip": null } } };
const Hunyuan3Dv2Conditioning = { "display_name": "Hunyuan3Dv2Conditioning", "inputs": { "clip_vision_output": { "name": "clip_vision_output" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null } } };
const Hunyuan3Dv2ConditioningMultiView = { "display_name": "Hunyuan3Dv2ConditioningMultiView", "inputs": { "front": { "name": "front" }, "left": { "name": "left" }, "back": { "name": "back" }, "right": { "name": "right" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null } } };
const HunyuanImageToVideo = { "display_name": "HunyuanImageToVideo", "inputs": { "positive": { "name": "positive" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "guidance_type": { "name": "guidance_type" }, "start_image": { "name": "start_image" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "latent", "tooltip": null } } };
const HunyuanRefinerLatent = { "display_name": "HunyuanRefinerLatent", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "latent": { "name": "latent" }, "noise_augmentation": { "name": "noise_augmentation" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const HunyuanVideo15ImageToVideo = { "display_name": "HunyuanVideo15ImageToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "start_image": { "name": "start_image" }, "clip_vision_output": { "name": "clip_vision_output" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const HunyuanVideo15LatentUpscaleWithModel = { "display_name": "Hunyuan Video 15 Latent Upscale With Model", "inputs": { "model": { "name": "model" }, "samples": { "name": "samples" }, "upscale_method": { "name": "upscale_method" }, "width": { "name": "width" }, "height": { "name": "height" }, "crop": { "name": "crop" } }, "outputs": { "0": { "tooltip": null } } };
const HunyuanVideo15SuperResolution = { "display_name": "HunyuanVideo15SuperResolution", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "latent": { "name": "latent" }, "noise_augmentation": { "name": "noise_augmentation" }, "vae": { "name": "vae" }, "start_image": { "name": "start_image" }, "clip_vision_output": { "name": "clip_vision_output" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const HypernetworkLoader = { "display_name": "HypernetworkLoader", "inputs": { "model": { "name": "model" }, "hypernetwork_name": { "name": "hypernetwork_name" }, "strength": { "name": "strength" } }, "outputs": { "0": { "tooltip": null } } };
const HyperTile = { "display_name": "HyperTile", "inputs": { "model": { "name": "model" }, "tile_size": { "name": "tile_size" }, "swap_size": { "name": "swap_size" }, "max_depth": { "name": "max_depth" }, "scale_depth": { "name": "scale_depth" } }, "outputs": { "0": { "tooltip": null } } };
const IdeogramV1 = { "display_name": "Ideogram V1", "description": "Generates images using the Ideogram V1 model.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation" }, "turbo": { "name": "turbo", "tooltip": "Whether to use turbo mode (faster generation, potentially lower quality)" }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "The aspect ratio for image generation." }, "magic_prompt_option": { "name": "magic_prompt_option", "tooltip": "Determine if MagicPrompt should be used in generation" }, "seed": { "name": "seed" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Description of what to exclude from the image" }, "num_images": { "name": "num_images" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const IdeogramV2 = { "display_name": "Ideogram V2", "description": "Generates images using the Ideogram V2 model.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation" }, "turbo": { "name": "turbo", "tooltip": "Whether to use turbo mode (faster generation, potentially lower quality)" }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "The aspect ratio for image generation. Ignored if resolution is not set to AUTO." }, "resolution": { "name": "resolution", "tooltip": "The resolution for image generation. If not set to AUTO, this overrides the aspect_ratio setting." }, "magic_prompt_option": { "name": "magic_prompt_option", "tooltip": "Determine if MagicPrompt should be used in generation" }, "seed": { "name": "seed" }, "style_type": { "name": "style_type", "tooltip": "Style type for generation (V2 only)" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Description of what to exclude from the image" }, "num_images": { "name": "num_images" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const IdeogramV3 = { "display_name": "Ideogram V3", "description": "Generates images using the Ideogram V3 model. Supports both regular image generation from text prompts and image editing with mask.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation or editing" }, "image": { "name": "image", "tooltip": "Optional reference image for image editing." }, "mask": { "name": "mask", "tooltip": "Optional mask for inpainting (white areas will be replaced)" }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "The aspect ratio for image generation. Ignored if resolution is not set to Auto." }, "resolution": { "name": "resolution", "tooltip": "The resolution for image generation. If not set to Auto, this overrides the aspect_ratio setting." }, "magic_prompt_option": { "name": "magic_prompt_option", "tooltip": "Determine if MagicPrompt should be used in generation" }, "seed": { "name": "seed" }, "num_images": { "name": "num_images" }, "rendering_speed": { "name": "rendering_speed", "tooltip": "Controls the trade-off between generation speed and quality" }, "character_image": { "name": "character_image", "tooltip": "Image to use as character reference." }, "character_mask": { "name": "character_mask", "tooltip": "Optional mask for character reference image." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ImageAddNoise = { "display_name": "ImageAddNoise", "inputs": { "image": { "name": "image" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "strength": { "name": "strength" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ImageBatch = { "display_name": "Batch Images", "inputs": { "image1": { "name": "image1" }, "image2": { "name": "image2" } } };
const ImageBlend = { "display_name": "ImageBlend", "inputs": { "image1": { "name": "image1" }, "image2": { "name": "image2" }, "blend_factor": { "name": "blend_factor" }, "blend_mode": { "name": "blend_mode" } }, "outputs": { "0": { "tooltip": null } } };
const ImageBlur = { "display_name": "ImageBlur", "inputs": { "image": { "name": "image" }, "blur_radius": { "name": "blur_radius" }, "sigma": { "name": "sigma" } }, "outputs": { "0": { "tooltip": null } } };
const ImageColorToMask = { "display_name": "ImageColorToMask", "inputs": { "image": { "name": "image" }, "color": { "name": "color" } }, "outputs": { "0": { "tooltip": null } } };
const ImageCompositeMasked = { "display_name": "ImageCompositeMasked", "inputs": { "destination": { "name": "destination" }, "source": { "name": "source" }, "x": { "name": "x" }, "y": { "name": "y" }, "resize_source": { "name": "resize_source" }, "mask": { "name": "mask" } }, "outputs": { "0": { "tooltip": null } } };
const ImageCrop = { "display_name": "Image Crop", "inputs": { "image": { "name": "image" }, "width": { "name": "width" }, "height": { "name": "height" }, "x": { "name": "x" }, "y": { "name": "y" } }, "outputs": { "0": { "tooltip": null } } };
const ImageDeduplication = { "display_name": "Image Deduplication", "inputs": { "images": { "name": "images", "tooltip": "List of images to process." }, "similarity_threshold": { "name": "similarity_threshold", "tooltip": "Similarity threshold (0-1). Higher means more similar. Images above this threshold are considered duplicates." } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const ImageFlip = { "display_name": "ImageFlip", "inputs": { "image": { "name": "image" }, "flip_method": { "name": "flip_method" } }, "outputs": { "0": { "tooltip": null } } };
const ImageFromBatch = { "display_name": "ImageFromBatch", "inputs": { "image": { "name": "image" }, "batch_index": { "name": "batch_index" }, "length": { "name": "length" } }, "outputs": { "0": { "tooltip": null } } };
const ImageGrid = { "display_name": "Image Grid", "inputs": { "images": { "name": "images", "tooltip": "List of images to process." }, "columns": { "name": "columns", "tooltip": "Number of columns in the grid." }, "cell_width": { "name": "cell_width", "tooltip": "Width of each cell in the grid." }, "cell_height": { "name": "cell_height", "tooltip": "Height of each cell in the grid." }, "padding": { "name": "padding", "tooltip": "Padding between images." } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const ImageInvert = { "display_name": "Invert Image", "inputs": { "image": { "name": "image" } } };
const ImageOnlyCheckpointLoader = { "display_name": "Image Only Checkpoint Loader (img2vid model)", "inputs": { "ckpt_name": { "name": "ckpt_name" } } };
const ImageOnlyCheckpointSave = { "display_name": "ImageOnlyCheckpointSave", "inputs": { "model": { "name": "model" }, "clip_vision": { "name": "clip_vision" }, "vae": { "name": "vae" }, "filename_prefix": { "name": "filename_prefix" } } };
const ImagePadForOutpaint = { "display_name": "Pad Image for Outpainting", "inputs": { "image": { "name": "image" }, "left": { "name": "left" }, "top": { "name": "top" }, "right": { "name": "right" }, "bottom": { "name": "bottom" }, "feathering": { "name": "feathering" } } };
const ImageQuantize = { "display_name": "ImageQuantize", "inputs": { "image": { "name": "image" }, "colors": { "name": "colors" }, "dither": { "name": "dither" } }, "outputs": { "0": { "tooltip": null } } };
const ImageRGBToYUV = { "display_name": "ImageRGBToYUV", "inputs": { "image": { "name": "image" } }, "outputs": { "0": { "name": "Y", "tooltip": null }, "1": { "name": "U", "tooltip": null }, "2": { "name": "V", "tooltip": null } } };
const ImageRotate = { "display_name": "ImageRotate", "inputs": { "image": { "name": "image" }, "rotation": { "name": "rotation" } }, "outputs": { "0": { "tooltip": null } } };
const ImageScale = { "display_name": "Upscale Image", "inputs": { "image": { "name": "image" }, "upscale_method": { "name": "upscale_method" }, "width": { "name": "width" }, "height": { "name": "height" }, "crop": { "name": "crop" } } };
const ImageScaleBy = { "display_name": "Upscale Image By", "inputs": { "image": { "name": "image" }, "upscale_method": { "name": "upscale_method" }, "scale_by": { "name": "scale_by" } } };
const ImageScaleToMaxDimension = { "display_name": "ImageScaleToMaxDimension", "inputs": { "image": { "name": "image" }, "upscale_method": { "name": "upscale_method" }, "largest_size": { "name": "largest_size" } }, "outputs": { "0": { "tooltip": null } } };
const ImageScaleToTotalPixels = { "display_name": "ImageScaleToTotalPixels", "inputs": { "image": { "name": "image" }, "upscale_method": { "name": "upscale_method" }, "megapixels": { "name": "megapixels" }, "resolution_steps": { "name": "resolution_steps" } }, "outputs": { "0": { "tooltip": null } } };
const ImageSharpen = { "display_name": "ImageSharpen", "inputs": { "image": { "name": "image" }, "sharpen_radius": { "name": "sharpen_radius" }, "sigma": { "name": "sigma" }, "alpha": { "name": "alpha" } }, "outputs": { "0": { "tooltip": null } } };
const ImageStitch = { "display_name": "Image Stitch", "description": "Stitches image2 to image1 in the specified direction.\nIf image2 is not provided, returns image1 unchanged.\nOptional spacing can be added between images.", "inputs": { "image1": { "name": "image1" }, "direction": { "name": "direction" }, "match_image_size": { "name": "match_image_size" }, "spacing_width": { "name": "spacing_width" }, "spacing_color": { "name": "spacing_color" }, "image2": { "name": "image2" } }, "outputs": { "0": { "tooltip": null } } };
const ImageToMask = { "display_name": "Convert Image to Mask", "inputs": { "image": { "name": "image" }, "channel": { "name": "channel" } }, "outputs": { "0": { "tooltip": null } } };
const ImageUpscaleWithModel = { "display_name": "Upscale Image (using Model)", "inputs": { "upscale_model": { "name": "upscale_model" }, "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null } } };
const ImageYUVToRGB = { "display_name": "ImageYUVToRGB", "inputs": { "Y": { "name": "Y" }, "U": { "name": "U" }, "V": { "name": "V" } }, "outputs": { "0": { "tooltip": null } } };
const InpaintModelConditioning = { "display_name": "InpaintModelConditioning", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "pixels": { "name": "pixels" }, "mask": { "name": "mask" }, "noise_mask": { "name": "noise_mask", "tooltip": "Add a noise mask to the latent so sampling will only happen within the mask. Might improve results or completely break things depending on the model." } }, "outputs": { "0": { "name": "positive" }, "1": { "name": "negative" }, "2": { "name": "latent" } } };
const InstructPixToPixConditioning = { "display_name": "InstructPixToPixConditioning", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "pixels": { "name": "pixels" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const InvertMask = { "display_name": "InvertMask", "inputs": { "mask": { "name": "mask" } }, "outputs": { "0": { "tooltip": null } } };
const JoinImageWithAlpha = { "display_name": "Join Image with Alpha", "inputs": { "image": { "name": "image" }, "alpha": { "name": "alpha" } }, "outputs": { "0": { "tooltip": null } } };
const Kandinsky5ImageToVideo = { "display_name": "Kandinsky5ImageToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "start_image": { "name": "start_image" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": "Empty video latent" }, "3": { "name": "cond_latent", "tooltip": "Clean encoded start images, used to replace the noisy start of the model output latents" } } };
const KarrasScheduler = { "display_name": "KarrasScheduler", "inputs": { "steps": { "name": "steps" }, "sigma_max": { "name": "sigma_max" }, "sigma_min": { "name": "sigma_min" }, "rho": { "name": "rho" } }, "outputs": { "0": { "tooltip": null } } };
const KlingCameraControlI2VNode = { "display_name": "Kling Image to Video (Camera Control)", "description": "Transform still images into cinematic videos with professional camera movements that simulate real-world cinematography. Control virtual camera actions including zoom, rotation, pan, tilt, and first-person view, while maintaining focus on your original image.", "inputs": { "start_frame": { "name": "start_frame", "tooltip": "Reference Image - URL or Base64 encoded string, cannot exceed 10MB, resolution not less than 300*300px, aspect ratio between 1:2.5 ~ 2.5:1. Base64 should not include data:image prefix." }, "prompt": { "name": "prompt", "tooltip": "Positive text prompt" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt" }, "cfg_scale": { "name": "cfg_scale" }, "aspect_ratio": { "name": "aspect_ratio" }, "camera_control": { "name": "camera_control", "tooltip": "Can be created using the Kling Camera Controls node. Controls the camera movement and motion during the video generation." } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "video_id", "tooltip": null }, "2": { "name": "duration", "tooltip": null } } };
const KlingCameraControls = { "display_name": "Kling Camera Controls", "description": "Allows specifying configuration options for Kling Camera Controls and motion control effects.", "inputs": { "camera_control_type": { "name": "camera_control_type" }, "horizontal_movement": { "name": "horizontal_movement", "tooltip": "Controls camera's movement along horizontal axis (x-axis). Negative indicates left, positive indicates right" }, "vertical_movement": { "name": "vertical_movement", "tooltip": "Controls camera's movement along vertical axis (y-axis). Negative indicates downward, positive indicates upward." }, "pan": { "name": "pan", "tooltip": "Controls camera's rotation in vertical plane (x-axis). Negative indicates downward rotation, positive indicates upward rotation." }, "tilt": { "name": "tilt", "tooltip": "Controls camera's rotation in horizontal plane (y-axis). Negative indicates left rotation, positive indicates right rotation." }, "roll": { "name": "roll", "tooltip": "Controls camera's rolling amount (z-axis). Negative indicates counterclockwise, positive indicates clockwise." }, "zoom": { "name": "zoom", "tooltip": "Controls change in camera's focal length. Negative indicates narrower field of view, positive indicates wider field of view." } }, "outputs": { "0": { "name": "camera_control", "tooltip": null } } };
const KlingCameraControlT2VNode = { "display_name": "Kling Text to Video (Camera Control)", "description": "Transform text into cinematic videos with professional camera movements that simulate real-world cinematography. Control virtual camera actions including zoom, rotation, pan, tilt, and first-person view, while maintaining focus on your original text.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Positive text prompt" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt" }, "cfg_scale": { "name": "cfg_scale" }, "aspect_ratio": { "name": "aspect_ratio" }, "camera_control": { "name": "camera_control", "tooltip": "Can be created using the Kling Camera Controls node. Controls the camera movement and motion during the video generation." } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "video_id", "tooltip": null }, "2": { "name": "duration", "tooltip": null } } };
const KlingDualCharacterVideoEffectNode = { "display_name": "Kling Dual Character Video Effects", "description": "Achieve different special effects when generating a video based on the effect_scene. First image will be positioned on left side, second on right side of the composite.", "inputs": { "image_left": { "name": "image_left", "tooltip": "Left side image" }, "image_right": { "name": "image_right", "tooltip": "Right side image" }, "effect_scene": { "name": "effect_scene" }, "model_name": { "name": "model_name" }, "mode": { "name": "mode" }, "duration": { "name": "duration" } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "duration", "tooltip": null } } };
const KlingImage2VideoNode = { "display_name": "Kling Image(First Frame) to Video", "inputs": { "start_frame": { "name": "start_frame", "tooltip": "The reference image used to generate the video." }, "prompt": { "name": "prompt", "tooltip": "Positive text prompt" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt" }, "model_name": { "name": "model_name" }, "cfg_scale": { "name": "cfg_scale" }, "mode": { "name": "mode" }, "aspect_ratio": { "name": "aspect_ratio" }, "duration": { "name": "duration" } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "video_id", "tooltip": null }, "2": { "name": "duration", "tooltip": null } } };
const KlingImageGenerationNode = { "display_name": "Kling Image Generation", "description": "Kling Image Generation Node. Generate an image from a text prompt with an optional reference image.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Positive text prompt" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt" }, "image_type": { "name": "image_type" }, "image_fidelity": { "name": "image_fidelity", "tooltip": "Reference intensity for user-uploaded images" }, "human_fidelity": { "name": "human_fidelity", "tooltip": "Subject reference similarity" }, "model_name": { "name": "model_name" }, "aspect_ratio": { "name": "aspect_ratio" }, "n": { "name": "n", "tooltip": "Number of generated images" }, "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null } } };
const KlingImageToVideoWithAudio = { "display_name": "Kling Image(First Frame) to Video with Audio", "inputs": { "model_name": { "name": "model_name" }, "start_frame": { "name": "start_frame" }, "prompt": { "name": "prompt", "tooltip": "Positive text prompt." }, "mode": { "name": "mode" }, "duration": { "name": "duration" }, "generate_audio": { "name": "generate_audio" } }, "outputs": { "0": { "tooltip": null } } };
const KlingLipSyncAudioToVideoNode = { "display_name": "Kling Lip Sync Video with Audio", "description": "Kling Lip Sync Audio to Video Node. Syncs mouth movements in a video file to the audio content of an audio file. When using, ensure that the audio contains clearly distinguishable vocals and that the video contains a distinct face. The audio file should not be larger than 5MB. The video file should not be larger than 100MB, should have height/width between 720px and 1920px, and should be between 2s and 10s in length.", "inputs": { "video": { "name": "video" }, "audio": { "name": "audio" }, "voice_language": { "name": "voice_language" } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "video_id", "tooltip": null }, "2": { "name": "duration", "tooltip": null } } };
const KlingLipSyncTextToVideoNode = { "display_name": "Kling Lip Sync Video with Text", "description": "Kling Lip Sync Text to Video Node. Syncs mouth movements in a video file to a text prompt. The video file should not be larger than 100MB, should have height/width between 720px and 1920px, and should be between 2s and 10s in length.", "inputs": { "video": { "name": "video" }, "text": { "name": "text", "tooltip": "Text Content for Lip-Sync Video Generation. Required when mode is text2video. Maximum length is 120 characters." }, "voice": { "name": "voice" }, "voice_speed": { "name": "voice_speed", "tooltip": "Speech Rate. Valid range: 0.8~2.0, accurate to one decimal place." } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "video_id", "tooltip": null }, "2": { "name": "duration", "tooltip": null } } };
const KlingMotionControl = { "display_name": "Kling Motion Control", "inputs": { "prompt": { "name": "prompt" }, "reference_image": { "name": "reference_image" }, "reference_video": { "name": "reference_video", "tooltip": "Motion reference video used to drive movement/expression.\nDuration limits depend on character_orientation:\n - image: 3–10s (max 10s)\n - video: 3–30s (max 30s)" }, "keep_original_sound": { "name": "keep_original_sound" }, "character_orientation": { "name": "character_orientation", "tooltip": "Controls where the character's facing/orientation comes from.\nvideo: movements, expressions, camera moves, and orientation follow the motion reference video (other details via prompt).\nimage: movements and expressions still follow the motion reference video, but the character orientation matches the reference image (camera/other details via prompt)." }, "mode": { "name": "mode" } }, "outputs": { "0": { "tooltip": null } } };
const KlingOmniProEditVideoNode = { "display_name": "Kling Omni Edit Video (Pro)", "description": "Edit an existing video with the latest model from Kling.", "inputs": { "model_name": { "name": "model_name" }, "prompt": { "name": "prompt", "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." }, "video": { "name": "video", "tooltip": "Video for editing. The output video length will be the same." }, "keep_original_sound": { "name": "keep_original_sound" }, "reference_images": { "name": "reference_images", "tooltip": "Up to 4 additional reference images." } }, "outputs": { "0": { "tooltip": null } } };
const KlingOmniProFirstLastFrameNode = { "display_name": "Kling Omni First-Last-Frame to Video (Pro)", "description": "Use a start frame, an optional end frame, or reference images with the latest Kling model.", "inputs": { "model_name": { "name": "model_name" }, "prompt": { "name": "prompt", "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." }, "duration": { "name": "duration" }, "first_frame": { "name": "first_frame" }, "end_frame": { "name": "end_frame", "tooltip": "An optional end frame for the video. This cannot be used simultaneously with 'reference_images'." }, "reference_images": { "name": "reference_images", "tooltip": "Up to 6 additional reference images." } }, "outputs": { "0": { "tooltip": null } } };
const KlingOmniProImageNode = { "display_name": "Kling Omni Image (Pro)", "description": "Create or edit images with the latest model from Kling.", "inputs": { "model_name": { "name": "model_name" }, "prompt": { "name": "prompt", "tooltip": "A text prompt describing the image content. This can include both positive and negative descriptions." }, "resolution": { "name": "resolution" }, "aspect_ratio": { "name": "aspect_ratio" }, "reference_images": { "name": "reference_images", "tooltip": "Up to 10 additional reference images." } }, "outputs": { "0": { "tooltip": null } } };
const KlingOmniProImageToVideoNode = { "display_name": "Kling Omni Image to Video (Pro)", "description": "Use up to 7 reference images to generate a video with the latest Kling model.", "inputs": { "model_name": { "name": "model_name" }, "prompt": { "name": "prompt", "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." }, "aspect_ratio": { "name": "aspect_ratio" }, "duration": { "name": "duration" }, "reference_images": { "name": "reference_images", "tooltip": "Up to 7 reference images." } }, "outputs": { "0": { "tooltip": null } } };
const KlingOmniProTextToVideoNode = { "display_name": "Kling Omni Text to Video (Pro)", "description": "Use text prompts to generate videos with the latest Kling model.", "inputs": { "model_name": { "name": "model_name" }, "prompt": { "name": "prompt", "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." }, "aspect_ratio": { "name": "aspect_ratio" }, "duration": { "name": "duration" } }, "outputs": { "0": { "tooltip": null } } };
const KlingOmniProVideoToVideoNode = { "display_name": "Kling Omni Video to Video (Pro)", "description": "Use a video and up to 4 reference images to generate a video with the latest Kling model.", "inputs": { "model_name": { "name": "model_name" }, "prompt": { "name": "prompt", "tooltip": "A text prompt describing the video content. This can include both positive and negative descriptions." }, "aspect_ratio": { "name": "aspect_ratio" }, "duration": { "name": "duration" }, "reference_video": { "name": "reference_video", "tooltip": "Video to use as a reference." }, "keep_original_sound": { "name": "keep_original_sound" }, "reference_images": { "name": "reference_images", "tooltip": "Up to 4 additional reference images." } }, "outputs": { "0": { "tooltip": null } } };
const KlingSingleImageVideoEffectNode = { "display_name": "Kling Video Effects", "description": "Achieve different special effects when generating a video based on the effect_scene.", "inputs": { "image": { "name": "image", "tooltip": " Reference Image. URL or Base64 encoded string (without data:image prefix). File size cannot exceed 10MB, resolution not less than 300*300px, aspect ratio between 1:2.5 ~ 2.5:1" }, "effect_scene": { "name": "effect_scene" }, "model_name": { "name": "model_name" }, "duration": { "name": "duration" } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "video_id", "tooltip": null }, "2": { "name": "duration", "tooltip": null } } };
const KlingStartEndFrameNode = { "display_name": "Kling Start-End Frame to Video", "description": "Generate a video sequence that transitions between your provided start and end images. The node creates all frames in between, producing a smooth transformation from the first frame to the last.", "inputs": { "start_frame": { "name": "start_frame", "tooltip": "Reference Image - URL or Base64 encoded string, cannot exceed 10MB, resolution not less than 300*300px, aspect ratio between 1:2.5 ~ 2.5:1. Base64 should not include data:image prefix." }, "end_frame": { "name": "end_frame", "tooltip": "Reference Image - End frame control. URL or Base64 encoded string, cannot exceed 10MB, resolution not less than 300*300px. Base64 should not include data:image prefix." }, "prompt": { "name": "prompt", "tooltip": "Positive text prompt" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt" }, "cfg_scale": { "name": "cfg_scale" }, "aspect_ratio": { "name": "aspect_ratio" }, "mode": { "name": "mode", "tooltip": "The configuration to use for the video generation following the format: mode / duration / model_name." } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "video_id", "tooltip": null }, "2": { "name": "duration", "tooltip": null } } };
const KlingTextToVideoNode = { "display_name": "Kling Text to Video", "description": "Kling Text to Video Node", "inputs": { "prompt": { "name": "prompt", "tooltip": "Positive text prompt" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt" }, "cfg_scale": { "name": "cfg_scale" }, "aspect_ratio": { "name": "aspect_ratio" }, "mode": { "name": "mode", "tooltip": "The configuration to use for the video generation following the format: mode / duration / model_name." } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "video_id", "tooltip": null }, "2": { "name": "duration", "tooltip": null } } };
const KlingTextToVideoWithAudio = { "display_name": "Kling Text to Video with Audio", "inputs": { "model_name": { "name": "model_name" }, "prompt": { "name": "prompt", "tooltip": "Positive text prompt." }, "mode": { "name": "mode" }, "aspect_ratio": { "name": "aspect_ratio" }, "duration": { "name": "duration" }, "generate_audio": { "name": "generate_audio" } }, "outputs": { "0": { "tooltip": null } } };
const KlingVideoExtendNode = { "display_name": "Kling Video Extend", "description": "Kling Video Extend Node. Extend videos made by other Kling nodes. The video_id is created by using other Kling Nodes.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Positive text prompt for guiding the video extension" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt for elements to avoid in the extended video" }, "cfg_scale": { "name": "cfg_scale" }, "video_id": { "name": "video_id", "tooltip": "The ID of the video to be extended. Supports videos generated by text-to-video, image-to-video, and previous video extension operations. Cannot exceed 3 minutes total duration after extension." } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "video_id", "tooltip": null }, "2": { "name": "duration", "tooltip": null } } };
const KlingVirtualTryOnNode = { "display_name": "Kling Virtual Try On", "description": "Kling Virtual Try On Node. Input a human image and a cloth image to try on the cloth on the human. You can merge multiple clothing item pictures into one image with a white background.", "inputs": { "human_image": { "name": "human_image" }, "cloth_image": { "name": "cloth_image" }, "model_name": { "name": "model_name" } }, "outputs": { "0": { "tooltip": null } } };
const KSampler = { "display_name": "KSampler", "description": "Uses the provided model, positive and negative conditioning to denoise the latent image.", "inputs": { "model": { "name": "model", "tooltip": "The model used for denoising the input latent." }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "steps": { "name": "steps", "tooltip": "The number of steps used in the denoising process." }, "cfg": { "name": "cfg", "tooltip": "The Classifier-Free Guidance scale balances creativity and adherence to the prompt. Higher values result in images more closely matching the prompt however too high values will negatively impact quality." }, "sampler_name": { "name": "sampler_name", "tooltip": "The algorithm used when sampling, this can affect the quality, speed, and style of the generated output." }, "scheduler": { "name": "scheduler", "tooltip": "The scheduler controls how noise is gradually removed to form the image." }, "positive": { "name": "positive", "tooltip": "The conditioning describing the attributes you want to include in the image." }, "negative": { "name": "negative", "tooltip": "The conditioning describing the attributes you want to exclude from the image." }, "latent_image": { "name": "latent_image", "tooltip": "The latent image to denoise." }, "denoise": { "name": "denoise", "tooltip": "The amount of denoising applied, lower values will maintain the structure of the initial image allowing for image to image sampling." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": "The denoised latent." } } };
const KSamplerAdvanced = { "display_name": "KSampler (Advanced)", "inputs": { "model": { "name": "model" }, "add_noise": { "name": "add_noise" }, "noise_seed": { "name": "noise_seed" }, "steps": { "name": "steps" }, "cfg": { "name": "cfg" }, "sampler_name": { "name": "sampler_name" }, "scheduler": { "name": "scheduler" }, "positive": { "name": "positive" }, "negative": { "name": "negative" }, "latent_image": { "name": "latent_image" }, "start_at_step": { "name": "start_at_step" }, "end_at_step": { "name": "end_at_step" }, "return_with_leftover_noise": { "name": "return_with_leftover_noise" }, "control_after_generate": { "name": "control after generate" } } };
const KSamplerSelect = { "display_name": "KSamplerSelect", "inputs": { "sampler_name": { "name": "sampler_name" } }, "outputs": { "0": { "tooltip": null } } };
const LaplaceScheduler = { "display_name": "LaplaceScheduler", "inputs": { "steps": { "name": "steps" }, "sigma_max": { "name": "sigma_max" }, "sigma_min": { "name": "sigma_min" }, "mu": { "name": "mu" }, "beta": { "name": "beta" } }, "outputs": { "0": { "tooltip": null } } };
const LatentAdd = { "display_name": "LatentAdd", "inputs": { "samples1": { "name": "samples1" }, "samples2": { "name": "samples2" } }, "outputs": { "0": { "tooltip": null } } };
const LatentApplyOperation = { "display_name": "LatentApplyOperation", "inputs": { "samples": { "name": "samples" }, "operation": { "name": "operation" } }, "outputs": { "0": { "tooltip": null } } };
const LatentApplyOperationCFG = { "display_name": "LatentApplyOperationCFG", "inputs": { "model": { "name": "model" }, "operation": { "name": "operation" } }, "outputs": { "0": { "tooltip": null } } };
const LatentBatch = { "display_name": "LatentBatch", "inputs": { "samples1": { "name": "samples1" }, "samples2": { "name": "samples2" } }, "outputs": { "0": { "tooltip": null } } };
const LatentBatchSeedBehavior = { "display_name": "LatentBatchSeedBehavior", "inputs": { "samples": { "name": "samples" }, "seed_behavior": { "name": "seed_behavior" } }, "outputs": { "0": { "tooltip": null } } };
const LatentBlend = { "display_name": "Latent Blend", "inputs": { "samples1": { "name": "samples1" }, "samples2": { "name": "samples2" }, "blend_factor": { "name": "blend_factor" } } };
const LatentComposite = { "display_name": "Latent Composite", "inputs": { "samples_to": { "name": "samples_to" }, "samples_from": { "name": "samples_from" }, "x": { "name": "x" }, "y": { "name": "y" }, "feather": { "name": "feather" } } };
const LatentCompositeMasked = { "display_name": "LatentCompositeMasked", "inputs": { "destination": { "name": "destination" }, "source": { "name": "source" }, "x": { "name": "x" }, "y": { "name": "y" }, "resize_source": { "name": "resize_source" }, "mask": { "name": "mask" } }, "outputs": { "0": { "tooltip": null } } };
const LatentConcat = { "display_name": "LatentConcat", "inputs": { "samples1": { "name": "samples1" }, "samples2": { "name": "samples2" }, "dim": { "name": "dim" } }, "outputs": { "0": { "tooltip": null } } };
const LatentCrop = { "display_name": "Crop Latent", "inputs": { "samples": { "name": "samples" }, "width": { "name": "width" }, "height": { "name": "height" }, "x": { "name": "x" }, "y": { "name": "y" } } };
const LatentCut = { "display_name": "LatentCut", "inputs": { "samples": { "name": "samples" }, "dim": { "name": "dim" }, "index": { "name": "index" }, "amount": { "name": "amount" } }, "outputs": { "0": { "tooltip": null } } };
const LatentCutToBatch = { "display_name": "LatentCutToBatch", "inputs": { "samples": { "name": "samples" }, "dim": { "name": "dim" }, "slice_size": { "name": "slice_size" } }, "outputs": { "0": { "tooltip": null } } };
const LatentFlip = { "display_name": "Flip Latent", "inputs": { "samples": { "name": "samples" }, "flip_method": { "name": "flip_method" } } };
const LatentFromBatch = { "display_name": "Latent From Batch", "inputs": { "samples": { "name": "samples" }, "batch_index": { "name": "batch_index" }, "length": { "name": "length" } } };
const LatentInterpolate = { "display_name": "LatentInterpolate", "inputs": { "samples1": { "name": "samples1" }, "samples2": { "name": "samples2" }, "ratio": { "name": "ratio" } }, "outputs": { "0": { "tooltip": null } } };
const LatentMultiply = { "display_name": "LatentMultiply", "inputs": { "samples": { "name": "samples" }, "multiplier": { "name": "multiplier" } }, "outputs": { "0": { "tooltip": null } } };
const LatentOperationSharpen = { "display_name": "LatentOperationSharpen", "inputs": { "sharpen_radius": { "name": "sharpen_radius" }, "sigma": { "name": "sigma" }, "alpha": { "name": "alpha" } }, "outputs": { "0": { "tooltip": null } } };
const LatentOperationTonemapReinhard = { "display_name": "LatentOperationTonemapReinhard", "inputs": { "multiplier": { "name": "multiplier" } }, "outputs": { "0": { "tooltip": null } } };
const LatentRotate = { "display_name": "Rotate Latent", "inputs": { "samples": { "name": "samples" }, "rotation": { "name": "rotation" } } };
const LatentSubtract = { "display_name": "LatentSubtract", "inputs": { "samples1": { "name": "samples1" }, "samples2": { "name": "samples2" } }, "outputs": { "0": { "tooltip": null } } };
const LatentUpscale = { "display_name": "Upscale Latent", "inputs": { "samples": { "name": "samples" }, "upscale_method": { "name": "upscale_method" }, "width": { "name": "width" }, "height": { "name": "height" }, "crop": { "name": "crop" } } };
const LatentUpscaleBy = { "display_name": "Upscale Latent By", "inputs": { "samples": { "name": "samples" }, "upscale_method": { "name": "upscale_method" }, "scale_by": { "name": "scale_by" } } };
const LatentUpscaleModelLoader = { "display_name": "Load Latent Upscale Model", "inputs": { "model_name": { "name": "model_name" } }, "outputs": { "0": { "tooltip": null } } };
const LazyCache = { "display_name": "LazyCache", "description": "A homebrew version of EasyCache - even 'easier' version of EasyCache to implement. Overall works worse than EasyCache, but better in some rare cases AND universal compatibility with everything in ComfyUI.", "inputs": { "model": { "name": "model", "tooltip": "The model to add LazyCache to." }, "reuse_threshold": { "name": "reuse_threshold", "tooltip": "The threshold for reusing cached steps." }, "start_percent": { "name": "start_percent", "tooltip": "The relative sampling step to begin use of LazyCache." }, "end_percent": { "name": "end_percent", "tooltip": "The relative sampling step to end use of LazyCache." }, "verbose": { "name": "verbose", "tooltip": "Whether to log verbose information." } }, "outputs": { "0": { "tooltip": "The model with LazyCache." } } };
const Load3D$1 = { "display_name": "Load 3D & Animation", "inputs": { "model_file": { "name": "model_file" }, "image": { "name": "image" }, "width": { "name": "width" }, "height": { "name": "height" }, "clear": {}, "upload 3d model": {}, "upload extra resources": {} }, "outputs": { "0": { "name": "image", "tooltip": null }, "1": { "name": "mask", "tooltip": null }, "2": { "name": "mesh_path", "tooltip": null }, "3": { "name": "normal", "tooltip": null }, "4": { "name": "camera_info", "tooltip": null }, "5": { "name": "recording_video", "tooltip": null } } };
const LoadAudio = { "display_name": "Load Audio", "inputs": { "audio": { "name": "audio" }, "audioUI": { "name": "audioUI" }, "upload": { "name": "choose file to upload" } }, "outputs": { "0": { "tooltip": null } } };
const LoadImage = { "display_name": "Load Image", "inputs": { "image": { "name": "image" }, "upload": { "name": "choose file to upload" } } };
const LoadImageDataSetFromFolder = { "display_name": "Load Image Dataset from Folder", "inputs": { "folder": { "name": "folder", "tooltip": "The folder to load images from." } }, "outputs": { "0": { "name": "images", "tooltip": "List of loaded images" } } };
const LoadImageMask = { "display_name": "Load Image (as Mask)", "inputs": { "image": { "name": "image" }, "channel": { "name": "channel" }, "upload": { "name": "choose file to upload" } } };
const LoadImageOutput = { "display_name": "Load Image (from Outputs)", "description": "Load an image from the output folder. When the refresh button is clicked, the node will update the image list and automatically select the first image, allowing for easy iteration.", "inputs": { "image": { "name": "image" }, "Auto-refresh after generation": {}, "refresh": {}, "upload": { "name": "choose file to upload" } } };
const LoadImageTextDataSetFromFolder = { "display_name": "Load Image and Text Dataset from Folder", "inputs": { "folder": { "name": "folder", "tooltip": "The folder to load images from." } }, "outputs": { "0": { "name": "images", "tooltip": "List of loaded images" }, "1": { "name": "texts", "tooltip": "List of text captions" } } };
const LoadLatent = { "display_name": "LoadLatent", "inputs": { "latent": { "name": "latent" } } };
const LoadTrainingDataset = { "display_name": "Load Training Dataset", "inputs": { "folder_name": { "name": "folder_name", "tooltip": "Name of folder containing the saved dataset (inside output directory)." } }, "outputs": { "0": { "name": "latents", "tooltip": "List of latent dicts" }, "1": { "name": "conditioning", "tooltip": "List of conditioning lists" } } };
const LoadVideo = { "display_name": "Load Video", "inputs": { "file": { "name": "file" }, "upload": { "name": "choose file to upload" } }, "outputs": { "0": { "tooltip": null } } };
const LoraLoader = { "display_name": "Load LoRA", "description": "LoRAs are used to modify diffusion and CLIP models, altering the way in which latents are denoised such as applying styles. Multiple LoRA nodes can be linked together.", "inputs": { "model": { "name": "model", "tooltip": "The diffusion model the LoRA will be applied to." }, "clip": { "name": "clip", "tooltip": "The CLIP model the LoRA will be applied to." }, "lora_name": { "name": "lora_name", "tooltip": "The name of the LoRA." }, "strength_model": { "name": "strength_model", "tooltip": "How strongly to modify the diffusion model. This value can be negative." }, "strength_clip": { "name": "strength_clip", "tooltip": "How strongly to modify the CLIP model. This value can be negative." } }, "outputs": { "0": { "tooltip": "The modified diffusion model." }, "1": { "tooltip": "The modified CLIP model." } } };
const LoraLoaderModelOnly = { "display_name": "LoraLoaderModelOnly", "description": "LoRAs are used to modify diffusion and CLIP models, altering the way in which latents are denoised such as applying styles. Multiple LoRA nodes can be linked together.", "inputs": { "model": { "name": "model" }, "lora_name": { "name": "lora_name" }, "strength_model": { "name": "strength_model" } }, "outputs": { "0": { "tooltip": "The modified diffusion model." } } };
const LoraModelLoader = { "display_name": "Load LoRA Model", "inputs": { "model": { "name": "model", "tooltip": "The diffusion model the LoRA will be applied to." }, "lora": { "name": "lora", "tooltip": "The LoRA model to apply to the diffusion model." }, "strength_model": { "name": "strength_model", "tooltip": "How strongly to modify the diffusion model. This value can be negative." } }, "outputs": { "0": { "name": "model", "tooltip": "The modified diffusion model." } } };
const LoraSave = { "display_name": "Extract and Save Lora", "inputs": { "filename_prefix": { "name": "filename_prefix" }, "rank": { "name": "rank" }, "lora_type": { "name": "lora_type" }, "bias_diff": { "name": "bias_diff" }, "model_diff": { "name": "model_diff", "tooltip": "The ModelSubtract output to be converted to a lora." }, "text_encoder_diff": { "name": "text_encoder_diff", "tooltip": "The CLIPSubtract output to be converted to a lora." } } };
const LossGraphNode = { "display_name": "Plot Loss Graph", "inputs": { "loss": { "name": "loss", "tooltip": "Loss map from training node." }, "filename_prefix": { "name": "filename_prefix", "tooltip": "Prefix for the saved loss graph image." } } };
const LotusConditioning = { "display_name": "LotusConditioning", "outputs": { "0": { "name": "conditioning", "tooltip": null } } };
const LTXVAddGuide = { "display_name": "LTXVAddGuide", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "latent": { "name": "latent" }, "image": { "name": "image", "tooltip": "Image or video to condition the latent video on. Must be 8*n + 1 frames. If the video is not 8*n + 1 frames, it will be cropped to the nearest 8*n + 1 frames." }, "frame_idx": { "name": "frame_idx", "tooltip": "Frame index to start the conditioning at. For single-frame images or videos with 1-8 frames, any frame_idx value is acceptable. For videos with 9+ frames, frame_idx must be divisible by 8, otherwise it will be rounded down to the nearest multiple of 8. Negative values are counted from the end of the video." }, "strength": { "name": "strength" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const LtxvApiImageToVideo = { "display_name": "LTXV Image To Video", "description": "Professional-quality videos with customizable duration and resolution based on start image.", "inputs": { "image": { "name": "image", "tooltip": "First frame to be used for the video." }, "model": { "name": "model" }, "prompt": { "name": "prompt" }, "duration": { "name": "duration" }, "resolution": { "name": "resolution" }, "fps": { "name": "fps" }, "generate_audio": { "name": "generate_audio", "tooltip": "When true, the generated video will include AI-generated audio matching the scene." } }, "outputs": { "0": { "tooltip": null } } };
const LtxvApiTextToVideo = { "display_name": "LTXV Text To Video", "description": "Professional-quality videos with customizable duration and resolution.", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt" }, "duration": { "name": "duration" }, "resolution": { "name": "resolution" }, "fps": { "name": "fps" }, "generate_audio": { "name": "generate_audio", "tooltip": "When true, the generated video will include AI-generated audio matching the scene." } }, "outputs": { "0": { "tooltip": null } } };
const LTXVConditioning = { "display_name": "LTXVConditioning", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "frame_rate": { "name": "frame_rate" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null } } };
const LTXVCropGuides = { "display_name": "LTXVCropGuides", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "latent": { "name": "latent" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const LTXVImgToVideo = { "display_name": "LTXVImgToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "image": { "name": "image" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "strength": { "name": "strength" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const LTXVPreprocess = { "display_name": "LTXVPreprocess", "inputs": { "image": { "name": "image" }, "img_compression": { "name": "img_compression", "tooltip": "Amount of compression to apply on image." } }, "outputs": { "0": { "name": "output_image", "tooltip": null } } };
const LTXVScheduler = { "display_name": "LTXVScheduler", "inputs": { "steps": { "name": "steps" }, "max_shift": { "name": "max_shift" }, "base_shift": { "name": "base_shift" }, "stretch": { "name": "stretch", "tooltip": "Stretch the sigmas to be in the range [terminal, 1]." }, "terminal": { "name": "terminal", "tooltip": "The terminal value of the sigmas after stretching." }, "latent": { "name": "latent" } }, "outputs": { "0": { "tooltip": null } } };
const LumaConceptsNode = { "display_name": "Luma Concepts", "description": "Camera Concepts for use with Luma Text to Video and Luma Image to Video nodes.", "inputs": { "concept1": { "name": "concept1" }, "concept2": { "name": "concept2" }, "concept3": { "name": "concept3" }, "concept4": { "name": "concept4" }, "luma_concepts": { "name": "luma_concepts", "tooltip": "Optional Camera Concepts to add to the ones chosen here." } }, "outputs": { "0": { "name": "luma_concepts", "tooltip": null } } };
const LumaImageModifyNode = { "display_name": "Luma Image to Image", "description": "Modifies images synchronously based on prompt and aspect ratio.", "inputs": { "image": { "name": "image" }, "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation" }, "image_weight": { "name": "image_weight", "tooltip": "Weight of the image; the closer to 1.0, the less the image will be modified." }, "model": { "name": "model" }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const LumaImageNode = { "display_name": "Luma Text to Image", "description": "Generates images synchronously based on prompt and aspect ratio.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation" }, "model": { "name": "model" }, "aspect_ratio": { "name": "aspect_ratio" }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "style_image_weight": { "name": "style_image_weight", "tooltip": "Weight of style image. Ignored if no style_image provided." }, "image_luma_ref": { "name": "image_luma_ref", "tooltip": "Luma Reference node connection to influence generation with input images; up to 4 images can be considered." }, "style_image": { "name": "style_image", "tooltip": "Style reference image; only 1 image will be used." }, "character_image": { "name": "character_image", "tooltip": "Character reference images; can be a batch of multiple, up to 4 images can be considered." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const LumaImageToVideoNode = { "display_name": "Luma Image to Video", "description": "Generates videos synchronously based on prompt, input images, and output_size.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the video generation" }, "model": { "name": "model" }, "resolution": { "name": "resolution" }, "duration": { "name": "duration" }, "loop": { "name": "loop" }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "first_image": { "name": "first_image", "tooltip": "First frame of generated video." }, "last_image": { "name": "last_image", "tooltip": "Last frame of generated video." }, "luma_concepts": { "name": "luma_concepts", "tooltip": "Optional Camera Concepts to dictate camera motion via the Luma Concepts node." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const LumaReferenceNode = { "display_name": "Luma Reference", "description": "Holds an image and weight for use with Luma Generate Image node.", "inputs": { "image": { "name": "image", "tooltip": "Image to use as reference." }, "weight": { "name": "weight", "tooltip": "Weight of image reference." }, "luma_ref": { "name": "luma_ref" } }, "outputs": { "0": { "name": "luma_ref", "tooltip": null } } };
const LumaVideoNode = { "display_name": "Luma Text to Video", "description": "Generates videos synchronously based on prompt and output_size.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the video generation" }, "model": { "name": "model" }, "aspect_ratio": { "name": "aspect_ratio" }, "resolution": { "name": "resolution" }, "duration": { "name": "duration" }, "loop": { "name": "loop" }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "luma_concepts": { "name": "luma_concepts", "tooltip": "Optional Camera Concepts to dictate camera motion via the Luma Concepts node." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const Mahiro = { "display_name": "Mahiro is so cute that she deserves a better guidance function!! (。・ω・。)", "description": "Modify the guidance to scale more on the 'direction' of the positive prompt rather than the difference between the negative prompt.", "inputs": { "model": { "name": "model" } }, "outputs": { "0": { "name": "patched_model", "tooltip": null } } };
const MakeTrainingDataset = { "display_name": "Make Training Dataset", "inputs": { "images": { "name": "images", "tooltip": "List of images to encode." }, "vae": { "name": "vae", "tooltip": "VAE model for encoding images to latents." }, "clip": { "name": "clip", "tooltip": "CLIP model for encoding text to conditioning." }, "texts": { "name": "texts", "tooltip": "List of text captions. Can be length n (matching images), 1 (repeated for all), or omitted (uses empty string)." } }, "outputs": { "0": { "name": "latents", "tooltip": "List of latent dicts" }, "1": { "name": "conditioning", "tooltip": "List of conditioning lists" } } };
const ManualSigmas = { "display_name": "ManualSigmas", "inputs": { "sigmas": { "name": "sigmas" } }, "outputs": { "0": { "tooltip": null } } };
const MaskComposite = { "display_name": "MaskComposite", "inputs": { "destination": { "name": "destination" }, "source": { "name": "source" }, "x": { "name": "x" }, "y": { "name": "y" }, "operation": { "name": "operation" } }, "outputs": { "0": { "tooltip": null } } };
const MaskPreview = { "display_name": "Preview Mask", "description": "Saves the input images to your ComfyUI output directory.", "inputs": { "mask": { "name": "mask" } } };
const MaskToImage = { "display_name": "Convert Mask to Image", "inputs": { "mask": { "name": "mask" } }, "outputs": { "0": { "tooltip": null } } };
const MergeImageLists = { "display_name": "Merge Image Lists", "inputs": { "images": { "name": "images", "tooltip": "List of images to process." } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const MergeTextLists = { "display_name": "Merge Text Lists", "inputs": { "texts": { "name": "texts", "tooltip": "List of texts to process." } }, "outputs": { "0": { "name": "texts", "tooltip": "Processed texts" } } };
const MinimaxHailuoVideoNode = { "display_name": "MiniMax Hailuo Video", "description": "Generates videos from prompt, with optional start frame using the new MiniMax Hailuo-02 model.", "inputs": { "prompt_text": { "name": "prompt_text", "tooltip": "Text prompt to guide the video generation." }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "first_frame_image": { "name": "first_frame_image", "tooltip": "Optional image to use as the first frame to generate a video." }, "prompt_optimizer": { "name": "prompt_optimizer", "tooltip": "Optimize prompt to improve generation quality when needed." }, "duration": { "name": "duration", "tooltip": "The length of the output video in seconds." }, "resolution": { "name": "resolution", "tooltip": "The dimensions of the video display. 1080p is 1920x1080, 768p is 1366x768." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const MinimaxImageToVideoNode = { "display_name": "MiniMax Image to Video", "description": "Generates videos synchronously based on an image and prompt, and optional parameters.", "inputs": { "image": { "name": "image", "tooltip": "Image to use as first frame of video generation" }, "prompt_text": { "name": "prompt_text", "tooltip": "Text prompt to guide the video generation" }, "model": { "name": "model", "tooltip": "Model to use for video generation" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const MinimaxTextToVideoNode = { "display_name": "MiniMax Text to Video", "description": "Generates videos synchronously based on a prompt, and optional parameters.", "inputs": { "prompt_text": { "name": "prompt_text", "tooltip": "Text prompt to guide the video generation" }, "model": { "name": "model", "tooltip": "Model to use for video generation" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ModelComputeDtype = { "display_name": "ModelComputeDtype", "inputs": { "model": { "name": "model" }, "dtype": { "name": "dtype" } } };
const ModelMergeAdd = { "display_name": "ModelMergeAdd", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" } } };
const ModelMergeAuraflow = { "display_name": "ModelMergeAuraflow", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "init_x_linear_": { "name": "init_x_linear." }, "positional_encoding": { "name": "positional_encoding" }, "cond_seq_linear_": { "name": "cond_seq_linear." }, "register_tokens": { "name": "register_tokens" }, "t_embedder_": { "name": "t_embedder." }, "double_layers_0_": { "name": "double_layers.0." }, "double_layers_1_": { "name": "double_layers.1." }, "double_layers_2_": { "name": "double_layers.2." }, "double_layers_3_": { "name": "double_layers.3." }, "single_layers_0_": { "name": "single_layers.0." }, "single_layers_1_": { "name": "single_layers.1." }, "single_layers_2_": { "name": "single_layers.2." }, "single_layers_3_": { "name": "single_layers.3." }, "single_layers_4_": { "name": "single_layers.4." }, "single_layers_5_": { "name": "single_layers.5." }, "single_layers_6_": { "name": "single_layers.6." }, "single_layers_7_": { "name": "single_layers.7." }, "single_layers_8_": { "name": "single_layers.8." }, "single_layers_9_": { "name": "single_layers.9." }, "single_layers_10_": { "name": "single_layers.10." }, "single_layers_11_": { "name": "single_layers.11." }, "single_layers_12_": { "name": "single_layers.12." }, "single_layers_13_": { "name": "single_layers.13." }, "single_layers_14_": { "name": "single_layers.14." }, "single_layers_15_": { "name": "single_layers.15." }, "single_layers_16_": { "name": "single_layers.16." }, "single_layers_17_": { "name": "single_layers.17." }, "single_layers_18_": { "name": "single_layers.18." }, "single_layers_19_": { "name": "single_layers.19." }, "single_layers_20_": { "name": "single_layers.20." }, "single_layers_21_": { "name": "single_layers.21." }, "single_layers_22_": { "name": "single_layers.22." }, "single_layers_23_": { "name": "single_layers.23." }, "single_layers_24_": { "name": "single_layers.24." }, "single_layers_25_": { "name": "single_layers.25." }, "single_layers_26_": { "name": "single_layers.26." }, "single_layers_27_": { "name": "single_layers.27." }, "single_layers_28_": { "name": "single_layers.28." }, "single_layers_29_": { "name": "single_layers.29." }, "single_layers_30_": { "name": "single_layers.30." }, "single_layers_31_": { "name": "single_layers.31." }, "modF_": { "name": "modF." }, "final_linear_": { "name": "final_linear." } } };
const ModelMergeBlocks = { "display_name": "ModelMergeBlocks", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "input": { "name": "input" }, "middle": { "name": "middle" }, "out": { "name": "out" } } };
const ModelMergeCosmos14B = { "display_name": "ModelMergeCosmos14B", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "pos_embedder_": { "name": "pos_embedder." }, "extra_pos_embedder_": { "name": "extra_pos_embedder." }, "x_embedder_": { "name": "x_embedder." }, "t_embedder_": { "name": "t_embedder." }, "affline_norm_": { "name": "affline_norm." }, "blocks_block0_": { "name": "blocks.block0." }, "blocks_block1_": { "name": "blocks.block1." }, "blocks_block2_": { "name": "blocks.block2." }, "blocks_block3_": { "name": "blocks.block3." }, "blocks_block4_": { "name": "blocks.block4." }, "blocks_block5_": { "name": "blocks.block5." }, "blocks_block6_": { "name": "blocks.block6." }, "blocks_block7_": { "name": "blocks.block7." }, "blocks_block8_": { "name": "blocks.block8." }, "blocks_block9_": { "name": "blocks.block9." }, "blocks_block10_": { "name": "blocks.block10." }, "blocks_block11_": { "name": "blocks.block11." }, "blocks_block12_": { "name": "blocks.block12." }, "blocks_block13_": { "name": "blocks.block13." }, "blocks_block14_": { "name": "blocks.block14." }, "blocks_block15_": { "name": "blocks.block15." }, "blocks_block16_": { "name": "blocks.block16." }, "blocks_block17_": { "name": "blocks.block17." }, "blocks_block18_": { "name": "blocks.block18." }, "blocks_block19_": { "name": "blocks.block19." }, "blocks_block20_": { "name": "blocks.block20." }, "blocks_block21_": { "name": "blocks.block21." }, "blocks_block22_": { "name": "blocks.block22." }, "blocks_block23_": { "name": "blocks.block23." }, "blocks_block24_": { "name": "blocks.block24." }, "blocks_block25_": { "name": "blocks.block25." }, "blocks_block26_": { "name": "blocks.block26." }, "blocks_block27_": { "name": "blocks.block27." }, "blocks_block28_": { "name": "blocks.block28." }, "blocks_block29_": { "name": "blocks.block29." }, "blocks_block30_": { "name": "blocks.block30." }, "blocks_block31_": { "name": "blocks.block31." }, "blocks_block32_": { "name": "blocks.block32." }, "blocks_block33_": { "name": "blocks.block33." }, "blocks_block34_": { "name": "blocks.block34." }, "blocks_block35_": { "name": "blocks.block35." }, "final_layer_": { "name": "final_layer." } } };
const ModelMergeCosmos7B = { "display_name": "ModelMergeCosmos7B", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "pos_embedder_": { "name": "pos_embedder." }, "extra_pos_embedder_": { "name": "extra_pos_embedder." }, "x_embedder_": { "name": "x_embedder." }, "t_embedder_": { "name": "t_embedder." }, "affline_norm_": { "name": "affline_norm." }, "blocks_block0_": { "name": "blocks.block0." }, "blocks_block1_": { "name": "blocks.block1." }, "blocks_block2_": { "name": "blocks.block2." }, "blocks_block3_": { "name": "blocks.block3." }, "blocks_block4_": { "name": "blocks.block4." }, "blocks_block5_": { "name": "blocks.block5." }, "blocks_block6_": { "name": "blocks.block6." }, "blocks_block7_": { "name": "blocks.block7." }, "blocks_block8_": { "name": "blocks.block8." }, "blocks_block9_": { "name": "blocks.block9." }, "blocks_block10_": { "name": "blocks.block10." }, "blocks_block11_": { "name": "blocks.block11." }, "blocks_block12_": { "name": "blocks.block12." }, "blocks_block13_": { "name": "blocks.block13." }, "blocks_block14_": { "name": "blocks.block14." }, "blocks_block15_": { "name": "blocks.block15." }, "blocks_block16_": { "name": "blocks.block16." }, "blocks_block17_": { "name": "blocks.block17." }, "blocks_block18_": { "name": "blocks.block18." }, "blocks_block19_": { "name": "blocks.block19." }, "blocks_block20_": { "name": "blocks.block20." }, "blocks_block21_": { "name": "blocks.block21." }, "blocks_block22_": { "name": "blocks.block22." }, "blocks_block23_": { "name": "blocks.block23." }, "blocks_block24_": { "name": "blocks.block24." }, "blocks_block25_": { "name": "blocks.block25." }, "blocks_block26_": { "name": "blocks.block26." }, "blocks_block27_": { "name": "blocks.block27." }, "final_layer_": { "name": "final_layer." } } };
const ModelMergeCosmosPredict2_14B = { "display_name": "ModelMergeCosmosPredict2_14B", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "pos_embedder_": { "name": "pos_embedder." }, "x_embedder_": { "name": "x_embedder." }, "t_embedder_": { "name": "t_embedder." }, "t_embedding_norm_": { "name": "t_embedding_norm." }, "blocks_0_": { "name": "blocks.0." }, "blocks_1_": { "name": "blocks.1." }, "blocks_2_": { "name": "blocks.2." }, "blocks_3_": { "name": "blocks.3." }, "blocks_4_": { "name": "blocks.4." }, "blocks_5_": { "name": "blocks.5." }, "blocks_6_": { "name": "blocks.6." }, "blocks_7_": { "name": "blocks.7." }, "blocks_8_": { "name": "blocks.8." }, "blocks_9_": { "name": "blocks.9." }, "blocks_10_": { "name": "blocks.10." }, "blocks_11_": { "name": "blocks.11." }, "blocks_12_": { "name": "blocks.12." }, "blocks_13_": { "name": "blocks.13." }, "blocks_14_": { "name": "blocks.14." }, "blocks_15_": { "name": "blocks.15." }, "blocks_16_": { "name": "blocks.16." }, "blocks_17_": { "name": "blocks.17." }, "blocks_18_": { "name": "blocks.18." }, "blocks_19_": { "name": "blocks.19." }, "blocks_20_": { "name": "blocks.20." }, "blocks_21_": { "name": "blocks.21." }, "blocks_22_": { "name": "blocks.22." }, "blocks_23_": { "name": "blocks.23." }, "blocks_24_": { "name": "blocks.24." }, "blocks_25_": { "name": "blocks.25." }, "blocks_26_": { "name": "blocks.26." }, "blocks_27_": { "name": "blocks.27." }, "blocks_28_": { "name": "blocks.28." }, "blocks_29_": { "name": "blocks.29." }, "blocks_30_": { "name": "blocks.30." }, "blocks_31_": { "name": "blocks.31." }, "blocks_32_": { "name": "blocks.32." }, "blocks_33_": { "name": "blocks.33." }, "blocks_34_": { "name": "blocks.34." }, "blocks_35_": { "name": "blocks.35." }, "final_layer_": { "name": "final_layer." } } };
const ModelMergeCosmosPredict2_2B = { "display_name": "ModelMergeCosmosPredict2_2B", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "pos_embedder_": { "name": "pos_embedder." }, "x_embedder_": { "name": "x_embedder." }, "t_embedder_": { "name": "t_embedder." }, "t_embedding_norm_": { "name": "t_embedding_norm." }, "blocks_0_": { "name": "blocks.0." }, "blocks_1_": { "name": "blocks.1." }, "blocks_2_": { "name": "blocks.2." }, "blocks_3_": { "name": "blocks.3." }, "blocks_4_": { "name": "blocks.4." }, "blocks_5_": { "name": "blocks.5." }, "blocks_6_": { "name": "blocks.6." }, "blocks_7_": { "name": "blocks.7." }, "blocks_8_": { "name": "blocks.8." }, "blocks_9_": { "name": "blocks.9." }, "blocks_10_": { "name": "blocks.10." }, "blocks_11_": { "name": "blocks.11." }, "blocks_12_": { "name": "blocks.12." }, "blocks_13_": { "name": "blocks.13." }, "blocks_14_": { "name": "blocks.14." }, "blocks_15_": { "name": "blocks.15." }, "blocks_16_": { "name": "blocks.16." }, "blocks_17_": { "name": "blocks.17." }, "blocks_18_": { "name": "blocks.18." }, "blocks_19_": { "name": "blocks.19." }, "blocks_20_": { "name": "blocks.20." }, "blocks_21_": { "name": "blocks.21." }, "blocks_22_": { "name": "blocks.22." }, "blocks_23_": { "name": "blocks.23." }, "blocks_24_": { "name": "blocks.24." }, "blocks_25_": { "name": "blocks.25." }, "blocks_26_": { "name": "blocks.26." }, "blocks_27_": { "name": "blocks.27." }, "final_layer_": { "name": "final_layer." } } };
const ModelMergeFlux1 = { "display_name": "ModelMergeFlux1", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "img_in_": { "name": "img_in." }, "time_in_": { "name": "time_in." }, "guidance_in": { "name": "guidance_in" }, "vector_in_": { "name": "vector_in." }, "txt_in_": { "name": "txt_in." }, "double_blocks_0_": { "name": "double_blocks.0." }, "double_blocks_1_": { "name": "double_blocks.1." }, "double_blocks_2_": { "name": "double_blocks.2." }, "double_blocks_3_": { "name": "double_blocks.3." }, "double_blocks_4_": { "name": "double_blocks.4." }, "double_blocks_5_": { "name": "double_blocks.5." }, "double_blocks_6_": { "name": "double_blocks.6." }, "double_blocks_7_": { "name": "double_blocks.7." }, "double_blocks_8_": { "name": "double_blocks.8." }, "double_blocks_9_": { "name": "double_blocks.9." }, "double_blocks_10_": { "name": "double_blocks.10." }, "double_blocks_11_": { "name": "double_blocks.11." }, "double_blocks_12_": { "name": "double_blocks.12." }, "double_blocks_13_": { "name": "double_blocks.13." }, "double_blocks_14_": { "name": "double_blocks.14." }, "double_blocks_15_": { "name": "double_blocks.15." }, "double_blocks_16_": { "name": "double_blocks.16." }, "double_blocks_17_": { "name": "double_blocks.17." }, "double_blocks_18_": { "name": "double_blocks.18." }, "single_blocks_0_": { "name": "single_blocks.0." }, "single_blocks_1_": { "name": "single_blocks.1." }, "single_blocks_2_": { "name": "single_blocks.2." }, "single_blocks_3_": { "name": "single_blocks.3." }, "single_blocks_4_": { "name": "single_blocks.4." }, "single_blocks_5_": { "name": "single_blocks.5." }, "single_blocks_6_": { "name": "single_blocks.6." }, "single_blocks_7_": { "name": "single_blocks.7." }, "single_blocks_8_": { "name": "single_blocks.8." }, "single_blocks_9_": { "name": "single_blocks.9." }, "single_blocks_10_": { "name": "single_blocks.10." }, "single_blocks_11_": { "name": "single_blocks.11." }, "single_blocks_12_": { "name": "single_blocks.12." }, "single_blocks_13_": { "name": "single_blocks.13." }, "single_blocks_14_": { "name": "single_blocks.14." }, "single_blocks_15_": { "name": "single_blocks.15." }, "single_blocks_16_": { "name": "single_blocks.16." }, "single_blocks_17_": { "name": "single_blocks.17." }, "single_blocks_18_": { "name": "single_blocks.18." }, "single_blocks_19_": { "name": "single_blocks.19." }, "single_blocks_20_": { "name": "single_blocks.20." }, "single_blocks_21_": { "name": "single_blocks.21." }, "single_blocks_22_": { "name": "single_blocks.22." }, "single_blocks_23_": { "name": "single_blocks.23." }, "single_blocks_24_": { "name": "single_blocks.24." }, "single_blocks_25_": { "name": "single_blocks.25." }, "single_blocks_26_": { "name": "single_blocks.26." }, "single_blocks_27_": { "name": "single_blocks.27." }, "single_blocks_28_": { "name": "single_blocks.28." }, "single_blocks_29_": { "name": "single_blocks.29." }, "single_blocks_30_": { "name": "single_blocks.30." }, "single_blocks_31_": { "name": "single_blocks.31." }, "single_blocks_32_": { "name": "single_blocks.32." }, "single_blocks_33_": { "name": "single_blocks.33." }, "single_blocks_34_": { "name": "single_blocks.34." }, "single_blocks_35_": { "name": "single_blocks.35." }, "single_blocks_36_": { "name": "single_blocks.36." }, "single_blocks_37_": { "name": "single_blocks.37." }, "final_layer_": { "name": "final_layer." } } };
const ModelMergeLTXV = { "display_name": "ModelMergeLTXV", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "patchify_proj_": { "name": "patchify_proj." }, "adaln_single_": { "name": "adaln_single." }, "caption_projection_": { "name": "caption_projection." }, "transformer_blocks_0_": { "name": "transformer_blocks.0." }, "transformer_blocks_1_": { "name": "transformer_blocks.1." }, "transformer_blocks_2_": { "name": "transformer_blocks.2." }, "transformer_blocks_3_": { "name": "transformer_blocks.3." }, "transformer_blocks_4_": { "name": "transformer_blocks.4." }, "transformer_blocks_5_": { "name": "transformer_blocks.5." }, "transformer_blocks_6_": { "name": "transformer_blocks.6." }, "transformer_blocks_7_": { "name": "transformer_blocks.7." }, "transformer_blocks_8_": { "name": "transformer_blocks.8." }, "transformer_blocks_9_": { "name": "transformer_blocks.9." }, "transformer_blocks_10_": { "name": "transformer_blocks.10." }, "transformer_blocks_11_": { "name": "transformer_blocks.11." }, "transformer_blocks_12_": { "name": "transformer_blocks.12." }, "transformer_blocks_13_": { "name": "transformer_blocks.13." }, "transformer_blocks_14_": { "name": "transformer_blocks.14." }, "transformer_blocks_15_": { "name": "transformer_blocks.15." }, "transformer_blocks_16_": { "name": "transformer_blocks.16." }, "transformer_blocks_17_": { "name": "transformer_blocks.17." }, "transformer_blocks_18_": { "name": "transformer_blocks.18." }, "transformer_blocks_19_": { "name": "transformer_blocks.19." }, "transformer_blocks_20_": { "name": "transformer_blocks.20." }, "transformer_blocks_21_": { "name": "transformer_blocks.21." }, "transformer_blocks_22_": { "name": "transformer_blocks.22." }, "transformer_blocks_23_": { "name": "transformer_blocks.23." }, "transformer_blocks_24_": { "name": "transformer_blocks.24." }, "transformer_blocks_25_": { "name": "transformer_blocks.25." }, "transformer_blocks_26_": { "name": "transformer_blocks.26." }, "transformer_blocks_27_": { "name": "transformer_blocks.27." }, "scale_shift_table": { "name": "scale_shift_table" }, "proj_out_": { "name": "proj_out." } } };
const ModelMergeMochiPreview = { "display_name": "ModelMergeMochiPreview", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "pos_frequencies_": { "name": "pos_frequencies." }, "t_embedder_": { "name": "t_embedder." }, "t5_y_embedder_": { "name": "t5_y_embedder." }, "t5_yproj_": { "name": "t5_yproj." }, "blocks_0_": { "name": "blocks.0." }, "blocks_1_": { "name": "blocks.1." }, "blocks_2_": { "name": "blocks.2." }, "blocks_3_": { "name": "blocks.3." }, "blocks_4_": { "name": "blocks.4." }, "blocks_5_": { "name": "blocks.5." }, "blocks_6_": { "name": "blocks.6." }, "blocks_7_": { "name": "blocks.7." }, "blocks_8_": { "name": "blocks.8." }, "blocks_9_": { "name": "blocks.9." }, "blocks_10_": { "name": "blocks.10." }, "blocks_11_": { "name": "blocks.11." }, "blocks_12_": { "name": "blocks.12." }, "blocks_13_": { "name": "blocks.13." }, "blocks_14_": { "name": "blocks.14." }, "blocks_15_": { "name": "blocks.15." }, "blocks_16_": { "name": "blocks.16." }, "blocks_17_": { "name": "blocks.17." }, "blocks_18_": { "name": "blocks.18." }, "blocks_19_": { "name": "blocks.19." }, "blocks_20_": { "name": "blocks.20." }, "blocks_21_": { "name": "blocks.21." }, "blocks_22_": { "name": "blocks.22." }, "blocks_23_": { "name": "blocks.23." }, "blocks_24_": { "name": "blocks.24." }, "blocks_25_": { "name": "blocks.25." }, "blocks_26_": { "name": "blocks.26." }, "blocks_27_": { "name": "blocks.27." }, "blocks_28_": { "name": "blocks.28." }, "blocks_29_": { "name": "blocks.29." }, "blocks_30_": { "name": "blocks.30." }, "blocks_31_": { "name": "blocks.31." }, "blocks_32_": { "name": "blocks.32." }, "blocks_33_": { "name": "blocks.33." }, "blocks_34_": { "name": "blocks.34." }, "blocks_35_": { "name": "blocks.35." }, "blocks_36_": { "name": "blocks.36." }, "blocks_37_": { "name": "blocks.37." }, "blocks_38_": { "name": "blocks.38." }, "blocks_39_": { "name": "blocks.39." }, "blocks_40_": { "name": "blocks.40." }, "blocks_41_": { "name": "blocks.41." }, "blocks_42_": { "name": "blocks.42." }, "blocks_43_": { "name": "blocks.43." }, "blocks_44_": { "name": "blocks.44." }, "blocks_45_": { "name": "blocks.45." }, "blocks_46_": { "name": "blocks.46." }, "blocks_47_": { "name": "blocks.47." }, "final_layer_": { "name": "final_layer." } } };
const ModelMergeQwenImage = { "display_name": "ModelMergeQwenImage", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "pos_embeds_": { "name": "pos_embeds." }, "img_in_": { "name": "img_in." }, "txt_norm_": { "name": "txt_norm." }, "txt_in_": { "name": "txt_in." }, "time_text_embed_": { "name": "time_text_embed." }, "transformer_blocks_0_": { "name": "transformer_blocks.0." }, "transformer_blocks_1_": { "name": "transformer_blocks.1." }, "transformer_blocks_2_": { "name": "transformer_blocks.2." }, "transformer_blocks_3_": { "name": "transformer_blocks.3." }, "transformer_blocks_4_": { "name": "transformer_blocks.4." }, "transformer_blocks_5_": { "name": "transformer_blocks.5." }, "transformer_blocks_6_": { "name": "transformer_blocks.6." }, "transformer_blocks_7_": { "name": "transformer_blocks.7." }, "transformer_blocks_8_": { "name": "transformer_blocks.8." }, "transformer_blocks_9_": { "name": "transformer_blocks.9." }, "transformer_blocks_10_": { "name": "transformer_blocks.10." }, "transformer_blocks_11_": { "name": "transformer_blocks.11." }, "transformer_blocks_12_": { "name": "transformer_blocks.12." }, "transformer_blocks_13_": { "name": "transformer_blocks.13." }, "transformer_blocks_14_": { "name": "transformer_blocks.14." }, "transformer_blocks_15_": { "name": "transformer_blocks.15." }, "transformer_blocks_16_": { "name": "transformer_blocks.16." }, "transformer_blocks_17_": { "name": "transformer_blocks.17." }, "transformer_blocks_18_": { "name": "transformer_blocks.18." }, "transformer_blocks_19_": { "name": "transformer_blocks.19." }, "transformer_blocks_20_": { "name": "transformer_blocks.20." }, "transformer_blocks_21_": { "name": "transformer_blocks.21." }, "transformer_blocks_22_": { "name": "transformer_blocks.22." }, "transformer_blocks_23_": { "name": "transformer_blocks.23." }, "transformer_blocks_24_": { "name": "transformer_blocks.24." }, "transformer_blocks_25_": { "name": "transformer_blocks.25." }, "transformer_blocks_26_": { "name": "transformer_blocks.26." }, "transformer_blocks_27_": { "name": "transformer_blocks.27." }, "transformer_blocks_28_": { "name": "transformer_blocks.28." }, "transformer_blocks_29_": { "name": "transformer_blocks.29." }, "transformer_blocks_30_": { "name": "transformer_blocks.30." }, "transformer_blocks_31_": { "name": "transformer_blocks.31." }, "transformer_blocks_32_": { "name": "transformer_blocks.32." }, "transformer_blocks_33_": { "name": "transformer_blocks.33." }, "transformer_blocks_34_": { "name": "transformer_blocks.34." }, "transformer_blocks_35_": { "name": "transformer_blocks.35." }, "transformer_blocks_36_": { "name": "transformer_blocks.36." }, "transformer_blocks_37_": { "name": "transformer_blocks.37." }, "transformer_blocks_38_": { "name": "transformer_blocks.38." }, "transformer_blocks_39_": { "name": "transformer_blocks.39." }, "transformer_blocks_40_": { "name": "transformer_blocks.40." }, "transformer_blocks_41_": { "name": "transformer_blocks.41." }, "transformer_blocks_42_": { "name": "transformer_blocks.42." }, "transformer_blocks_43_": { "name": "transformer_blocks.43." }, "transformer_blocks_44_": { "name": "transformer_blocks.44." }, "transformer_blocks_45_": { "name": "transformer_blocks.45." }, "transformer_blocks_46_": { "name": "transformer_blocks.46." }, "transformer_blocks_47_": { "name": "transformer_blocks.47." }, "transformer_blocks_48_": { "name": "transformer_blocks.48." }, "transformer_blocks_49_": { "name": "transformer_blocks.49." }, "transformer_blocks_50_": { "name": "transformer_blocks.50." }, "transformer_blocks_51_": { "name": "transformer_blocks.51." }, "transformer_blocks_52_": { "name": "transformer_blocks.52." }, "transformer_blocks_53_": { "name": "transformer_blocks.53." }, "transformer_blocks_54_": { "name": "transformer_blocks.54." }, "transformer_blocks_55_": { "name": "transformer_blocks.55." }, "transformer_blocks_56_": { "name": "transformer_blocks.56." }, "transformer_blocks_57_": { "name": "transformer_blocks.57." }, "transformer_blocks_58_": { "name": "transformer_blocks.58." }, "transformer_blocks_59_": { "name": "transformer_blocks.59." }, "proj_out_": { "name": "proj_out." } } };
const ModelMergeSD1 = { "display_name": "ModelMergeSD1", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "time_embed_": { "name": "time_embed." }, "label_emb_": { "name": "label_emb." }, "input_blocks_0_": { "name": "input_blocks.0." }, "input_blocks_1_": { "name": "input_blocks.1." }, "input_blocks_2_": { "name": "input_blocks.2." }, "input_blocks_3_": { "name": "input_blocks.3." }, "input_blocks_4_": { "name": "input_blocks.4." }, "input_blocks_5_": { "name": "input_blocks.5." }, "input_blocks_6_": { "name": "input_blocks.6." }, "input_blocks_7_": { "name": "input_blocks.7." }, "input_blocks_8_": { "name": "input_blocks.8." }, "input_blocks_9_": { "name": "input_blocks.9." }, "input_blocks_10_": { "name": "input_blocks.10." }, "input_blocks_11_": { "name": "input_blocks.11." }, "middle_block_0_": { "name": "middle_block.0." }, "middle_block_1_": { "name": "middle_block.1." }, "middle_block_2_": { "name": "middle_block.2." }, "output_blocks_0_": { "name": "output_blocks.0." }, "output_blocks_1_": { "name": "output_blocks.1." }, "output_blocks_2_": { "name": "output_blocks.2." }, "output_blocks_3_": { "name": "output_blocks.3." }, "output_blocks_4_": { "name": "output_blocks.4." }, "output_blocks_5_": { "name": "output_blocks.5." }, "output_blocks_6_": { "name": "output_blocks.6." }, "output_blocks_7_": { "name": "output_blocks.7." }, "output_blocks_8_": { "name": "output_blocks.8." }, "output_blocks_9_": { "name": "output_blocks.9." }, "output_blocks_10_": { "name": "output_blocks.10." }, "output_blocks_11_": { "name": "output_blocks.11." }, "out_": { "name": "out." } } };
const ModelMergeSD2 = { "display_name": "ModelMergeSD2", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "time_embed_": { "name": "time_embed." }, "label_emb_": { "name": "label_emb." }, "input_blocks_0_": { "name": "input_blocks.0." }, "input_blocks_1_": { "name": "input_blocks.1." }, "input_blocks_2_": { "name": "input_blocks.2." }, "input_blocks_3_": { "name": "input_blocks.3." }, "input_blocks_4_": { "name": "input_blocks.4." }, "input_blocks_5_": { "name": "input_blocks.5." }, "input_blocks_6_": { "name": "input_blocks.6." }, "input_blocks_7_": { "name": "input_blocks.7." }, "input_blocks_8_": { "name": "input_blocks.8." }, "input_blocks_9_": { "name": "input_blocks.9." }, "input_blocks_10_": { "name": "input_blocks.10." }, "input_blocks_11_": { "name": "input_blocks.11." }, "middle_block_0_": { "name": "middle_block.0." }, "middle_block_1_": { "name": "middle_block.1." }, "middle_block_2_": { "name": "middle_block.2." }, "output_blocks_0_": { "name": "output_blocks.0." }, "output_blocks_1_": { "name": "output_blocks.1." }, "output_blocks_2_": { "name": "output_blocks.2." }, "output_blocks_3_": { "name": "output_blocks.3." }, "output_blocks_4_": { "name": "output_blocks.4." }, "output_blocks_5_": { "name": "output_blocks.5." }, "output_blocks_6_": { "name": "output_blocks.6." }, "output_blocks_7_": { "name": "output_blocks.7." }, "output_blocks_8_": { "name": "output_blocks.8." }, "output_blocks_9_": { "name": "output_blocks.9." }, "output_blocks_10_": { "name": "output_blocks.10." }, "output_blocks_11_": { "name": "output_blocks.11." }, "out_": { "name": "out." } } };
const ModelMergeSD3_2B = { "display_name": "ModelMergeSD3_2B", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "pos_embed_": { "name": "pos_embed." }, "x_embedder_": { "name": "x_embedder." }, "context_embedder_": { "name": "context_embedder." }, "y_embedder_": { "name": "y_embedder." }, "t_embedder_": { "name": "t_embedder." }, "joint_blocks_0_": { "name": "joint_blocks.0." }, "joint_blocks_1_": { "name": "joint_blocks.1." }, "joint_blocks_2_": { "name": "joint_blocks.2." }, "joint_blocks_3_": { "name": "joint_blocks.3." }, "joint_blocks_4_": { "name": "joint_blocks.4." }, "joint_blocks_5_": { "name": "joint_blocks.5." }, "joint_blocks_6_": { "name": "joint_blocks.6." }, "joint_blocks_7_": { "name": "joint_blocks.7." }, "joint_blocks_8_": { "name": "joint_blocks.8." }, "joint_blocks_9_": { "name": "joint_blocks.9." }, "joint_blocks_10_": { "name": "joint_blocks.10." }, "joint_blocks_11_": { "name": "joint_blocks.11." }, "joint_blocks_12_": { "name": "joint_blocks.12." }, "joint_blocks_13_": { "name": "joint_blocks.13." }, "joint_blocks_14_": { "name": "joint_blocks.14." }, "joint_blocks_15_": { "name": "joint_blocks.15." }, "joint_blocks_16_": { "name": "joint_blocks.16." }, "joint_blocks_17_": { "name": "joint_blocks.17." }, "joint_blocks_18_": { "name": "joint_blocks.18." }, "joint_blocks_19_": { "name": "joint_blocks.19." }, "joint_blocks_20_": { "name": "joint_blocks.20." }, "joint_blocks_21_": { "name": "joint_blocks.21." }, "joint_blocks_22_": { "name": "joint_blocks.22." }, "joint_blocks_23_": { "name": "joint_blocks.23." }, "final_layer_": { "name": "final_layer." } } };
const ModelMergeSD35_Large = { "display_name": "ModelMergeSD35_Large", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "pos_embed_": { "name": "pos_embed." }, "x_embedder_": { "name": "x_embedder." }, "context_embedder_": { "name": "context_embedder." }, "y_embedder_": { "name": "y_embedder." }, "t_embedder_": { "name": "t_embedder." }, "joint_blocks_0_": { "name": "joint_blocks.0." }, "joint_blocks_1_": { "name": "joint_blocks.1." }, "joint_blocks_2_": { "name": "joint_blocks.2." }, "joint_blocks_3_": { "name": "joint_blocks.3." }, "joint_blocks_4_": { "name": "joint_blocks.4." }, "joint_blocks_5_": { "name": "joint_blocks.5." }, "joint_blocks_6_": { "name": "joint_blocks.6." }, "joint_blocks_7_": { "name": "joint_blocks.7." }, "joint_blocks_8_": { "name": "joint_blocks.8." }, "joint_blocks_9_": { "name": "joint_blocks.9." }, "joint_blocks_10_": { "name": "joint_blocks.10." }, "joint_blocks_11_": { "name": "joint_blocks.11." }, "joint_blocks_12_": { "name": "joint_blocks.12." }, "joint_blocks_13_": { "name": "joint_blocks.13." }, "joint_blocks_14_": { "name": "joint_blocks.14." }, "joint_blocks_15_": { "name": "joint_blocks.15." }, "joint_blocks_16_": { "name": "joint_blocks.16." }, "joint_blocks_17_": { "name": "joint_blocks.17." }, "joint_blocks_18_": { "name": "joint_blocks.18." }, "joint_blocks_19_": { "name": "joint_blocks.19." }, "joint_blocks_20_": { "name": "joint_blocks.20." }, "joint_blocks_21_": { "name": "joint_blocks.21." }, "joint_blocks_22_": { "name": "joint_blocks.22." }, "joint_blocks_23_": { "name": "joint_blocks.23." }, "joint_blocks_24_": { "name": "joint_blocks.24." }, "joint_blocks_25_": { "name": "joint_blocks.25." }, "joint_blocks_26_": { "name": "joint_blocks.26." }, "joint_blocks_27_": { "name": "joint_blocks.27." }, "joint_blocks_28_": { "name": "joint_blocks.28." }, "joint_blocks_29_": { "name": "joint_blocks.29." }, "joint_blocks_30_": { "name": "joint_blocks.30." }, "joint_blocks_31_": { "name": "joint_blocks.31." }, "joint_blocks_32_": { "name": "joint_blocks.32." }, "joint_blocks_33_": { "name": "joint_blocks.33." }, "joint_blocks_34_": { "name": "joint_blocks.34." }, "joint_blocks_35_": { "name": "joint_blocks.35." }, "joint_blocks_36_": { "name": "joint_blocks.36." }, "joint_blocks_37_": { "name": "joint_blocks.37." }, "final_layer_": { "name": "final_layer." } } };
const ModelMergeSDXL = { "display_name": "ModelMergeSDXL", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "time_embed_": { "name": "time_embed." }, "label_emb_": { "name": "label_emb." }, "input_blocks_0": { "name": "input_blocks.0" }, "input_blocks_1": { "name": "input_blocks.1" }, "input_blocks_2": { "name": "input_blocks.2" }, "input_blocks_3": { "name": "input_blocks.3" }, "input_blocks_4": { "name": "input_blocks.4" }, "input_blocks_5": { "name": "input_blocks.5" }, "input_blocks_6": { "name": "input_blocks.6" }, "input_blocks_7": { "name": "input_blocks.7" }, "input_blocks_8": { "name": "input_blocks.8" }, "middle_block_0": { "name": "middle_block.0" }, "middle_block_1": { "name": "middle_block.1" }, "middle_block_2": { "name": "middle_block.2" }, "output_blocks_0": { "name": "output_blocks.0" }, "output_blocks_1": { "name": "output_blocks.1" }, "output_blocks_2": { "name": "output_blocks.2" }, "output_blocks_3": { "name": "output_blocks.3" }, "output_blocks_4": { "name": "output_blocks.4" }, "output_blocks_5": { "name": "output_blocks.5" }, "output_blocks_6": { "name": "output_blocks.6" }, "output_blocks_7": { "name": "output_blocks.7" }, "output_blocks_8": { "name": "output_blocks.8" }, "out_": { "name": "out." } } };
const ModelMergeSimple = { "display_name": "ModelMergeSimple", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "ratio": { "name": "ratio" } } };
const ModelMergeSubtract = { "display_name": "ModelMergeSubtract", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "multiplier": { "name": "multiplier" } } };
const ModelMergeWAN2_1 = { "display_name": "ModelMergeWAN2_1", "description": "1.3B model has 30 blocks, 14B model has 40 blocks. Image to video model has the extra img_emb.", "inputs": { "model1": { "name": "model1" }, "model2": { "name": "model2" }, "patch_embedding_": { "name": "patch_embedding." }, "time_embedding_": { "name": "time_embedding." }, "time_projection_": { "name": "time_projection." }, "text_embedding_": { "name": "text_embedding." }, "img_emb_": { "name": "img_emb." }, "blocks_0_": { "name": "blocks.0." }, "blocks_1_": { "name": "blocks.1." }, "blocks_2_": { "name": "blocks.2." }, "blocks_3_": { "name": "blocks.3." }, "blocks_4_": { "name": "blocks.4." }, "blocks_5_": { "name": "blocks.5." }, "blocks_6_": { "name": "blocks.6." }, "blocks_7_": { "name": "blocks.7." }, "blocks_8_": { "name": "blocks.8." }, "blocks_9_": { "name": "blocks.9." }, "blocks_10_": { "name": "blocks.10." }, "blocks_11_": { "name": "blocks.11." }, "blocks_12_": { "name": "blocks.12." }, "blocks_13_": { "name": "blocks.13." }, "blocks_14_": { "name": "blocks.14." }, "blocks_15_": { "name": "blocks.15." }, "blocks_16_": { "name": "blocks.16." }, "blocks_17_": { "name": "blocks.17." }, "blocks_18_": { "name": "blocks.18." }, "blocks_19_": { "name": "blocks.19." }, "blocks_20_": { "name": "blocks.20." }, "blocks_21_": { "name": "blocks.21." }, "blocks_22_": { "name": "blocks.22." }, "blocks_23_": { "name": "blocks.23." }, "blocks_24_": { "name": "blocks.24." }, "blocks_25_": { "name": "blocks.25." }, "blocks_26_": { "name": "blocks.26." }, "blocks_27_": { "name": "blocks.27." }, "blocks_28_": { "name": "blocks.28." }, "blocks_29_": { "name": "blocks.29." }, "blocks_30_": { "name": "blocks.30." }, "blocks_31_": { "name": "blocks.31." }, "blocks_32_": { "name": "blocks.32." }, "blocks_33_": { "name": "blocks.33." }, "blocks_34_": { "name": "blocks.34." }, "blocks_35_": { "name": "blocks.35." }, "blocks_36_": { "name": "blocks.36." }, "blocks_37_": { "name": "blocks.37." }, "blocks_38_": { "name": "blocks.38." }, "blocks_39_": { "name": "blocks.39." }, "head_": { "name": "head." } } };
const ModelPatchLoader = { "display_name": "ModelPatchLoader", "inputs": { "name": { "name": "name" } } };
const ModelSamplingAuraFlow = { "display_name": "ModelSamplingAuraFlow", "inputs": { "model": { "name": "model" }, "shift": { "name": "shift" } } };
const ModelSamplingContinuousEDM = { "display_name": "ModelSamplingContinuousEDM", "inputs": { "model": { "name": "model" }, "sampling": { "name": "sampling" }, "sigma_max": { "name": "sigma_max" }, "sigma_min": { "name": "sigma_min" } } };
const ModelSamplingContinuousV = { "display_name": "ModelSamplingContinuousV", "inputs": { "model": { "name": "model" }, "sampling": { "name": "sampling" }, "sigma_max": { "name": "sigma_max" }, "sigma_min": { "name": "sigma_min" } } };
const ModelSamplingDiscrete = { "display_name": "ModelSamplingDiscrete", "inputs": { "model": { "name": "model" }, "sampling": { "name": "sampling" }, "zsnr": { "name": "zsnr" } } };
const ModelSamplingFlux = { "display_name": "ModelSamplingFlux", "inputs": { "model": { "name": "model" }, "max_shift": { "name": "max_shift" }, "base_shift": { "name": "base_shift" }, "width": { "name": "width" }, "height": { "name": "height" } } };
const ModelSamplingLTXV = { "display_name": "ModelSamplingLTXV", "inputs": { "model": { "name": "model" }, "max_shift": { "name": "max_shift" }, "base_shift": { "name": "base_shift" }, "latent": { "name": "latent" } }, "outputs": { "0": { "tooltip": null } } };
const ModelSamplingSD3 = { "display_name": "ModelSamplingSD3", "inputs": { "model": { "name": "model" }, "shift": { "name": "shift" } } };
const ModelSamplingStableCascade = { "display_name": "ModelSamplingStableCascade", "inputs": { "model": { "name": "model" }, "shift": { "name": "shift" } } };
const ModelSave = { "display_name": "ModelSave", "inputs": { "model": { "name": "model" }, "filename_prefix": { "name": "filename_prefix" } } };
const MoonvalleyImg2VideoNode = { "display_name": "Moonvalley Marey Image to Video", "description": "Moonvalley Marey Image to Video Node", "inputs": { "image": { "name": "image", "tooltip": "The reference image used to generate the video" }, "prompt": { "name": "prompt" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative prompt text" }, "resolution": { "name": "resolution", "tooltip": "Resolution of the output video" }, "prompt_adherence": { "name": "prompt_adherence", "tooltip": "Guidance scale for generation control" }, "seed": { "name": "seed", "tooltip": "Random seed value" }, "steps": { "name": "steps", "tooltip": "Number of denoising steps" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const MoonvalleyTxt2VideoNode = { "display_name": "Moonvalley Marey Text to Video", "inputs": { "prompt": { "name": "prompt" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative prompt text" }, "resolution": { "name": "resolution", "tooltip": "Resolution of the output video" }, "prompt_adherence": { "name": "prompt_adherence", "tooltip": "Guidance scale for generation control" }, "seed": { "name": "seed", "tooltip": "Random seed value" }, "steps": { "name": "steps", "tooltip": "Inference steps" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const MoonvalleyVideo2VideoNode = { "display_name": "Moonvalley Marey Video to Video", "inputs": { "prompt": { "name": "prompt", "tooltip": "Describes the video to generate" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative prompt text" }, "seed": { "name": "seed", "tooltip": "Random seed value" }, "video": { "name": "video", "tooltip": "The reference video used to generate the output video. Must be at least 5 seconds long. Videos longer than 5s will be automatically trimmed. Only MP4 format supported." }, "steps": { "name": "steps", "tooltip": "Number of inference steps" }, "control_type": { "name": "control_type" }, "motion_intensity": { "name": "motion_intensity", "tooltip": "Only used if control_type is 'Motion Transfer'" } }, "outputs": { "0": { "tooltip": null } } };
const Morphology = { "display_name": "ImageMorphology", "inputs": { "image": { "name": "image" }, "operation": { "name": "operation" }, "kernel_size": { "name": "kernel_size" } }, "outputs": { "0": { "tooltip": null } } };
const NormalizeImages = { "display_name": "Normalize Images", "inputs": { "images": { "name": "images", "tooltip": "Image to process." }, "mean": { "name": "mean", "tooltip": "Mean value for normalization." }, "std": { "name": "std", "tooltip": "Standard deviation for normalization." } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const NormalizeVideoLatentStart = { "display_name": "NormalizeVideoLatentStart", "description": "Normalizes the initial frames of a video latent to match the mean and standard deviation of subsequent reference frames. Helps reduce differences between the starting frames and the rest of the video.", "inputs": { "latent": { "name": "latent" }, "start_frame_count": { "name": "start_frame_count", "tooltip": "Number of latent frames to normalize, counted from the start" }, "reference_frame_count": { "name": "reference_frame_count", "tooltip": "Number of latent frames after the start frames to use as reference" } }, "outputs": { "0": { "name": "latent", "tooltip": null } } };
const OpenAIChatConfig = { "display_name": "OpenAI ChatGPT Advanced Options", "description": "Allows specifying advanced configuration options for the OpenAI Chat Nodes.", "inputs": { "truncation": { "name": "truncation", "tooltip": "The truncation strategy to use for the model response. auto: If the context of this response and previous ones exceeds the model's context window size, the model will truncate the response to fit the context window by dropping input items in the middle of the conversation.disabled: If a model response will exceed the context window size for a model, the request will fail with a 400 error" }, "max_output_tokens": { "name": "max_output_tokens", "tooltip": "An upper bound for the number of tokens that can be generated for a response, including visible output tokens" }, "instructions": { "name": "instructions", "tooltip": "Instructions for the model on how to generate the response" } }, "outputs": { "0": { "tooltip": null } } };
const OpenAIChatNode = { "display_name": "OpenAI ChatGPT", "description": "Generate text responses from an OpenAI model.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text inputs to the model, used to generate a response." }, "persist_context": { "name": "persist_context", "tooltip": "This parameter is deprecated and has no effect." }, "model": { "name": "model", "tooltip": "The model used to generate the response" }, "images": { "name": "images", "tooltip": "Optional image(s) to use as context for the model. To include multiple images, you can use the Batch Images node." }, "files": { "name": "files", "tooltip": "Optional file(s) to use as context for the model. Accepts inputs from the OpenAI Chat Input Files node." }, "advanced_options": { "name": "advanced_options", "tooltip": "Optional configuration for the model. Accepts inputs from the OpenAI Chat Advanced Options node." } }, "outputs": { "0": { "tooltip": null } } };
const OpenAIDalle2 = { "display_name": "OpenAI DALL·E 2", "description": "Generates images synchronously via OpenAI's DALL·E 2 endpoint.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text prompt for DALL·E" }, "seed": { "name": "seed", "tooltip": "not implemented yet in backend" }, "size": { "name": "size", "tooltip": "Image size" }, "n": { "name": "n", "tooltip": "How many images to generate" }, "image": { "name": "image", "tooltip": "Optional reference image for image editing." }, "mask": { "name": "mask", "tooltip": "Optional mask for inpainting (white areas will be replaced)" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const OpenAIDalle3 = { "display_name": "OpenAI DALL·E 3", "description": "Generates images synchronously via OpenAI's DALL·E 3 endpoint.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text prompt for DALL·E" }, "seed": { "name": "seed", "tooltip": "not implemented yet in backend" }, "quality": { "name": "quality", "tooltip": "Image quality" }, "style": { "name": "style", "tooltip": "Vivid causes the model to lean towards generating hyper-real and dramatic images. Natural causes the model to produce more natural, less hyper-real looking images." }, "size": { "name": "size", "tooltip": "Image size" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const OpenAIGPTImage1 = { "display_name": "OpenAI GPT Image 1", "description": "Generates images synchronously via OpenAI's GPT Image 1 endpoint.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text prompt for GPT Image" }, "seed": { "name": "seed", "tooltip": "not implemented yet in backend" }, "quality": { "name": "quality", "tooltip": "Image quality, affects cost and generation time." }, "background": { "name": "background", "tooltip": "Return image with or without background" }, "size": { "name": "size", "tooltip": "Image size" }, "n": { "name": "n", "tooltip": "How many images to generate" }, "image": { "name": "image", "tooltip": "Optional reference image for image editing." }, "mask": { "name": "mask", "tooltip": "Optional mask for inpainting (white areas will be replaced)" }, "model": { "name": "model" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const OpenAIInputFiles = { "display_name": "OpenAI ChatGPT Input Files", "description": "Loads and prepares input files (text, pdf, etc.) to include as inputs for the OpenAI Chat Node. The files will be read by the OpenAI model when generating a response. 🛈 TIP: Can be chained together with other OpenAI Input File nodes.", "inputs": { "file": { "name": "file", "tooltip": "Input files to include as context for the model. Only accepts text (.txt) and PDF (.pdf) files for now." }, "OPENAI_INPUT_FILES": { "name": "OPENAI_INPUT_FILES", "tooltip": "An optional additional file(s) to batch together with the file loaded from this node. Allows chaining of input files so that a single message can include multiple input files." } }, "outputs": { "0": { "tooltip": null } } };
const OpenAIVideoSora2 = { "display_name": "OpenAI Sora - Video", "description": "OpenAI video and audio generation.", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt", "tooltip": "Guiding text; may be empty if an input image is present." }, "size": { "name": "size" }, "duration": { "name": "duration" }, "image": { "name": "image" }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const OptimalStepsScheduler = { "display_name": "OptimalStepsScheduler", "inputs": { "model_type": { "name": "model_type" }, "steps": { "name": "steps" }, "denoise": { "name": "denoise" } }, "outputs": { "0": { "tooltip": null } } };
const PairConditioningCombine = { "display_name": "Cond Pair Combine", "inputs": { "positive_A": { "name": "positive_A" }, "negative_A": { "name": "negative_A" }, "positive_B": { "name": "positive_B" }, "negative_B": { "name": "negative_B" } }, "outputs": { "0": { "name": "positive" }, "1": { "name": "negative" } } };
const PairConditioningSetDefaultCombine = { "display_name": "Cond Pair Set Default Combine", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "positive_DEFAULT": { "name": "positive_DEFAULT" }, "negative_DEFAULT": { "name": "negative_DEFAULT" }, "hooks": { "name": "hooks" } }, "outputs": { "0": { "name": "positive" }, "1": { "name": "negative" } } };
const PairConditioningSetProperties = { "display_name": "Cond Pair Set Props", "inputs": { "positive_NEW": { "name": "positive_NEW" }, "negative_NEW": { "name": "negative_NEW" }, "strength": { "name": "strength" }, "set_cond_area": { "name": "set_cond_area" }, "mask": { "name": "mask" }, "hooks": { "name": "hooks" }, "timesteps": { "name": "timesteps" } }, "outputs": { "0": { "name": "positive" }, "1": { "name": "negative" } } };
const PairConditioningSetPropertiesAndCombine = { "display_name": "Cond Pair Set Props Combine", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "positive_NEW": { "name": "positive_NEW" }, "negative_NEW": { "name": "negative_NEW" }, "strength": { "name": "strength" }, "set_cond_area": { "name": "set_cond_area" }, "mask": { "name": "mask" }, "hooks": { "name": "hooks" }, "timesteps": { "name": "timesteps" } }, "outputs": { "0": { "name": "positive" }, "1": { "name": "negative" } } };
const PatchModelAddDownscale = { "display_name": "PatchModelAddDownscale (Kohya Deep Shrink)", "inputs": { "model": { "name": "model" }, "block_number": { "name": "block_number" }, "downscale_factor": { "name": "downscale_factor" }, "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" }, "downscale_after_skip": { "name": "downscale_after_skip" }, "downscale_method": { "name": "downscale_method" }, "upscale_method": { "name": "upscale_method" } }, "outputs": { "0": { "tooltip": null } } };
const PerpNeg = { "display_name": "Perp-Neg (DEPRECATED by PerpNegGuider)", "inputs": { "model": { "name": "model" }, "empty_conditioning": { "name": "empty_conditioning" }, "neg_scale": { "name": "neg_scale" } }, "outputs": { "0": { "tooltip": null } } };
const PerpNegGuider = { "display_name": "PerpNegGuider", "inputs": { "model": { "name": "model" }, "positive": { "name": "positive" }, "negative": { "name": "negative" }, "empty_conditioning": { "name": "empty_conditioning" }, "cfg": { "name": "cfg" }, "neg_scale": { "name": "neg_scale" } }, "outputs": { "0": { "tooltip": null } } };
const PerturbedAttentionGuidance = { "display_name": "PerturbedAttentionGuidance", "inputs": { "model": { "name": "model" }, "scale": { "name": "scale" } }, "outputs": { "0": { "tooltip": null } } };
const PhotoMakerEncode = { "display_name": "PhotoMakerEncode", "inputs": { "photomaker": { "name": "photomaker" }, "image": { "name": "image" }, "clip": { "name": "clip" }, "text": { "name": "text" } }, "outputs": { "0": { "tooltip": null } } };
const PhotoMakerLoader = { "display_name": "PhotoMakerLoader", "inputs": { "photomaker_model_name": { "name": "photomaker_model_name" } }, "outputs": { "0": { "tooltip": null } } };
const PixverseImageToVideoNode = { "display_name": "PixVerse Image to Video", "description": "Generates videos based on prompt and output_size.", "inputs": { "image": { "name": "image" }, "prompt": { "name": "prompt", "tooltip": "Prompt for the video generation" }, "quality": { "name": "quality" }, "duration_seconds": { "name": "duration_seconds" }, "motion_mode": { "name": "motion_mode" }, "seed": { "name": "seed", "tooltip": "Seed for video generation." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "An optional text description of undesired elements on an image." }, "pixverse_template": { "name": "pixverse_template", "tooltip": "An optional template to influence style of generation, created by the PixVerse Template node." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const PixverseTemplateNode = { "display_name": "PixVerse Template", "inputs": { "template": { "name": "template" } }, "outputs": { "0": { "name": "pixverse_template", "tooltip": null } } };
const PixverseTextToVideoNode = { "display_name": "PixVerse Text to Video", "description": "Generates videos based on prompt and output_size.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the video generation" }, "aspect_ratio": { "name": "aspect_ratio" }, "quality": { "name": "quality" }, "duration_seconds": { "name": "duration_seconds" }, "motion_mode": { "name": "motion_mode" }, "seed": { "name": "seed", "tooltip": "Seed for video generation." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "An optional text description of undesired elements on an image." }, "pixverse_template": { "name": "pixverse_template", "tooltip": "An optional template to influence style of generation, created by the PixVerse Template node." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const PixverseTransitionVideoNode = { "display_name": "PixVerse Transition Video", "description": "Generates videos based on prompt and output_size.", "inputs": { "first_frame": { "name": "first_frame" }, "last_frame": { "name": "last_frame" }, "prompt": { "name": "prompt", "tooltip": "Prompt for the video generation" }, "quality": { "name": "quality" }, "duration_seconds": { "name": "duration_seconds" }, "motion_mode": { "name": "motion_mode" }, "seed": { "name": "seed", "tooltip": "Seed for video generation." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "An optional text description of undesired elements on an image." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const PolyexponentialScheduler = { "display_name": "PolyexponentialScheduler", "inputs": { "steps": { "name": "steps" }, "sigma_max": { "name": "sigma_max" }, "sigma_min": { "name": "sigma_min" }, "rho": { "name": "rho" } }, "outputs": { "0": { "tooltip": null } } };
const PorterDuffImageComposite = { "display_name": "Porter-Duff Image Composite", "inputs": { "source": { "name": "source" }, "source_alpha": { "name": "source_alpha" }, "destination": { "name": "destination" }, "destination_alpha": { "name": "destination_alpha" }, "mode": { "name": "mode" } }, "outputs": { "0": { "tooltip": null }, "1": { "tooltip": null } } };
const Preview3D = { "display_name": "Preview 3D & Animation", "inputs": { "model_file": { "name": "model_file" }, "camera_info": { "name": "camera_info" }, "bg_image": { "name": "bg_image" }, "image": { "name": "image" } } };
const PreviewAny = { "display_name": "Preview as Text", "inputs": { "source": { "name": "source" }, "preview": {}, "previewMode": {} } };
const PreviewAudio = { "display_name": "Preview Audio", "inputs": { "audio": { "name": "audio" }, "audioUI": { "name": "audioUI" } } };
const PreviewImage = { "display_name": "Preview Image", "description": "Saves the input images to your ComfyUI output directory.", "inputs": { "images": { "name": "images" } } };
const PrimitiveBoolean = { "display_name": "Boolean", "inputs": { "value": { "name": "value" } }, "outputs": { "0": { "tooltip": null } } };
const PrimitiveFloat = { "display_name": "Float", "inputs": { "value": { "name": "value" } }, "outputs": { "0": { "tooltip": null } } };
const PrimitiveInt = { "display_name": "Int", "inputs": { "value": { "name": "value" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const PrimitiveString = { "display_name": "String", "inputs": { "value": { "name": "value" } }, "outputs": { "0": { "tooltip": null } } };
const PrimitiveStringMultiline = { "display_name": "String (Multiline)", "inputs": { "value": { "name": "value" } }, "outputs": { "0": { "tooltip": null } } };
const QuadrupleCLIPLoader = { "display_name": "QuadrupleCLIPLoader", "description": "[Recipes]\n\nhidream: long clip-l, long clip-g, t5xxl, llama_8b_3.1_instruct", "inputs": { "clip_name1": { "name": "clip_name1" }, "clip_name2": { "name": "clip_name2" }, "clip_name3": { "name": "clip_name3" }, "clip_name4": { "name": "clip_name4" } }, "outputs": { "0": { "tooltip": null } } };
const QwenImageDiffsynthControlnet = { "display_name": "QwenImageDiffsynthControlnet", "inputs": { "model": { "name": "model" }, "model_patch": { "name": "model_patch" }, "vae": { "name": "vae" }, "image": { "name": "image" }, "strength": { "name": "strength" }, "mask": { "name": "mask" } } };
const RandomCropImages = { "display_name": "Random Crop Images", "inputs": { "images": { "name": "images", "tooltip": "Image to process." }, "width": { "name": "width", "tooltip": "Crop width." }, "height": { "name": "height", "tooltip": "Crop height." }, "seed": { "name": "seed", "tooltip": "Random seed." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const RandomNoise = { "display_name": "RandomNoise", "inputs": { "noise_seed": { "name": "noise_seed" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const RebatchImages = { "display_name": "Rebatch Images", "inputs": { "images": { "name": "images" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const RebatchLatents = { "display_name": "Rebatch Latents", "inputs": { "latents": { "name": "latents" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "tooltip": null } } };
const RecordAudio = { "display_name": "Record Audio", "inputs": { "audio": { "name": "audio" } }, "outputs": { "0": { "tooltip": null } } };
const RecraftColorRGB = { "display_name": "Recraft Color RGB", "description": "Create Recraft Color by choosing specific RGB values.", "inputs": { "r": { "name": "r", "tooltip": "Red value of color." }, "g": { "name": "g", "tooltip": "Green value of color." }, "b": { "name": "b", "tooltip": "Blue value of color." }, "recraft_color": { "name": "recraft_color" } }, "outputs": { "0": { "name": "recraft_color", "tooltip": null } } };
const RecraftControls = { "display_name": "Recraft Controls", "description": "Create Recraft Controls for customizing Recraft generation.", "inputs": { "colors": { "name": "colors" }, "background_color": { "name": "background_color" } }, "outputs": { "0": { "name": "recraft_controls", "tooltip": null } } };
const RecraftCreativeUpscaleNode = { "display_name": "Recraft Creative Upscale Image", "description": "Upscale image synchronously.\nEnhances a given raster image using ‘creative upscale’ tool, boosting resolution with a focus on refining small details and faces.", "inputs": { "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null } } };
const RecraftCrispUpscaleNode = { "display_name": "Recraft Crisp Upscale Image", "description": "Upscale image synchronously.\nEnhances a given raster image using ‘crisp upscale’ tool, increasing image resolution, making the image sharper and cleaner.", "inputs": { "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null } } };
const RecraftImageInpaintingNode = { "display_name": "Recraft Image Inpainting", "description": "Modify image based on prompt and mask.", "inputs": { "image": { "name": "image" }, "mask": { "name": "mask" }, "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation." }, "n": { "name": "n", "tooltip": "The number of images to generate." }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "recraft_style": { "name": "recraft_style" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "An optional text description of undesired elements on an image." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const RecraftImageToImageNode = { "display_name": "Recraft Image to Image", "description": "Modify image based on prompt and strength.", "inputs": { "image": { "name": "image" }, "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation." }, "n": { "name": "n", "tooltip": "The number of images to generate." }, "strength": { "name": "strength", "tooltip": "Defines the difference with the original image, should lie in [0, 1], where 0 means almost identical, and 1 means miserable similarity." }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "recraft_style": { "name": "recraft_style" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "An optional text description of undesired elements on an image." }, "recraft_controls": { "name": "recraft_controls", "tooltip": "Optional additional controls over the generation via the Recraft Controls node." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const RecraftRemoveBackgroundNode = { "display_name": "Recraft Remove Background", "description": "Remove background from image, and return processed image and mask.", "inputs": { "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null }, "1": { "tooltip": null } } };
const RecraftReplaceBackgroundNode = { "display_name": "Recraft Replace Background", "description": "Replace background on image, based on provided prompt.", "inputs": { "image": { "name": "image" }, "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation." }, "n": { "name": "n", "tooltip": "The number of images to generate." }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "recraft_style": { "name": "recraft_style" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "An optional text description of undesired elements on an image." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const RecraftStyleV3DigitalIllustration = { "display_name": "Recraft Style - Digital Illustration", "description": "Select realistic_image style and optional substyle.", "inputs": { "substyle": { "name": "substyle" } }, "outputs": { "0": { "name": "recraft_style", "tooltip": null } } };
const RecraftStyleV3InfiniteStyleLibrary = { "display_name": "Recraft Style - Infinite Style Library", "description": "Select style based on preexisting UUID from Recraft's Infinite Style Library.", "inputs": { "style_id": { "name": "style_id", "tooltip": "UUID of style from Infinite Style Library." } }, "outputs": { "0": { "name": "recraft_style", "tooltip": null } } };
const RecraftStyleV3LogoRaster = { "display_name": "Recraft Style - Logo Raster", "description": "Select realistic_image style and optional substyle.", "inputs": { "substyle": { "name": "substyle" } }, "outputs": { "0": { "name": "recraft_style", "tooltip": null } } };
const RecraftStyleV3RealisticImage = { "display_name": "Recraft Style - Realistic Image", "description": "Select realistic_image style and optional substyle.", "inputs": { "substyle": { "name": "substyle" } }, "outputs": { "0": { "name": "recraft_style", "tooltip": null } } };
const RecraftTextToImageNode = { "display_name": "Recraft Text to Image", "description": "Generates images synchronously based on prompt and resolution.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation." }, "size": { "name": "size", "tooltip": "The size of the generated image." }, "n": { "name": "n", "tooltip": "The number of images to generate." }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "recraft_style": { "name": "recraft_style" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "An optional text description of undesired elements on an image." }, "recraft_controls": { "name": "recraft_controls", "tooltip": "Optional additional controls over the generation via the Recraft Controls node." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const RecraftTextToVectorNode = { "display_name": "Recraft Text to Vector", "description": "Generates SVG synchronously based on prompt and resolution.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Prompt for the image generation." }, "substyle": { "name": "substyle" }, "size": { "name": "size", "tooltip": "The size of the generated image." }, "n": { "name": "n", "tooltip": "The number of images to generate." }, "seed": { "name": "seed", "tooltip": "Seed to determine if node should re-run; actual results are nondeterministic regardless of seed." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "An optional text description of undesired elements on an image." }, "recraft_controls": { "name": "recraft_controls", "tooltip": "Optional additional controls over the generation via the Recraft Controls node." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const RecraftVectorizeImageNode = { "display_name": "Recraft Vectorize Image", "description": "Generates SVG synchronously from an input image.", "inputs": { "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null } } };
const ReferenceLatent = { "display_name": "ReferenceLatent", "description": "This node sets the guiding latent for an edit model. If the model supports it you can chain multiple to set multiple reference images.", "inputs": { "conditioning": { "name": "conditioning" }, "latent": { "name": "latent" } }, "outputs": { "0": { "tooltip": null } } };
const RegexExtract = { "display_name": "Regex Extract", "inputs": { "string": { "name": "string" }, "regex_pattern": { "name": "regex_pattern" }, "mode": { "name": "mode" }, "case_insensitive": { "name": "case_insensitive" }, "multiline": { "name": "multiline" }, "dotall": { "name": "dotall" }, "group_index": { "name": "group_index" } }, "outputs": { "0": { "tooltip": null } } };
const RegexMatch = { "display_name": "Regex Match", "inputs": { "string": { "name": "string" }, "regex_pattern": { "name": "regex_pattern" }, "case_insensitive": { "name": "case_insensitive" }, "multiline": { "name": "multiline" }, "dotall": { "name": "dotall" } }, "outputs": { "0": { "name": "matches", "tooltip": null } } };
const RegexReplace = { "display_name": "Regex Replace", "description": "Find and replace text using regex patterns.", "inputs": { "string": { "name": "string" }, "regex_pattern": { "name": "regex_pattern" }, "replace": { "name": "replace" }, "case_insensitive": { "name": "case_insensitive" }, "multiline": { "name": "multiline" }, "dotall": { "name": "dotall", "tooltip": "When enabled, the dot (.) character will match any character including newline characters. When disabled, dots won't match newlines." }, "count": { "name": "count", "tooltip": "Maximum number of replacements to make. Set to 0 to replace all occurrences (default). Set to 1 to replace only the first match, 2 for the first two matches, etc." } }, "outputs": { "0": { "tooltip": null } } };
const RenormCFG = { "display_name": "RenormCFG", "inputs": { "model": { "name": "model" }, "cfg_trunc": { "name": "cfg_trunc" }, "renorm_cfg": { "name": "renorm_cfg" } }, "outputs": { "0": { "tooltip": null } } };
const RepeatImageBatch = { "display_name": "RepeatImageBatch", "inputs": { "image": { "name": "image" }, "amount": { "name": "amount" } }, "outputs": { "0": { "tooltip": null } } };
const RepeatLatentBatch = { "display_name": "Repeat Latent Batch", "inputs": { "samples": { "name": "samples" }, "amount": { "name": "amount" } } };
const ReplaceText = { "display_name": "Replace Text", "inputs": { "texts": { "name": "texts", "tooltip": "Text to process." }, "find": { "name": "find", "tooltip": "Text to find." }, "replace": { "name": "replace", "tooltip": "Text to replace with." } }, "outputs": { "0": { "name": "texts", "tooltip": "Processed texts" } } };
const ReplaceVideoLatentFrames = { "display_name": "ReplaceVideoLatentFrames", "inputs": { "destination": { "name": "destination", "tooltip": "The destination latent where frames will be replaced." }, "index": { "name": "index", "tooltip": "The starting latent frame index in the destination latent where the source latent frames will be placed. Negative values count from the end." }, "source": { "name": "source", "tooltip": "The source latent providing frames to insert into the destination latent. If not provided, the destination latent is returned unchanged." } }, "outputs": { "0": { "tooltip": null } } };
const RescaleCFG = { "display_name": "RescaleCFG", "inputs": { "model": { "name": "model" }, "multiplier": { "name": "multiplier" } } };
const ResizeAndPadImage = { "display_name": "ResizeAndPadImage", "inputs": { "image": { "name": "image" }, "target_width": { "name": "target_width" }, "target_height": { "name": "target_height" }, "padding_color": { "name": "padding_color" }, "interpolation": { "name": "interpolation" } }, "outputs": { "0": { "tooltip": null } } };
const ResizeImagesByLongerEdge = { "display_name": "Resize Images by Longer Edge", "inputs": { "images": { "name": "images", "tooltip": "Image to process." }, "longer_edge": { "name": "longer_edge", "tooltip": "Target length for the longer edge." } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const ResizeImagesByShorterEdge = { "display_name": "Resize Images by Shorter Edge", "inputs": { "images": { "name": "images", "tooltip": "Image to process." }, "shorter_edge": { "name": "shorter_edge", "tooltip": "Target length for the shorter edge." } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const ResolutionBucket = { "display_name": "Resolution Bucket", "inputs": { "latents": { "name": "latents", "tooltip": "List of latent dicts to bucket by resolution." }, "conditioning": { "name": "conditioning", "tooltip": "List of conditioning lists (must match latents length)." } }, "outputs": { "0": { "name": "latents", "tooltip": "List of batched latent dicts, one per resolution bucket." }, "1": { "name": "conditioning", "tooltip": "List of condition lists, one per resolution bucket." } } };
const Rodin3D_Detail = { "display_name": "Rodin 3D Generate - Detail Generate", "description": "Generate 3D Assets using Rodin API", "inputs": { "Images": { "name": "Images" }, "Seed": { "name": "Seed" }, "Material_Type": { "name": "Material_Type" }, "Polygon_count": { "name": "Polygon_count" } }, "outputs": { "0": { "name": "3D Model Path", "tooltip": null } } };
const Rodin3D_Gen2 = { "display_name": "Rodin 3D Generate - Gen-2 Generate", "description": "Generate 3D Assets using Rodin API", "inputs": { "Images": { "name": "Images" }, "TAPose": { "name": "TAPose" }, "Seed": { "name": "Seed" }, "Material_Type": { "name": "Material_Type" }, "Polygon_count": { "name": "Polygon_count" } }, "outputs": { "0": { "name": "3D Model Path", "tooltip": null } } };
const Rodin3D_Regular = { "display_name": "Rodin 3D Generate - Regular Generate", "description": "Generate 3D Assets using Rodin API", "inputs": { "Images": { "name": "Images" }, "Seed": { "name": "Seed" }, "Material_Type": { "name": "Material_Type" }, "Polygon_count": { "name": "Polygon_count" } }, "outputs": { "0": { "name": "3D Model Path", "tooltip": null } } };
const Rodin3D_Sketch = { "display_name": "Rodin 3D Generate - Sketch Generate", "description": "Generate 3D Assets using Rodin API", "inputs": { "Images": { "name": "Images" }, "Seed": { "name": "Seed" } }, "outputs": { "0": { "name": "3D Model Path", "tooltip": null } } };
const Rodin3D_Smooth = { "display_name": "Rodin 3D Generate - Smooth Generate", "description": "Generate 3D Assets using Rodin API", "inputs": { "Images": { "name": "Images" }, "Seed": { "name": "Seed" }, "Material_Type": { "name": "Material_Type" }, "Polygon_count": { "name": "Polygon_count" } }, "outputs": { "0": { "name": "3D Model Path", "tooltip": null } } };
const RunwayFirstLastFrameNode = { "display_name": "Runway First-Last-Frame to Video", "description": "Upload first and last keyframes, draft a prompt, and generate a video. More complex transitions, such as cases where the Last frame is completely different from the First frame, may benefit from the longer 10s duration. This would give the generation more time to smoothly transition between the two inputs. Before diving in, review these best practices to ensure that your input selections will set your generation up for success: https://help.runwayml.com/hc/en-us/articles/34170748696595-Creating-with-Keyframes-on-Gen-3.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text prompt for the generation" }, "start_frame": { "name": "start_frame", "tooltip": "Start frame to be used for the video" }, "end_frame": { "name": "end_frame", "tooltip": "End frame to be used for the video. Supported for gen3a_turbo only." }, "duration": { "name": "duration" }, "ratio": { "name": "ratio" }, "seed": { "name": "seed", "tooltip": "Random seed for generation" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const RunwayImageToVideoNodeGen3a = { "display_name": "Runway Image to Video (Gen3a Turbo)", "description": "Generate a video from a single starting frame using Gen3a Turbo model. Before diving in, review these best practices to ensure that your input selections will set your generation up for success: https://help.runwayml.com/hc/en-us/articles/33927968552339-Creating-with-Act-One-on-Gen-3-Alpha-and-Turbo.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text prompt for the generation" }, "start_frame": { "name": "start_frame", "tooltip": "Start frame to be used for the video" }, "duration": { "name": "duration" }, "ratio": { "name": "ratio" }, "seed": { "name": "seed", "tooltip": "Random seed for generation" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const RunwayImageToVideoNodeGen4 = { "display_name": "Runway Image to Video (Gen4 Turbo)", "description": "Generate a video from a single starting frame using Gen4 Turbo model. Before diving in, review these best practices to ensure that your input selections will set your generation up for success: https://help.runwayml.com/hc/en-us/articles/37327109429011-Creating-with-Gen-4-Video.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text prompt for the generation" }, "start_frame": { "name": "start_frame", "tooltip": "Start frame to be used for the video" }, "duration": { "name": "duration" }, "ratio": { "name": "ratio" }, "seed": { "name": "seed", "tooltip": "Random seed for generation" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const RunwayTextToImageNode = { "display_name": "Runway Text to Image", "description": "Generate an image from a text prompt using Runway's Gen 4 model. You can also include reference image to guide the generation.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text prompt for the generation" }, "ratio": { "name": "ratio" }, "reference_image": { "name": "reference_image", "tooltip": "Optional reference image to guide the generation" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerCustom = { "display_name": "SamplerCustom", "inputs": { "model": { "name": "model" }, "add_noise": { "name": "add_noise" }, "noise_seed": { "name": "noise_seed" }, "cfg": { "name": "cfg" }, "positive": { "name": "positive" }, "negative": { "name": "negative" }, "sampler": { "name": "sampler" }, "sigmas": { "name": "sigmas" }, "latent_image": { "name": "latent_image" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "name": "output", "tooltip": null }, "1": { "name": "denoised_output", "tooltip": null } } };
const SamplerCustomAdvanced = { "display_name": "SamplerCustomAdvanced", "inputs": { "noise": { "name": "noise" }, "guider": { "name": "guider" }, "sampler": { "name": "sampler" }, "sigmas": { "name": "sigmas" }, "latent_image": { "name": "latent_image" } }, "outputs": { "0": { "name": "output", "tooltip": null }, "1": { "name": "denoised_output", "tooltip": null } } };
const SamplerDPMAdaptative = { "display_name": "SamplerDPMAdaptative", "inputs": { "order": { "name": "order" }, "rtol": { "name": "rtol" }, "atol": { "name": "atol" }, "h_init": { "name": "h_init" }, "pcoeff": { "name": "pcoeff" }, "icoeff": { "name": "icoeff" }, "dcoeff": { "name": "dcoeff" }, "accept_safety": { "name": "accept_safety" }, "eta": { "name": "eta" }, "s_noise": { "name": "s_noise" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerDPMPP_2M_SDE = { "display_name": "SamplerDPMPP_2M_SDE", "inputs": { "solver_type": { "name": "solver_type" }, "eta": { "name": "eta" }, "s_noise": { "name": "s_noise" }, "noise_device": { "name": "noise_device" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerDPMPP_2S_Ancestral = { "display_name": "SamplerDPMPP_2S_Ancestral", "inputs": { "eta": { "name": "eta" }, "s_noise": { "name": "s_noise" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerDPMPP_3M_SDE = { "display_name": "SamplerDPMPP_3M_SDE", "inputs": { "eta": { "name": "eta" }, "s_noise": { "name": "s_noise" }, "noise_device": { "name": "noise_device" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerDPMPP_SDE = { "display_name": "SamplerDPMPP_SDE", "inputs": { "eta": { "name": "eta" }, "s_noise": { "name": "s_noise" }, "r": { "name": "r" }, "noise_device": { "name": "noise_device" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerER_SDE = { "display_name": "SamplerER_SDE", "inputs": { "solver_type": { "name": "solver_type" }, "max_stage": { "name": "max_stage" }, "eta": { "name": "eta", "tooltip": "Stochastic strength of reverse-time SDE.\nWhen eta=0, it reduces to deterministic ODE. This setting doesn't apply to ER-SDE solver type." }, "s_noise": { "name": "s_noise" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerEulerAncestral = { "display_name": "SamplerEulerAncestral", "inputs": { "eta": { "name": "eta" }, "s_noise": { "name": "s_noise" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerEulerAncestralCFGPP = { "display_name": "SamplerEulerAncestralCFG++", "inputs": { "eta": { "name": "eta" }, "s_noise": { "name": "s_noise" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerEulerCFGpp = { "display_name": "SamplerEulerCFG++", "inputs": { "version": { "name": "version" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerLCMUpscale = { "display_name": "SamplerLCMUpscale", "inputs": { "scale_ratio": { "name": "scale_ratio" }, "scale_steps": { "name": "scale_steps" }, "upscale_method": { "name": "upscale_method" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerLMS = { "display_name": "SamplerLMS", "inputs": { "order": { "name": "order" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerSASolver = { "display_name": "SamplerSASolver", "inputs": { "model": { "name": "model" }, "eta": { "name": "eta" }, "sde_start_percent": { "name": "sde_start_percent" }, "sde_end_percent": { "name": "sde_end_percent" }, "s_noise": { "name": "s_noise" }, "predictor_order": { "name": "predictor_order" }, "corrector_order": { "name": "corrector_order" }, "use_pece": { "name": "use_pece" }, "simple_order_2": { "name": "simple_order_2" } }, "outputs": { "0": { "tooltip": null } } };
const SamplerSEEDS2 = { "display_name": "SamplerSEEDS2", "description": "This sampler node can represent multiple samplers:\n\nseeds_2\n- default setting\n\nexp_heun_2_x0\n- solver_type=phi_2, r=1.0, eta=0.0\n\nexp_heun_2_x0_sde\n- solver_type=phi_2, r=1.0, eta=1.0, s_noise=1.0", "inputs": { "solver_type": { "name": "solver_type" }, "eta": { "name": "eta", "tooltip": "Stochastic strength" }, "s_noise": { "name": "s_noise", "tooltip": "SDE noise multiplier" }, "r": { "name": "r", "tooltip": "Relative step size for the intermediate stage (c2 node)" } }, "outputs": { "0": { "tooltip": null } } };
const SamplingPercentToSigma = { "display_name": "SamplingPercentToSigma", "inputs": { "model": { "name": "model" }, "sampling_percent": { "name": "sampling_percent" }, "return_actual_sigma": { "name": "return_actual_sigma", "tooltip": "Return the actual sigma value instead of the value used for interval checks.\nThis only affects results at 0.0 and 1.0." } }, "outputs": { "0": { "name": "sigma_value", "tooltip": null } } };
const SaveAnimatedPNG = { "display_name": "SaveAnimatedPNG", "inputs": { "images": { "name": "images" }, "filename_prefix": { "name": "filename_prefix" }, "fps": { "name": "fps" }, "compress_level": { "name": "compress_level" } } };
const SaveAnimatedWEBP = { "display_name": "SaveAnimatedWEBP", "inputs": { "images": { "name": "images" }, "filename_prefix": { "name": "filename_prefix" }, "fps": { "name": "fps" }, "lossless": { "name": "lossless" }, "quality": { "name": "quality" }, "method": { "name": "method" } } };
const SaveAudio = { "display_name": "Save Audio (FLAC)", "inputs": { "audio": { "name": "audio" }, "filename_prefix": { "name": "filename_prefix" }, "audioUI": { "name": "audioUI" } } };
const SaveAudioMP3 = { "display_name": "Save Audio (MP3)", "inputs": { "audio": { "name": "audio" }, "filename_prefix": { "name": "filename_prefix" }, "quality": { "name": "quality" }, "audioUI": { "name": "audioUI" } } };
const SaveAudioOpus = { "display_name": "Save Audio (Opus)", "inputs": { "audio": { "name": "audio" }, "filename_prefix": { "name": "filename_prefix" }, "quality": { "name": "quality" }, "audioUI": { "name": "audioUI" } } };
const SaveGLB = { "display_name": "SaveGLB", "inputs": { "mesh": { "name": "mesh" }, "filename_prefix": { "name": "filename_prefix" }, "image": { "name": "image" } } };
const SaveImage = { "display_name": "Save Image", "description": "Saves the input images to your ComfyUI output directory.", "inputs": { "images": { "name": "images", "tooltip": "The images to save." }, "filename_prefix": { "name": "filename_prefix", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes." } } };
const SaveImageDataSetToFolder = { "display_name": "Save Image Dataset to Folder", "inputs": { "images": { "name": "images", "tooltip": "List of images to save." }, "folder_name": { "name": "folder_name", "tooltip": "Name of the folder to save images to (inside output directory)." }, "filename_prefix": { "name": "filename_prefix", "tooltip": "Prefix for saved image filenames." } } };
const SaveImageTextDataSetToFolder = { "display_name": "Save Image and Text Dataset to Folder", "inputs": { "images": { "name": "images", "tooltip": "List of images to save." }, "texts": { "name": "texts", "tooltip": "List of text captions to save." }, "folder_name": { "name": "folder_name", "tooltip": "Name of the folder to save images to (inside output directory)." }, "filename_prefix": { "name": "filename_prefix", "tooltip": "Prefix for saved image filenames." } } };
const SaveImageWebsocket = { "display_name": "SaveImageWebsocket", "inputs": { "images": { "name": "images" } } };
const SaveLatent = { "display_name": "SaveLatent", "inputs": { "samples": { "name": "samples" }, "filename_prefix": { "name": "filename_prefix" } } };
const SaveLoRA = { "display_name": "Save LoRA Weights", "inputs": { "lora": { "name": "lora", "tooltip": "The LoRA model to save. Do not use the model with LoRA layers." }, "prefix": { "name": "prefix", "tooltip": "The prefix to use for the saved LoRA file." }, "steps": { "name": "steps", "tooltip": "Optional: The number of steps to LoRA has been trained for, used to name the saved file." } } };
const SaveSVGNode = { "display_name": "SaveSVGNode", "description": "Save SVG files on disk.", "inputs": { "svg": { "name": "svg" }, "filename_prefix": { "name": "filename_prefix", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes." } } };
const SaveTrainingDataset = { "display_name": "Save Training Dataset", "inputs": { "latents": { "name": "latents", "tooltip": "List of latent dicts from MakeTrainingDataset." }, "conditioning": { "name": "conditioning", "tooltip": "List of conditioning lists from MakeTrainingDataset." }, "folder_name": { "name": "folder_name", "tooltip": "Name of folder to save dataset (inside output directory)." }, "shard_size": { "name": "shard_size", "tooltip": "Number of samples per shard file." } } };
const SaveVideo = { "display_name": "Save Video", "description": "Saves the input images to your ComfyUI output directory.", "inputs": { "video": { "name": "video", "tooltip": "The video to save." }, "filename_prefix": { "name": "filename_prefix", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes." }, "format": { "name": "format", "tooltip": "The format to save the video as." }, "codec": { "name": "codec", "tooltip": "The codec to use for the video." } } };
const SaveWEBM = { "display_name": "SaveWEBM", "inputs": { "images": { "name": "images" }, "filename_prefix": { "name": "filename_prefix" }, "codec": { "name": "codec" }, "fps": { "name": "fps" }, "crf": { "name": "crf", "tooltip": "Higher crf means lower quality with a smaller file size, lower crf means higher quality higher filesize." } } };
const ScaleROPE = { "display_name": "ScaleROPE", "description": "Scale and shift the ROPE of the model.", "inputs": { "model": { "name": "model" }, "scale_x": { "name": "scale_x" }, "shift_x": { "name": "shift_x" }, "scale_y": { "name": "scale_y" }, "shift_y": { "name": "shift_y" }, "scale_t": { "name": "scale_t" }, "shift_t": { "name": "shift_t" } }, "outputs": { "0": { "tooltip": null } } };
const SD_4XUpscale_Conditioning = { "display_name": "SD_4XUpscale_Conditioning", "inputs": { "images": { "name": "images" }, "positive": { "name": "positive" }, "negative": { "name": "negative" }, "scale_ratio": { "name": "scale_ratio" }, "noise_augmentation": { "name": "noise_augmentation" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const SDTurboScheduler = { "display_name": "SDTurboScheduler", "inputs": { "model": { "name": "model" }, "steps": { "name": "steps" }, "denoise": { "name": "denoise" } }, "outputs": { "0": { "tooltip": null } } };
const SelfAttentionGuidance = { "display_name": "Self-Attention Guidance", "inputs": { "model": { "name": "model" }, "scale": { "name": "scale" }, "blur_sigma": { "name": "blur_sigma" } }, "outputs": { "0": { "tooltip": null } } };
const SetClipHooks = { "display_name": "Set CLIP Hooks", "inputs": { "clip": { "name": "clip" }, "apply_to_conds": { "name": "apply_to_conds" }, "schedule_clip": { "name": "schedule_clip" }, "hooks": { "name": "hooks" } } };
const SetFirstSigma = { "display_name": "SetFirstSigma", "inputs": { "sigmas": { "name": "sigmas" }, "sigma": { "name": "sigma" } }, "outputs": { "0": { "tooltip": null } } };
const SetHookKeyframes = { "display_name": "Set Hook Keyframes", "inputs": { "hooks": { "name": "hooks" }, "hook_kf": { "name": "hook_kf" } } };
const SetLatentNoiseMask = { "display_name": "Set Latent Noise Mask", "inputs": { "samples": { "name": "samples" }, "mask": { "name": "mask" } } };
const SetUnionControlNetType = { "display_name": "SetUnionControlNetType", "inputs": { "control_net": { "name": "control_net" }, "type": { "name": "type" } }, "outputs": { "0": { "tooltip": null } } };
const ShuffleDataset = { "display_name": "Shuffle Image Dataset", "inputs": { "images": { "name": "images", "tooltip": "List of images to process." }, "seed": { "name": "seed", "tooltip": "Random seed." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "name": "images", "tooltip": "Processed images" } } };
const ShuffleImageTextDataset = { "display_name": "Shuffle Image-Text Dataset", "inputs": { "images": { "name": "images", "tooltip": "List of images to shuffle." }, "texts": { "name": "texts", "tooltip": "List of texts to shuffle." }, "seed": { "name": "seed", "tooltip": "Random seed." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "name": "images", "tooltip": "Shuffled images" }, "1": { "name": "texts", "tooltip": "Shuffled texts" } } };
const SkipLayerGuidanceDiT = { "display_name": "SkipLayerGuidanceDiT", "description": "Generic version of SkipLayerGuidance node that can be used on every DiT model.", "inputs": { "model": { "name": "model" }, "double_layers": { "name": "double_layers" }, "single_layers": { "name": "single_layers" }, "scale": { "name": "scale" }, "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" }, "rescaling_scale": { "name": "rescaling_scale" } }, "outputs": { "0": { "tooltip": null } } };
const SkipLayerGuidanceDiTSimple = { "display_name": "SkipLayerGuidanceDiTSimple", "description": "Simple version of the SkipLayerGuidanceDiT node that only modifies the uncond pass.", "inputs": { "model": { "name": "model" }, "double_layers": { "name": "double_layers" }, "single_layers": { "name": "single_layers" }, "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" } }, "outputs": { "0": { "tooltip": null } } };
const SkipLayerGuidanceSD3 = { "display_name": "SkipLayerGuidanceSD3", "description": "Generic version of SkipLayerGuidance node that can be used on every DiT model.", "inputs": { "model": { "name": "model" }, "layers": { "name": "layers" }, "scale": { "name": "scale" }, "start_percent": { "name": "start_percent" }, "end_percent": { "name": "end_percent" } }, "outputs": { "0": { "tooltip": null } } };
const SolidMask = { "display_name": "SolidMask", "inputs": { "value": { "name": "value" }, "width": { "name": "width" }, "height": { "name": "height" } }, "outputs": { "0": { "tooltip": null } } };
const SplitAudioChannels = { "display_name": "Split Audio Channels", "description": "Separates the audio into left and right channels.", "inputs": { "audio": { "name": "audio" } }, "outputs": { "0": { "name": "left", "tooltip": null }, "1": { "name": "right", "tooltip": null } } };
const SplitImageWithAlpha = { "display_name": "Split Image with Alpha", "inputs": { "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null }, "1": { "tooltip": null } } };
const SplitSigmas = { "display_name": "SplitSigmas", "inputs": { "sigmas": { "name": "sigmas" }, "step": { "name": "step" } }, "outputs": { "0": { "name": "high_sigmas", "tooltip": null }, "1": { "name": "low_sigmas", "tooltip": null } } };
const SplitSigmasDenoise = { "display_name": "SplitSigmasDenoise", "inputs": { "sigmas": { "name": "sigmas" }, "denoise": { "name": "denoise" } }, "outputs": { "0": { "name": "high_sigmas", "tooltip": null }, "1": { "name": "low_sigmas", "tooltip": null } } };
const StabilityAudioInpaint = { "display_name": "Stability AI Audio Inpaint", "description": "Transforms part of existing audio sample using text instructions.", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt" }, "audio": { "name": "audio", "tooltip": "Audio must be between 6 and 190 seconds long." }, "duration": { "name": "duration", "tooltip": "Controls the duration in seconds of the generated audio." }, "seed": { "name": "seed", "tooltip": "The random seed used for generation." }, "steps": { "name": "steps", "tooltip": "Controls the number of sampling steps." }, "mask_start": { "name": "mask_start" }, "mask_end": { "name": "mask_end" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const StabilityAudioToAudio = { "display_name": "Stability AI Audio To Audio", "description": "Transforms existing audio samples into new high-quality compositions using text instructions.", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt" }, "audio": { "name": "audio", "tooltip": "Audio must be between 6 and 190 seconds long." }, "duration": { "name": "duration", "tooltip": "Controls the duration in seconds of the generated audio." }, "seed": { "name": "seed", "tooltip": "The random seed used for generation." }, "steps": { "name": "steps", "tooltip": "Controls the number of sampling steps." }, "strength": { "name": "strength", "tooltip": "Parameter controls how much influence the audio parameter has on the generated audio." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const StabilityStableImageSD_3_5Node = { "display_name": "Stability AI Stable Diffusion 3.5 Image", "description": "Generates images synchronously based on prompt and resolution.", "inputs": { "prompt": { "name": "prompt", "tooltip": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines elements, colors, and subjects will lead to better results." }, "model": { "name": "model" }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "Aspect ratio of generated image." }, "style_preset": { "name": "style_preset", "tooltip": "Optional desired style of generated image." }, "cfg_scale": { "name": "cfg_scale", "tooltip": "How strictly the diffusion process adheres to the prompt text (higher values keep your image closer to your prompt)" }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "image": { "name": "image" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Keywords of what you do not wish to see in the output image. This is an advanced feature." }, "image_denoise": { "name": "image_denoise", "tooltip": "Denoise of input image; 0.0 yields image identical to input, 1.0 is as if no image was provided at all." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const StabilityStableImageUltraNode = { "display_name": "Stability AI Stable Image Ultra", "description": "Generates images synchronously based on prompt and resolution.", "inputs": { "prompt": { "name": "prompt", "tooltip": "What you wish to see in the output image. A strong, descriptive prompt that clearly defineselements, colors, and subjects will lead to better results. To control the weight of a given word use the format `(word:weight)`,where `word` is the word you'd like to control the weight of and `weight`is a value between 0 and 1. For example: `The sky was a crisp (blue:0.3) and (green:0.8)`would convey a sky that was blue and green, but more green than blue." }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "Aspect ratio of generated image." }, "style_preset": { "name": "style_preset", "tooltip": "Optional desired style of generated image." }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "image": { "name": "image" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "A blurb of text describing what you do not wish to see in the output image. This is an advanced feature." }, "image_denoise": { "name": "image_denoise", "tooltip": "Denoise of input image; 0.0 yields image identical to input, 1.0 is as if no image was provided at all." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const StabilityTextToAudio = { "display_name": "Stability AI Text To Audio", "description": "Generates high-quality music and sound effects from text descriptions.", "inputs": { "model": { "name": "model" }, "prompt": { "name": "prompt" }, "duration": { "name": "duration", "tooltip": "Controls the duration in seconds of the generated audio." }, "seed": { "name": "seed", "tooltip": "The random seed used for generation." }, "steps": { "name": "steps", "tooltip": "Controls the number of sampling steps." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const StabilityUpscaleConservativeNode = { "display_name": "Stability AI Upscale Conservative", "description": "Upscale image with minimal alterations to 4K resolution.", "inputs": { "image": { "name": "image" }, "prompt": { "name": "prompt", "tooltip": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines elements, colors, and subjects will lead to better results." }, "creativity": { "name": "creativity", "tooltip": "Controls the likelihood of creating additional details not heavily conditioned by the init image." }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Keywords of what you do not wish to see in the output image. This is an advanced feature." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const StabilityUpscaleCreativeNode = { "display_name": "Stability AI Upscale Creative", "description": "Upscale image with minimal alterations to 4K resolution.", "inputs": { "image": { "name": "image" }, "prompt": { "name": "prompt", "tooltip": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines elements, colors, and subjects will lead to better results." }, "creativity": { "name": "creativity", "tooltip": "Controls the likelihood of creating additional details not heavily conditioned by the init image." }, "style_preset": { "name": "style_preset", "tooltip": "Optional desired style of generated image." }, "seed": { "name": "seed", "tooltip": "The random seed used for creating the noise." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Keywords of what you do not wish to see in the output image. This is an advanced feature." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const StabilityUpscaleFastNode = { "display_name": "Stability AI Upscale Fast", "description": "Quickly upscales an image via Stability API call to 4x its original size; intended for upscaling low-quality/compressed images.", "inputs": { "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null } } };
const StableCascade_EmptyLatentImage = { "display_name": "StableCascade_EmptyLatentImage", "inputs": { "width": { "name": "width" }, "height": { "name": "height" }, "compression": { "name": "compression" }, "batch_size": { "name": "batch_size" } }, "outputs": { "0": { "name": "stage_c", "tooltip": null }, "1": { "name": "stage_b", "tooltip": null } } };
const StableCascade_StageB_Conditioning = { "display_name": "StableCascade_StageB_Conditioning", "inputs": { "conditioning": { "name": "conditioning" }, "stage_c": { "name": "stage_c" } }, "outputs": { "0": { "tooltip": null } } };
const StableCascade_StageC_VAEEncode = { "display_name": "StableCascade_StageC_VAEEncode", "inputs": { "image": { "name": "image" }, "vae": { "name": "vae" }, "compression": { "name": "compression" } }, "outputs": { "0": { "name": "stage_c", "tooltip": null }, "1": { "name": "stage_b", "tooltip": null } } };
const StableCascade_SuperResolutionControlnet = { "display_name": "StableCascade_SuperResolutionControlnet", "inputs": { "image": { "name": "image" }, "vae": { "name": "vae" } }, "outputs": { "0": { "name": "controlnet_input", "tooltip": null }, "1": { "name": "stage_c", "tooltip": null }, "2": { "name": "stage_b", "tooltip": null } } };
const StableZero123_Conditioning = { "display_name": "StableZero123_Conditioning", "inputs": { "clip_vision": { "name": "clip_vision" }, "init_image": { "name": "init_image" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "batch_size": { "name": "batch_size" }, "elevation": { "name": "elevation" }, "azimuth": { "name": "azimuth" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const StableZero123_Conditioning_Batched = { "display_name": "StableZero123_Conditioning_Batched", "inputs": { "clip_vision": { "name": "clip_vision" }, "init_image": { "name": "init_image" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "batch_size": { "name": "batch_size" }, "elevation": { "name": "elevation" }, "azimuth": { "name": "azimuth" }, "elevation_batch_increment": { "name": "elevation_batch_increment" }, "azimuth_batch_increment": { "name": "azimuth_batch_increment" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const StringCompare = { "display_name": "Compare", "inputs": { "string_a": { "name": "string_a" }, "string_b": { "name": "string_b" }, "mode": { "name": "mode" }, "case_sensitive": { "name": "case_sensitive" } }, "outputs": { "0": { "tooltip": null } } };
const StringConcatenate = { "display_name": "Concatenate", "inputs": { "string_a": { "name": "string_a" }, "string_b": { "name": "string_b" }, "delimiter": { "name": "delimiter" } }, "outputs": { "0": { "tooltip": null } } };
const StringContains = { "display_name": "Contains", "inputs": { "string": { "name": "string" }, "substring": { "name": "substring" }, "case_sensitive": { "name": "case_sensitive" } }, "outputs": { "0": { "name": "contains", "tooltip": null } } };
const StringLength = { "display_name": "Length", "inputs": { "string": { "name": "string" } }, "outputs": { "0": { "name": "length", "tooltip": null } } };
const StringReplace = { "display_name": "Replace", "inputs": { "string": { "name": "string" }, "find": { "name": "find" }, "replace": { "name": "replace" } }, "outputs": { "0": { "tooltip": null } } };
const StringSubstring = { "display_name": "Substring", "inputs": { "string": { "name": "string" }, "start": { "name": "start" }, "end": { "name": "end" } }, "outputs": { "0": { "tooltip": null } } };
const StringTrim = { "display_name": "Trim", "inputs": { "string": { "name": "string" }, "mode": { "name": "mode" } }, "outputs": { "0": { "tooltip": null } } };
const StripWhitespace = { "display_name": "Strip Whitespace", "inputs": { "texts": { "name": "texts", "tooltip": "Text to process." } }, "outputs": { "0": { "name": "texts", "tooltip": "Processed texts" } } };
const StyleModelApply = { "display_name": "Apply Style Model", "inputs": { "conditioning": { "name": "conditioning" }, "style_model": { "name": "style_model" }, "clip_vision_output": { "name": "clip_vision_output" }, "strength": { "name": "strength" }, "strength_type": { "name": "strength_type" } } };
const StyleModelLoader = { "display_name": "Load Style Model", "inputs": { "style_model_name": { "name": "style_model_name" } } };
const SV3D_Conditioning = { "display_name": "SV3D_Conditioning", "inputs": { "clip_vision": { "name": "clip_vision" }, "init_image": { "name": "init_image" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "video_frames": { "name": "video_frames" }, "elevation": { "name": "elevation" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const SVD_img2vid_Conditioning = { "display_name": "SVD_img2vid_Conditioning", "inputs": { "clip_vision": { "name": "clip_vision" }, "init_image": { "name": "init_image" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "video_frames": { "name": "video_frames" }, "motion_bucket_id": { "name": "motion_bucket_id" }, "fps": { "name": "fps" }, "augmentation_level": { "name": "augmentation_level" } }, "outputs": { "0": { "name": "positive" }, "1": { "name": "negative" }, "2": { "name": "latent" } } };
const T5TokenizerOptions = { "display_name": "T5TokenizerOptions", "inputs": { "clip": { "name": "clip" }, "min_padding": { "name": "min_padding" }, "min_length": { "name": "min_length" } }, "outputs": { "0": { "tooltip": null } } };
const TCFG = { "display_name": "Tangential Damping CFG", "description": "TCFG – Tangential Damping CFG (2503.18137)\n\nRefine the uncond (negative) to align with the cond (positive) for improving quality.", "inputs": { "model": { "name": "model" } }, "outputs": { "0": { "name": "patched_model", "tooltip": null } } };
const TemporalScoreRescaling = { "display_name": "TSR - Temporal Score Rescaling", "description": "[Post-CFG Function]\nTSR - Temporal Score Rescaling (2510.01184)\n\nRescaling the model's score or noise to steer the sampling diversity.\n", "inputs": { "model": { "name": "model" }, "tsr_k": { "name": "tsr_k", "tooltip": "Controls the rescaling strength.\nLower k produces more detailed results; higher k produces smoother results in image generation. Setting k = 1 disables rescaling." }, "tsr_sigma": { "name": "tsr_sigma", "tooltip": "Controls how early rescaling takes effect.\nLarger values take effect earlier." } }, "outputs": { "0": { "name": "patched_model", "tooltip": null } } };
const TextEncodeAceStepAudio = { "display_name": "TextEncodeAceStepAudio", "inputs": { "clip": { "name": "clip" }, "tags": { "name": "tags" }, "lyrics": { "name": "lyrics" }, "lyrics_strength": { "name": "lyrics_strength" } }, "outputs": { "0": { "tooltip": null } } };
const TextEncodeHunyuanVideo_ImageToVideo = { "display_name": "TextEncodeHunyuanVideo_ImageToVideo", "inputs": { "clip": { "name": "clip" }, "clip_vision_output": { "name": "clip_vision_output" }, "prompt": { "name": "prompt" }, "image_interleave": { "name": "image_interleave", "tooltip": "How much the image influences things vs the text prompt. Higher number means more influence from the text prompt." } }, "outputs": { "0": { "tooltip": null } } };
const TextEncodeQwenImageEdit = { "display_name": "TextEncodeQwenImageEdit", "inputs": { "clip": { "name": "clip" }, "prompt": { "name": "prompt" }, "vae": { "name": "vae" }, "image": { "name": "image" } }, "outputs": { "0": { "tooltip": null } } };
const TextEncodeQwenImageEditPlus = { "display_name": "TextEncodeQwenImageEditPlus", "inputs": { "clip": { "name": "clip" }, "prompt": { "name": "prompt" }, "vae": { "name": "vae" }, "image1": { "name": "image1" }, "image2": { "name": "image2" }, "image3": { "name": "image3" } }, "outputs": { "0": { "tooltip": null } } };
const TextToLowercase = { "display_name": "Text to Lowercase", "inputs": { "texts": { "name": "texts", "tooltip": "Text to process." } }, "outputs": { "0": { "name": "texts", "tooltip": "Processed texts" } } };
const TextToUppercase = { "display_name": "Text to Uppercase", "inputs": { "texts": { "name": "texts", "tooltip": "Text to process." } }, "outputs": { "0": { "name": "texts", "tooltip": "Processed texts" } } };
const ThresholdMask = { "display_name": "ThresholdMask", "inputs": { "mask": { "name": "mask" }, "value": { "name": "value" } }, "outputs": { "0": { "tooltip": null } } };
const TomePatchModel = { "display_name": "TomePatchModel", "inputs": { "model": { "name": "model" }, "ratio": { "name": "ratio" } }, "outputs": { "0": { "tooltip": null } } };
const TopazImageEnhance = { "display_name": "Topaz Image Enhance", "description": "Industry-standard upscaling and image enhancement.", "inputs": { "model": { "name": "model" }, "image": { "name": "image" }, "prompt": { "name": "prompt", "tooltip": "Optional text prompt for creative upscaling guidance." }, "subject_detection": { "name": "subject_detection" }, "face_enhancement": { "name": "face_enhancement", "tooltip": "Enhance faces (if present) during processing." }, "face_enhancement_creativity": { "name": "face_enhancement_creativity", "tooltip": "Set the creativity level for face enhancement." }, "face_enhancement_strength": { "name": "face_enhancement_strength", "tooltip": "Controls how sharp enhanced faces are relative to the background." }, "crop_to_fill": { "name": "crop_to_fill", "tooltip": "By default, the image is letterboxed when the output aspect ratio differs. Enable to crop the image to fill the output dimensions." }, "output_width": { "name": "output_width", "tooltip": "Zero value means to calculate automatically (usually it will be original size or output_height if specified)." }, "output_height": { "name": "output_height", "tooltip": "Zero value means to output in the same height as original or output width." }, "creativity": { "name": "creativity" }, "face_preservation": { "name": "face_preservation", "tooltip": "Preserve subjects' facial identity." }, "color_preservation": { "name": "color_preservation", "tooltip": "Preserve the original colors." } }, "outputs": { "0": { "tooltip": null } } };
const TopazVideoEnhance = { "display_name": "Topaz Video Enhance", "description": "Breathe new life into video with powerful upscaling and recovery technology.", "inputs": { "video": { "name": "video" }, "upscaler_enabled": { "name": "upscaler_enabled" }, "upscaler_model": { "name": "upscaler_model" }, "upscaler_resolution": { "name": "upscaler_resolution" }, "upscaler_creativity": { "name": "upscaler_creativity", "tooltip": "Creativity level (applies only to Starlight (Astra) Creative)." }, "interpolation_enabled": { "name": "interpolation_enabled" }, "interpolation_model": { "name": "interpolation_model" }, "interpolation_slowmo": { "name": "interpolation_slowmo", "tooltip": "Slow-motion factor applied to the input video. For example, 2 makes the output twice as slow and doubles the duration." }, "interpolation_frame_rate": { "name": "interpolation_frame_rate", "tooltip": "Output frame rate." }, "interpolation_duplicate": { "name": "interpolation_duplicate", "tooltip": "Analyze the input for duplicate frames and remove them." }, "interpolation_duplicate_threshold": { "name": "interpolation_duplicate_threshold", "tooltip": "Detection sensitivity for duplicate frames." }, "dynamic_compression_level": { "name": "dynamic_compression_level", "tooltip": "CQP level." } }, "outputs": { "0": { "tooltip": null } } };
const TorchCompileModel = { "display_name": "TorchCompileModel", "inputs": { "model": { "name": "model" }, "backend": { "name": "backend" } }, "outputs": { "0": { "tooltip": null } } };
const TrainLoraNode = { "display_name": "Train LoRA", "inputs": { "model": { "name": "model", "tooltip": "The model to train the LoRA on." }, "latents": { "name": "latents", "tooltip": "The Latents to use for training, serve as dataset/input of the model." }, "positive": { "name": "positive", "tooltip": "The positive conditioning to use for training." }, "batch_size": { "name": "batch_size", "tooltip": "The batch size to use for training." }, "grad_accumulation_steps": { "name": "grad_accumulation_steps", "tooltip": "The number of gradient accumulation steps to use for training." }, "steps": { "name": "steps", "tooltip": "The number of steps to train the LoRA for." }, "learning_rate": { "name": "learning_rate", "tooltip": "The learning rate to use for training." }, "rank": { "name": "rank", "tooltip": "The rank of the LoRA layers." }, "optimizer": { "name": "optimizer", "tooltip": "The optimizer to use for training." }, "loss_function": { "name": "loss_function", "tooltip": "The loss function to use for training." }, "seed": { "name": "seed", "tooltip": "The seed to use for training (used in generator for LoRA weight initialization and noise sampling)" }, "training_dtype": { "name": "training_dtype", "tooltip": "The dtype to use for training." }, "lora_dtype": { "name": "lora_dtype", "tooltip": "The dtype to use for lora." }, "algorithm": { "name": "algorithm", "tooltip": "The algorithm to use for training." }, "gradient_checkpointing": { "name": "gradient_checkpointing", "tooltip": "Use gradient checkpointing for training." }, "existing_lora": { "name": "existing_lora", "tooltip": "The existing LoRA to append to. Set to None for new LoRA." }, "bucket_mode": { "name": "bucket_mode", "tooltip": "Enable resolution bucket mode. When enabled, expects pre-bucketed latents from ResolutionBucket node." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "name": "model", "tooltip": "Model with LoRA applied" }, "1": { "name": "lora", "tooltip": "LoRA weights" }, "2": { "name": "loss_map", "tooltip": "Loss history" }, "3": { "name": "steps", "tooltip": "Total training steps" } } };
const TrimAudioDuration = { "display_name": "Trim Audio Duration", "description": "Trim audio tensor into chosen time range.", "inputs": { "audio": { "name": "audio" }, "start_index": { "name": "start_index", "tooltip": "Start time in seconds, can be negative to count from the end (supports sub-seconds)." }, "duration": { "name": "duration", "tooltip": "Duration in seconds" } }, "outputs": { "0": { "tooltip": null } } };
const TrimVideoLatent = { "display_name": "TrimVideoLatent", "inputs": { "samples": { "name": "samples" }, "trim_amount": { "name": "trim_amount" } }, "outputs": { "0": { "tooltip": null } } };
const TripleCLIPLoader = { "display_name": "TripleCLIPLoader", "description": "[Recipes]\n\nsd3: clip-l, clip-g, t5", "inputs": { "clip_name1": { "name": "clip_name1" }, "clip_name2": { "name": "clip_name2" }, "clip_name3": { "name": "clip_name3" } }, "outputs": { "0": { "tooltip": null } } };
const TripoConversionNode = { "display_name": "Tripo: Convert model", "inputs": { "original_model_task_id": { "name": "original_model_task_id" }, "format": { "name": "format" }, "quad": { "name": "quad" }, "face_limit": { "name": "face_limit" }, "texture_size": { "name": "texture_size" }, "texture_format": { "name": "texture_format" }, "force_symmetry": { "name": "force_symmetry" }, "flatten_bottom": { "name": "flatten_bottom" }, "flatten_bottom_threshold": { "name": "flatten_bottom_threshold" }, "pivot_to_center_bottom": { "name": "pivot_to_center_bottom" }, "scale_factor": { "name": "scale_factor" }, "with_animation": { "name": "with_animation" }, "pack_uv": { "name": "pack_uv" }, "bake": { "name": "bake" }, "part_names": { "name": "part_names" }, "fbx_preset": { "name": "fbx_preset" }, "export_vertex_colors": { "name": "export_vertex_colors" }, "export_orientation": { "name": "export_orientation" }, "animate_in_place": { "name": "animate_in_place" } } };
const TripoImageToModelNode = { "display_name": "Tripo: Image to Model", "inputs": { "image": { "name": "image" }, "model_version": { "name": "model_version", "tooltip": "The model version to use for generation" }, "style": { "name": "style" }, "texture": { "name": "texture" }, "pbr": { "name": "pbr" }, "model_seed": { "name": "model_seed" }, "orientation": { "name": "orientation" }, "texture_seed": { "name": "texture_seed" }, "texture_quality": { "name": "texture_quality" }, "texture_alignment": { "name": "texture_alignment" }, "face_limit": { "name": "face_limit" }, "quad": { "name": "quad" }, "geometry_quality": { "name": "geometry_quality" } }, "outputs": { "0": { "name": "model_file", "tooltip": null }, "1": { "name": "model task_id", "tooltip": null } } };
const TripoMultiviewToModelNode = { "display_name": "Tripo: Multiview to Model", "inputs": { "image": { "name": "image" }, "image_left": { "name": "image_left" }, "image_back": { "name": "image_back" }, "image_right": { "name": "image_right" }, "model_version": { "name": "model_version", "tooltip": "The model version to use for generation" }, "orientation": { "name": "orientation" }, "texture": { "name": "texture" }, "pbr": { "name": "pbr" }, "model_seed": { "name": "model_seed" }, "texture_seed": { "name": "texture_seed" }, "texture_quality": { "name": "texture_quality" }, "texture_alignment": { "name": "texture_alignment" }, "face_limit": { "name": "face_limit" }, "quad": { "name": "quad" }, "geometry_quality": { "name": "geometry_quality" } }, "outputs": { "0": { "name": "model_file", "tooltip": null }, "1": { "name": "model task_id", "tooltip": null } } };
const TripoRefineNode = { "display_name": "Tripo: Refine Draft model", "description": "Refine a draft model created by v1.4 Tripo models only.", "inputs": { "model_task_id": { "name": "model_task_id", "tooltip": "Must be a v1.4 Tripo model" } }, "outputs": { "0": { "name": "model_file", "tooltip": null }, "1": { "name": "model task_id", "tooltip": null } } };
const TripoRetargetNode = { "display_name": "Tripo: Retarget rigged model", "inputs": { "original_model_task_id": { "name": "original_model_task_id" }, "animation": { "name": "animation" } }, "outputs": { "0": { "name": "model_file", "tooltip": null }, "1": { "name": "retarget task_id", "tooltip": null } } };
const TripoRigNode = { "display_name": "Tripo: Rig model", "inputs": { "original_model_task_id": { "name": "original_model_task_id" } }, "outputs": { "0": { "name": "model_file", "tooltip": null }, "1": { "name": "rig task_id", "tooltip": null } } };
const TripoTextToModelNode = { "display_name": "Tripo: Text to Model", "inputs": { "prompt": { "name": "prompt" }, "negative_prompt": { "name": "negative_prompt" }, "model_version": { "name": "model_version" }, "style": { "name": "style" }, "texture": { "name": "texture" }, "pbr": { "name": "pbr" }, "image_seed": { "name": "image_seed" }, "model_seed": { "name": "model_seed" }, "texture_seed": { "name": "texture_seed" }, "texture_quality": { "name": "texture_quality" }, "face_limit": { "name": "face_limit" }, "quad": { "name": "quad" }, "geometry_quality": { "name": "geometry_quality" } }, "outputs": { "0": { "name": "model_file", "tooltip": null }, "1": { "name": "model task_id", "tooltip": null } } };
const TripoTextureNode = { "display_name": "Tripo: Texture model", "inputs": { "model_task_id": { "name": "model_task_id" }, "texture": { "name": "texture" }, "pbr": { "name": "pbr" }, "texture_seed": { "name": "texture_seed" }, "texture_quality": { "name": "texture_quality" }, "texture_alignment": { "name": "texture_alignment" } }, "outputs": { "0": { "name": "model_file", "tooltip": null }, "1": { "name": "model task_id", "tooltip": null } } };
const TruncateText = { "display_name": "Truncate Text", "inputs": { "texts": { "name": "texts", "tooltip": "Text to process." }, "max_length": { "name": "max_length", "tooltip": "Maximum text length." } }, "outputs": { "0": { "name": "texts", "tooltip": "Processed texts" } } };
const unCLIPCheckpointLoader = { "display_name": "unCLIPCheckpointLoader", "inputs": { "ckpt_name": { "name": "ckpt_name" } } };
const unCLIPConditioning = { "display_name": "unCLIPConditioning", "inputs": { "conditioning": { "name": "conditioning" }, "clip_vision_output": { "name": "clip_vision_output" }, "strength": { "name": "strength" }, "noise_augmentation": { "name": "noise_augmentation" } } };
const UNetCrossAttentionMultiply = { "display_name": "UNetCrossAttentionMultiply", "inputs": { "model": { "name": "model" }, "q": { "name": "q" }, "k": { "name": "k" }, "v": { "name": "v" }, "out": { "name": "out" } }, "outputs": { "0": { "tooltip": null } } };
const UNETLoader = { "display_name": "Load Diffusion Model", "inputs": { "unet_name": { "name": "unet_name" }, "weight_dtype": { "name": "weight_dtype" } } };
const UNetSelfAttentionMultiply = { "display_name": "UNetSelfAttentionMultiply", "inputs": { "model": { "name": "model" }, "q": { "name": "q" }, "k": { "name": "k" }, "v": { "name": "v" }, "out": { "name": "out" } }, "outputs": { "0": { "tooltip": null } } };
const UNetTemporalAttentionMultiply = { "display_name": "UNetTemporalAttentionMultiply", "inputs": { "model": { "name": "model" }, "self_structural": { "name": "self_structural" }, "self_temporal": { "name": "self_temporal" }, "cross_structural": { "name": "cross_structural" }, "cross_temporal": { "name": "cross_temporal" } }, "outputs": { "0": { "tooltip": null } } };
const UpscaleModelLoader = { "display_name": "Load Upscale Model", "inputs": { "model_name": { "name": "model_name" } }, "outputs": { "0": { "tooltip": null } } };
const USOStyleReference = { "display_name": "USOStyleReference", "inputs": { "model": { "name": "model" }, "model_patch": { "name": "model_patch" }, "clip_vision_output": { "name": "clip_vision_output" } } };
const VAEDecode = { "display_name": "VAE Decode", "description": "Decodes latent images back into pixel space images.", "inputs": { "samples": { "name": "samples", "tooltip": "The latent to be decoded." }, "vae": { "name": "vae", "tooltip": "The VAE model used for decoding the latent." } }, "outputs": { "0": { "tooltip": "The decoded image." } } };
const VAEDecodeAudio = { "display_name": "VAE Decode Audio", "inputs": { "samples": { "name": "samples" }, "vae": { "name": "vae" } }, "outputs": { "0": { "tooltip": null } } };
const VAEDecodeHunyuan3D = { "display_name": "VAEDecodeHunyuan3D", "inputs": { "samples": { "name": "samples" }, "vae": { "name": "vae" }, "num_chunks": { "name": "num_chunks" }, "octree_resolution": { "name": "octree_resolution" } }, "outputs": { "0": { "tooltip": null } } };
const VAEDecodeTiled = { "display_name": "VAE Decode (Tiled)", "inputs": { "samples": { "name": "samples" }, "vae": { "name": "vae" }, "tile_size": { "name": "tile_size" }, "overlap": { "name": "overlap" }, "temporal_size": { "name": "temporal_size", "tooltip": "Only used for video VAEs: Amount of frames to decode at a time." }, "temporal_overlap": { "name": "temporal_overlap", "tooltip": "Only used for video VAEs: Amount of frames to overlap." } } };
const VAEEncode = { "display_name": "VAE Encode", "inputs": { "pixels": { "name": "pixels" }, "vae": { "name": "vae" } } };
const VAEEncodeAudio = { "display_name": "VAE Encode Audio", "inputs": { "audio": { "name": "audio" }, "vae": { "name": "vae" } }, "outputs": { "0": { "tooltip": null } } };
const VAEEncodeForInpaint = { "display_name": "VAE Encode (for Inpainting)", "inputs": { "pixels": { "name": "pixels" }, "vae": { "name": "vae" }, "mask": { "name": "mask" }, "grow_mask_by": { "name": "grow_mask_by" } } };
const VAEEncodeTiled = { "display_name": "VAE Encode (Tiled)", "inputs": { "pixels": { "name": "pixels" }, "vae": { "name": "vae" }, "tile_size": { "name": "tile_size" }, "overlap": { "name": "overlap" }, "temporal_size": { "name": "temporal_size", "tooltip": "Only used for video VAEs: Amount of frames to encode at a time." }, "temporal_overlap": { "name": "temporal_overlap", "tooltip": "Only used for video VAEs: Amount of frames to overlap." } } };
const VAELoader = { "display_name": "Load VAE", "inputs": { "vae_name": { "name": "vae_name" } } };
const VAESave = { "display_name": "VAESave", "inputs": { "vae": { "name": "vae" }, "filename_prefix": { "name": "filename_prefix" } } };
const Veo3FirstLastFrameNode = { "display_name": "Google Veo 3 First-Last-Frame to Video", "description": "Generate video using prompt and first and last frames.", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text description of the video" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt to guide what to avoid in the video" }, "resolution": { "name": "resolution" }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "Aspect ratio of the output video" }, "duration": { "name": "duration", "tooltip": "Duration of the output video in seconds" }, "seed": { "name": "seed", "tooltip": "Seed for video generation" }, "first_frame": { "name": "first_frame", "tooltip": "Start frame" }, "last_frame": { "name": "last_frame", "tooltip": "End frame" }, "model": { "name": "model" }, "generate_audio": { "name": "generate_audio", "tooltip": "Generate audio for the video." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const Veo3VideoGenerationNode = { "display_name": "Google Veo 3 Video Generation", "description": "Generates videos from text prompts using Google's Veo 3 API", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text description of the video" }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "Aspect ratio of the output video" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt to guide what to avoid in the video" }, "duration_seconds": { "name": "duration_seconds", "tooltip": "Duration of the output video in seconds (Veo 3 only supports 8 seconds)" }, "enhance_prompt": { "name": "enhance_prompt", "tooltip": "This parameter is deprecated and ignored." }, "person_generation": { "name": "person_generation", "tooltip": "Whether to allow generating people in the video" }, "seed": { "name": "seed", "tooltip": "Seed for video generation (0 for random)" }, "image": { "name": "image", "tooltip": "Optional reference image to guide video generation" }, "model": { "name": "model", "tooltip": "Veo 3 model to use for video generation" }, "generate_audio": { "name": "generate_audio", "tooltip": "Generate audio for the video. Supported by all Veo 3 models." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const VeoVideoGenerationNode = { "display_name": "Google Veo 2 Video Generation", "description": "Generates videos from text prompts using Google's Veo 2 API", "inputs": { "prompt": { "name": "prompt", "tooltip": "Text description of the video" }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "Aspect ratio of the output video" }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative text prompt to guide what to avoid in the video" }, "duration_seconds": { "name": "duration_seconds", "tooltip": "Duration of the output video in seconds" }, "enhance_prompt": { "name": "enhance_prompt", "tooltip": "Whether to enhance the prompt with AI assistance" }, "person_generation": { "name": "person_generation", "tooltip": "Whether to allow generating people in the video" }, "seed": { "name": "seed", "tooltip": "Seed for video generation (0 for random)" }, "image": { "name": "image", "tooltip": "Optional reference image to guide video generation" }, "model": { "name": "model", "tooltip": "Veo 2 model to use for video generation" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const VideoLinearCFGGuidance = { "display_name": "VideoLinearCFGGuidance", "inputs": { "model": { "name": "model" }, "min_cfg": { "name": "min_cfg" } } };
const VideoTriangleCFGGuidance = { "display_name": "VideoTriangleCFGGuidance", "inputs": { "model": { "name": "model" }, "min_cfg": { "name": "min_cfg" } } };
const ViduImageToVideoNode = { "display_name": "Vidu Image To Video Generation", "description": "Generate video from image and optional prompt", "inputs": { "model": { "name": "model", "tooltip": "Model name" }, "image": { "name": "image", "tooltip": "An image to be used as the start frame of the generated video" }, "prompt": { "name": "prompt", "tooltip": "A textual description for video generation" }, "duration": { "name": "duration", "tooltip": "Duration of the output video in seconds" }, "seed": { "name": "seed", "tooltip": "Seed for video generation (0 for random)" }, "resolution": { "name": "resolution", "tooltip": "Supported values may vary by model & duration" }, "movement_amplitude": { "name": "movement_amplitude", "tooltip": "The movement amplitude of objects in the frame" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ViduReferenceVideoNode = { "display_name": "Vidu Reference To Video Generation", "description": "Generate video from multiple images and prompt", "inputs": { "model": { "name": "model", "tooltip": "Model name" }, "images": { "name": "images", "tooltip": "Images to use as references to generate a video with consistent subjects (max 7 images)." }, "prompt": { "name": "prompt", "tooltip": "A textual description for video generation" }, "duration": { "name": "duration", "tooltip": "Duration of the output video in seconds" }, "seed": { "name": "seed", "tooltip": "Seed for video generation (0 for random)" }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "The aspect ratio of the output video" }, "resolution": { "name": "resolution", "tooltip": "Supported values may vary by model & duration" }, "movement_amplitude": { "name": "movement_amplitude", "tooltip": "The movement amplitude of objects in the frame" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ViduStartEndToVideoNode = { "display_name": "Vidu Start End To Video Generation", "description": "Generate a video from start and end frames and a prompt", "inputs": { "model": { "name": "model", "tooltip": "Model name" }, "first_frame": { "name": "first_frame", "tooltip": "Start frame" }, "end_frame": { "name": "end_frame", "tooltip": "End frame" }, "prompt": { "name": "prompt", "tooltip": "A textual description for video generation" }, "duration": { "name": "duration", "tooltip": "Duration of the output video in seconds" }, "seed": { "name": "seed", "tooltip": "Seed for video generation (0 for random)" }, "resolution": { "name": "resolution", "tooltip": "Supported values may vary by model & duration" }, "movement_amplitude": { "name": "movement_amplitude", "tooltip": "The movement amplitude of objects in the frame" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const ViduTextToVideoNode = { "display_name": "Vidu Text To Video Generation", "description": "Generate video from text prompt", "inputs": { "model": { "name": "model", "tooltip": "Model name" }, "prompt": { "name": "prompt", "tooltip": "A textual description for video generation" }, "duration": { "name": "duration", "tooltip": "Duration of the output video in seconds" }, "seed": { "name": "seed", "tooltip": "Seed for video generation (0 for random)" }, "aspect_ratio": { "name": "aspect_ratio", "tooltip": "The aspect ratio of the output video" }, "resolution": { "name": "resolution", "tooltip": "Supported values may vary by model & duration" }, "movement_amplitude": { "name": "movement_amplitude", "tooltip": "The movement amplitude of objects in the frame" }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const VoxelToMesh = { "display_name": "VoxelToMesh", "inputs": { "voxel": { "name": "voxel" }, "algorithm": { "name": "algorithm" }, "threshold": { "name": "threshold" } }, "outputs": { "0": { "tooltip": null } } };
const VoxelToMeshBasic = { "display_name": "VoxelToMeshBasic", "inputs": { "voxel": { "name": "voxel" }, "threshold": { "name": "threshold" } }, "outputs": { "0": { "tooltip": null } } };
const VPScheduler = { "display_name": "VPScheduler", "inputs": { "steps": { "name": "steps" }, "beta_d": { "name": "beta_d" }, "beta_min": { "name": "beta_min" }, "eps_s": { "name": "eps_s" } }, "outputs": { "0": { "tooltip": null } } };
const Wan22FunControlToVideo = { "display_name": "Wan22FunControlToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "ref_image": { "name": "ref_image" }, "control_video": { "name": "control_video" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const Wan22ImageToVideoLatent = { "display_name": "Wan22ImageToVideoLatent", "inputs": { "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "start_image": { "name": "start_image" } }, "outputs": { "0": { "tooltip": null } } };
const WanAnimateToVideo = { "display_name": "WanAnimateToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "continue_motion_max_frames": { "name": "continue_motion_max_frames" }, "video_frame_offset": { "name": "video_frame_offset", "tooltip": "The amount of frames to seek in all the input videos. Used for generating longer videos by chunk. Connect to the video_frame_offset output of the previous node for extending a video." }, "clip_vision_output": { "name": "clip_vision_output" }, "reference_image": { "name": "reference_image" }, "face_video": { "name": "face_video" }, "pose_video": { "name": "pose_video" }, "background_video": { "name": "background_video" }, "character_mask": { "name": "character_mask" }, "continue_motion": { "name": "continue_motion" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null }, "3": { "name": "trim_latent", "tooltip": null }, "4": { "name": "trim_image", "tooltip": null }, "5": { "name": "video_frame_offset", "tooltip": null } } };
const wanBlockSwap = { "display_name": "wanBlockSwap", "description": "NOP", "inputs": { "model": { "name": "model" } }, "outputs": { "0": { "tooltip": null } } };
const WanCameraEmbedding = { "display_name": "WanCameraEmbedding", "inputs": { "camera_pose": { "name": "camera_pose" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "speed": { "name": "speed" }, "fx": { "name": "fx" }, "fy": { "name": "fy" }, "cx": { "name": "cx" }, "cy": { "name": "cy" } }, "outputs": { "0": { "name": "camera_embedding", "tooltip": null }, "1": { "name": "width", "tooltip": null }, "2": { "name": "height", "tooltip": null }, "3": { "name": "length", "tooltip": null } } };
const WanCameraImageToVideo = { "display_name": "WanCameraImageToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "clip_vision_output": { "name": "clip_vision_output" }, "start_image": { "name": "start_image" }, "camera_conditions": { "name": "camera_conditions" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanContextWindowsManual = { "display_name": "WAN Context Windows (Manual)", "description": "Manually set context windows for WAN-like models (dim=2).", "inputs": { "model": { "name": "model", "tooltip": "The model to apply context windows to during sampling." }, "context_length": { "name": "context_length", "tooltip": "The length of the context window." }, "context_overlap": { "name": "context_overlap", "tooltip": "The overlap of the context window." }, "context_schedule": { "name": "context_schedule", "tooltip": "The stride of the context window." }, "context_stride": { "name": "context_stride", "tooltip": "The stride of the context window; only applicable to uniform schedules." }, "closed_loop": { "name": "closed_loop", "tooltip": "Whether to close the context window loop; only applicable to looped schedules." }, "fuse_method": { "name": "fuse_method", "tooltip": "The method to use to fuse the context windows." }, "freenoise": { "name": "freenoise", "tooltip": "Whether to apply FreeNoise noise shuffling, improves window blending." } }, "outputs": { "0": { "tooltip": "The model with context windows applied during sampling." } } };
const WanFirstLastFrameToVideo = { "display_name": "WanFirstLastFrameToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "clip_vision_start_image": { "name": "clip_vision_start_image" }, "clip_vision_end_image": { "name": "clip_vision_end_image" }, "start_image": { "name": "start_image" }, "end_image": { "name": "end_image" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanFunControlToVideo = { "display_name": "WanFunControlToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "clip_vision_output": { "name": "clip_vision_output" }, "start_image": { "name": "start_image" }, "control_video": { "name": "control_video" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanFunInpaintToVideo = { "display_name": "WanFunInpaintToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "clip_vision_output": { "name": "clip_vision_output" }, "start_image": { "name": "start_image" }, "end_image": { "name": "end_image" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanHuMoImageToVideo = { "display_name": "WanHuMoImageToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "audio_encoder_output": { "name": "audio_encoder_output" }, "ref_image": { "name": "ref_image" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanImageToImageApi = { "display_name": "Wan Image to Image", "description": "Generates an image from one or two input images and a text prompt. The output image is currently fixed at 1.6 MP, and its aspect ratio matches the input image(s).", "inputs": { "model": { "name": "model", "tooltip": "Model to use." }, "image": { "name": "image", "tooltip": "Single-image editing or multi-image fusion. Maximum 2 images." }, "prompt": { "name": "prompt", "tooltip": "Prompt describing the elements and visual features. Supports English and Chinese." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative prompt describing what to avoid." }, "seed": { "name": "seed", "tooltip": "Seed to use for generation." }, "watermark": { "name": "watermark", "tooltip": "Whether to add an AI-generated watermark to the result." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const WanImageToVideo = { "display_name": "WanImageToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "clip_vision_output": { "name": "clip_vision_output" }, "start_image": { "name": "start_image" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanImageToVideoApi = { "display_name": "Wan Image to Video", "description": "Generates a video from the first frame and a text prompt.", "inputs": { "model": { "name": "model", "tooltip": "Model to use." }, "image": { "name": "image" }, "prompt": { "name": "prompt", "tooltip": "Prompt describing the elements and visual features. Supports English and Chinese." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative prompt describing what to avoid." }, "resolution": { "name": "resolution" }, "duration": { "name": "duration", "tooltip": "Duration 15 available only for WAN2.6 model." }, "audio": { "name": "audio", "tooltip": "Audio must contain a clear, loud voice, without extraneous noise or background music." }, "seed": { "name": "seed", "tooltip": "Seed to use for generation." }, "generate_audio": { "name": "generate_audio", "tooltip": "If no audio input is provided, generate audio automatically." }, "prompt_extend": { "name": "prompt_extend", "tooltip": "Whether to enhance the prompt with AI assistance." }, "watermark": { "name": "watermark", "tooltip": "Whether to add an AI-generated watermark to the result." }, "shot_type": { "name": "shot_type", "tooltip": "Specifies the shot type for the generated video, that is, whether the video is a single continuous shot or multiple shots with cuts. This parameter takes effect only when prompt_extend is True." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const WanMoveConcatTrack = { "display_name": "WanMoveConcatTrack", "inputs": { "tracks_1": { "name": "tracks_1" }, "tracks_2": { "name": "tracks_2" } }, "outputs": { "0": { "tooltip": null } } };
const WanMoveTracksFromCoords = { "display_name": "WanMoveTracksFromCoords", "inputs": { "track_coords": { "name": "track_coords" }, "track_mask": { "name": "track_mask" } }, "outputs": { "0": { "tooltip": null }, "1": { "name": "track_length", "tooltip": null } } };
const WanMoveTrackToVideo = { "display_name": "WanMoveTrackToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "strength": { "name": "strength", "tooltip": "Strength of the track conditioning." }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "start_image": { "name": "start_image" }, "tracks": { "name": "tracks" }, "clip_vision_output": { "name": "clip_vision_output" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanMoveVisualizeTracks = { "display_name": "WanMoveVisualizeTracks", "inputs": { "images": { "name": "images" }, "line_resolution": { "name": "line_resolution" }, "circle_size": { "name": "circle_size" }, "opacity": { "name": "opacity" }, "line_width": { "name": "line_width" }, "tracks": { "name": "tracks" } }, "outputs": { "0": { "tooltip": null } } };
const WanPhantomSubjectToVideo = { "display_name": "WanPhantomSubjectToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "images": { "name": "images" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative_text", "tooltip": null }, "2": { "name": "negative_img_text", "tooltip": null }, "3": { "name": "latent", "tooltip": null } } };
const WanSoundImageToVideo = { "display_name": "WanSoundImageToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "audio_encoder_output": { "name": "audio_encoder_output" }, "ref_image": { "name": "ref_image" }, "control_video": { "name": "control_video" }, "ref_motion": { "name": "ref_motion" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanSoundImageToVideoExtend = { "display_name": "WanSoundImageToVideoExtend", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "length": { "name": "length" }, "video_latent": { "name": "video_latent" }, "audio_encoder_output": { "name": "audio_encoder_output" }, "ref_image": { "name": "ref_image" }, "control_video": { "name": "control_video" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanTextToImageApi = { "display_name": "Wan Text to Image", "description": "Generates an image based on a text prompt.", "inputs": { "model": { "name": "model", "tooltip": "Model to use." }, "prompt": { "name": "prompt", "tooltip": "Prompt describing the elements and visual features. Supports English and Chinese." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative prompt describing what to avoid." }, "width": { "name": "width" }, "height": { "name": "height" }, "seed": { "name": "seed", "tooltip": "Seed to use for generation." }, "prompt_extend": { "name": "prompt_extend", "tooltip": "Whether to enhance the prompt with AI assistance." }, "watermark": { "name": "watermark", "tooltip": "Whether to add an AI-generated watermark to the result." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const WanTextToVideoApi = { "display_name": "Wan Text to Video", "description": "Generates a video based on a text prompt.", "inputs": { "model": { "name": "model", "tooltip": "Model to use." }, "prompt": { "name": "prompt", "tooltip": "Prompt describing the elements and visual features. Supports English and Chinese." }, "negative_prompt": { "name": "negative_prompt", "tooltip": "Negative prompt describing what to avoid." }, "size": { "name": "size" }, "duration": { "name": "duration", "tooltip": "A 15-second duration is available only for the Wan 2.6 model." }, "audio": { "name": "audio", "tooltip": "Audio must contain a clear, loud voice, without extraneous noise or background music." }, "seed": { "name": "seed", "tooltip": "Seed to use for generation." }, "generate_audio": { "name": "generate_audio", "tooltip": "If no audio input is provided, generate audio automatically." }, "prompt_extend": { "name": "prompt_extend", "tooltip": "Whether to enhance the prompt with AI assistance." }, "watermark": { "name": "watermark", "tooltip": "Whether to add an AI-generated watermark to the result." }, "shot_type": { "name": "shot_type", "tooltip": "Specifies the shot type for the generated video, that is, whether the video is a single continuous shot or multiple shots with cuts. This parameter takes effect only when prompt_extend is True." }, "control_after_generate": { "name": "control after generate" } }, "outputs": { "0": { "tooltip": null } } };
const WanTrackToVideo = { "display_name": "WanTrackToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "tracks": { "name": "tracks" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "temperature": { "name": "temperature" }, "topk": { "name": "topk" }, "start_image": { "name": "start_image" }, "clip_vision_output": { "name": "clip_vision_output" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null } } };
const WanVaceToVideo = { "display_name": "WanVaceToVideo", "inputs": { "positive": { "name": "positive" }, "negative": { "name": "negative" }, "vae": { "name": "vae" }, "width": { "name": "width" }, "height": { "name": "height" }, "length": { "name": "length" }, "batch_size": { "name": "batch_size" }, "strength": { "name": "strength" }, "control_video": { "name": "control_video" }, "control_masks": { "name": "control_masks" }, "reference_image": { "name": "reference_image" } }, "outputs": { "0": { "name": "positive", "tooltip": null }, "1": { "name": "negative", "tooltip": null }, "2": { "name": "latent", "tooltip": null }, "3": { "name": "trim_latent", "tooltip": null } } };
const WebcamCapture = { "display_name": "Webcam Capture", "inputs": { "image": { "name": "image" }, "width": { "name": "width" }, "height": { "name": "height" }, "capture_on_queue": { "name": "capture_on_queue" }, "waiting for camera___": {} } };
const ZImageFunControlnet = { "display_name": "ZImageFunControlnet", "inputs": { "model": { "name": "model" }, "model_patch": { "name": "model_patch" }, "vae": { "name": "vae" }, "strength": { "name": "strength" }, "image": { "name": "image" }, "inpaint_image": { "name": "inpaint_image" }, "mask": { "name": "mask" } } };
const enNodes = {
  AddNoise,
  AddTextPrefix,
  AddTextSuffix,
  AdjustBrightness,
  AdjustContrast,
  AlignYourStepsScheduler,
  APG,
  AudioAdjustVolume,
  AudioConcat,
  AudioEncoderEncode,
  AudioEncoderLoader,
  AudioMerge,
  BasicGuider,
  BasicScheduler,
  BetaSamplingScheduler,
  ByteDanceFirstLastFrameNode,
  ByteDanceImageEditNode,
  ByteDanceImageNode,
  ByteDanceImageReferenceNode,
  ByteDanceImageToVideoNode,
  ByteDanceSeedreamNode,
  ByteDanceTextToVideoNode,
  Canny,
  CaseConverter,
  CenterCropImages,
  CFGGuider,
  CFGNorm,
  CFGZeroStar,
  CheckpointLoader,
  CheckpointLoaderSimple,
  CheckpointSave,
  ChromaRadianceOptions,
  CLIPAttentionMultiply,
  CLIPLoader,
  CLIPMergeAdd,
  CLIPMergeSimple,
  CLIPMergeSubtract,
  CLIPSave,
  CLIPSetLastLayer,
  CLIPTextEncode,
  CLIPTextEncodeControlnet,
  CLIPTextEncodeFlux,
  CLIPTextEncodeHiDream,
  CLIPTextEncodeHunyuanDiT,
  CLIPTextEncodeKandinsky5,
  CLIPTextEncodeLumina2,
  CLIPTextEncodePixArtAlpha,
  CLIPTextEncodeSD3,
  CLIPTextEncodeSDXL,
  CLIPTextEncodeSDXLRefiner,
  CLIPVisionEncode,
  CLIPVisionLoader,
  CombineHooks2,
  CombineHooks4,
  CombineHooks8,
  ConditioningAverage,
  ConditioningCombine,
  ConditioningConcat,
  ConditioningSetArea,
  ConditioningSetAreaPercentage,
  ConditioningSetAreaPercentageVideo,
  ConditioningSetAreaStrength,
  ConditioningSetDefaultCombine,
  ConditioningSetMask,
  ConditioningSetProperties,
  ConditioningSetPropertiesAndCombine,
  ConditioningSetTimestepRange,
  ConditioningStableAudio,
  ConditioningTimestepsRange,
  ConditioningZeroOut,
  ContextWindowsManual,
  ControlNetApply,
  ControlNetApplyAdvanced,
  ControlNetApplySD3,
  ControlNetInpaintingAliMamaApply,
  ControlNetLoader,
  CosmosImageToVideoLatent,
  CosmosPredict2ImageToVideoLatent,
  CreateHookKeyframe,
  CreateHookKeyframesFromFloats,
  CreateHookKeyframesInterpolated,
  CreateHookLora,
  CreateHookLoraModelOnly,
  CreateHookModelAsLora,
  CreateHookModelAsLoraModelOnly,
  CreateVideo,
  CropMask,
  DiffControlNetLoader,
  DifferentialDiffusion,
  DiffusersLoader,
  DisableNoise,
  DualCFGGuider,
  DualCLIPLoader,
  EasyCache,
  EmptyAceStepLatentAudio,
  EmptyAudio,
  EmptyChromaRadianceLatentImage,
  EmptyCosmosLatentVideo,
  EmptyFlux2LatentImage,
  EmptyHunyuanImageLatent,
  EmptyHunyuanLatentVideo,
  EmptyHunyuanVideo15Latent,
  EmptyImage,
  EmptyLatentAudio,
  EmptyLatentHunyuan3Dv2,
  EmptyLatentImage,
  EmptyLTXVLatentVideo,
  EmptyMochiLatentVideo,
  EmptyQwenImageLayeredLatentImage,
  EmptySD3LatentImage,
  "Epsilon Scaling": { "display_name": "Epsilon Scaling", "inputs": { "model": { "name": "model" }, "scaling_factor": { "name": "scaling_factor" } }, "outputs": { "0": { "tooltip": null } } },
  ExponentialScheduler,
  ExtendIntermediateSigmas,
  FeatherMask,
  FlipSigmas,
  Flux2MaxImageNode,
  Flux2ProImageNode,
  Flux2Scheduler,
  FluxDisableGuidance,
  FluxGuidance,
  FluxKontextImageScale,
  FluxKontextMaxImageNode,
  FluxKontextMultiReferenceLatentMethod,
  FluxKontextProImageNode,
  FluxProExpandNode,
  FluxProFillNode,
  FluxProUltraImageNode,
  FreeU,
  FreeU_V2,
  FreSca,
  GeminiImage2Node,
  GeminiImageNode,
  GeminiInputFiles,
  GeminiNode,
  GenerateTracks,
  GetImageSize,
  GetVideoComponents,
  GITSScheduler,
  GLIGENLoader,
  GLIGENTextBoxApply,
  GrowMask,
  Hunyuan3Dv2Conditioning,
  Hunyuan3Dv2ConditioningMultiView,
  HunyuanImageToVideo,
  HunyuanRefinerLatent,
  HunyuanVideo15ImageToVideo,
  HunyuanVideo15LatentUpscaleWithModel,
  HunyuanVideo15SuperResolution,
  HypernetworkLoader,
  HyperTile,
  IdeogramV1,
  IdeogramV2,
  IdeogramV3,
  ImageAddNoise,
  ImageBatch,
  ImageBlend,
  ImageBlur,
  ImageColorToMask,
  ImageCompositeMasked,
  ImageCrop,
  ImageDeduplication,
  ImageFlip,
  ImageFromBatch,
  ImageGrid,
  ImageInvert,
  ImageOnlyCheckpointLoader,
  ImageOnlyCheckpointSave,
  ImagePadForOutpaint,
  ImageQuantize,
  ImageRGBToYUV,
  ImageRotate,
  ImageScale,
  ImageScaleBy,
  ImageScaleToMaxDimension,
  ImageScaleToTotalPixels,
  ImageSharpen,
  ImageStitch,
  ImageToMask,
  ImageUpscaleWithModel,
  ImageYUVToRGB,
  InpaintModelConditioning,
  InstructPixToPixConditioning,
  InvertMask,
  JoinImageWithAlpha,
  Kandinsky5ImageToVideo,
  KarrasScheduler,
  KlingCameraControlI2VNode,
  KlingCameraControls,
  KlingCameraControlT2VNode,
  KlingDualCharacterVideoEffectNode,
  KlingImage2VideoNode,
  KlingImageGenerationNode,
  KlingImageToVideoWithAudio,
  KlingLipSyncAudioToVideoNode,
  KlingLipSyncTextToVideoNode,
  KlingMotionControl,
  KlingOmniProEditVideoNode,
  KlingOmniProFirstLastFrameNode,
  KlingOmniProImageNode,
  KlingOmniProImageToVideoNode,
  KlingOmniProTextToVideoNode,
  KlingOmniProVideoToVideoNode,
  KlingSingleImageVideoEffectNode,
  KlingStartEndFrameNode,
  KlingTextToVideoNode,
  KlingTextToVideoWithAudio,
  KlingVideoExtendNode,
  KlingVirtualTryOnNode,
  KSampler,
  KSamplerAdvanced,
  KSamplerSelect,
  LaplaceScheduler,
  LatentAdd,
  LatentApplyOperation,
  LatentApplyOperationCFG,
  LatentBatch,
  LatentBatchSeedBehavior,
  LatentBlend,
  LatentComposite,
  LatentCompositeMasked,
  LatentConcat,
  LatentCrop,
  LatentCut,
  LatentCutToBatch,
  LatentFlip,
  LatentFromBatch,
  LatentInterpolate,
  LatentMultiply,
  LatentOperationSharpen,
  LatentOperationTonemapReinhard,
  LatentRotate,
  LatentSubtract,
  LatentUpscale,
  LatentUpscaleBy,
  LatentUpscaleModelLoader,
  LazyCache,
  Load3D: Load3D$1,
  LoadAudio,
  LoadImage,
  LoadImageDataSetFromFolder,
  LoadImageMask,
  LoadImageOutput,
  LoadImageTextDataSetFromFolder,
  LoadLatent,
  LoadTrainingDataset,
  LoadVideo,
  LoraLoader,
  LoraLoaderModelOnly,
  LoraModelLoader,
  LoraSave,
  LossGraphNode,
  LotusConditioning,
  LTXVAddGuide,
  LtxvApiImageToVideo,
  LtxvApiTextToVideo,
  LTXVConditioning,
  LTXVCropGuides,
  LTXVImgToVideo,
  LTXVPreprocess,
  LTXVScheduler,
  LumaConceptsNode,
  LumaImageModifyNode,
  LumaImageNode,
  LumaImageToVideoNode,
  LumaReferenceNode,
  LumaVideoNode,
  Mahiro,
  MakeTrainingDataset,
  ManualSigmas,
  MaskComposite,
  MaskPreview,
  MaskToImage,
  MergeImageLists,
  MergeTextLists,
  MinimaxHailuoVideoNode,
  MinimaxImageToVideoNode,
  MinimaxTextToVideoNode,
  ModelComputeDtype,
  ModelMergeAdd,
  ModelMergeAuraflow,
  ModelMergeBlocks,
  ModelMergeCosmos14B,
  ModelMergeCosmos7B,
  ModelMergeCosmosPredict2_14B,
  ModelMergeCosmosPredict2_2B,
  ModelMergeFlux1,
  ModelMergeLTXV,
  ModelMergeMochiPreview,
  ModelMergeQwenImage,
  ModelMergeSD1,
  ModelMergeSD2,
  ModelMergeSD3_2B,
  ModelMergeSD35_Large,
  ModelMergeSDXL,
  ModelMergeSimple,
  ModelMergeSubtract,
  ModelMergeWAN2_1,
  ModelPatchLoader,
  ModelSamplingAuraFlow,
  ModelSamplingContinuousEDM,
  ModelSamplingContinuousV,
  ModelSamplingDiscrete,
  ModelSamplingFlux,
  ModelSamplingLTXV,
  ModelSamplingSD3,
  ModelSamplingStableCascade,
  ModelSave,
  MoonvalleyImg2VideoNode,
  MoonvalleyTxt2VideoNode,
  MoonvalleyVideo2VideoNode,
  Morphology,
  NormalizeImages,
  NormalizeVideoLatentStart,
  OpenAIChatConfig,
  OpenAIChatNode,
  OpenAIDalle2,
  OpenAIDalle3,
  OpenAIGPTImage1,
  OpenAIInputFiles,
  OpenAIVideoSora2,
  OptimalStepsScheduler,
  PairConditioningCombine,
  PairConditioningSetDefaultCombine,
  PairConditioningSetProperties,
  PairConditioningSetPropertiesAndCombine,
  PatchModelAddDownscale,
  PerpNeg,
  PerpNegGuider,
  PerturbedAttentionGuidance,
  PhotoMakerEncode,
  PhotoMakerLoader,
  PixverseImageToVideoNode,
  PixverseTemplateNode,
  PixverseTextToVideoNode,
  PixverseTransitionVideoNode,
  PolyexponentialScheduler,
  PorterDuffImageComposite,
  Preview3D,
  PreviewAny,
  PreviewAudio,
  PreviewImage,
  PrimitiveBoolean,
  PrimitiveFloat,
  PrimitiveInt,
  PrimitiveString,
  PrimitiveStringMultiline,
  QuadrupleCLIPLoader,
  QwenImageDiffsynthControlnet,
  RandomCropImages,
  RandomNoise,
  RebatchImages,
  RebatchLatents,
  RecordAudio,
  RecraftColorRGB,
  RecraftControls,
  RecraftCreativeUpscaleNode,
  RecraftCrispUpscaleNode,
  RecraftImageInpaintingNode,
  RecraftImageToImageNode,
  RecraftRemoveBackgroundNode,
  RecraftReplaceBackgroundNode,
  RecraftStyleV3DigitalIllustration,
  RecraftStyleV3InfiniteStyleLibrary,
  RecraftStyleV3LogoRaster,
  RecraftStyleV3RealisticImage,
  RecraftTextToImageNode,
  RecraftTextToVectorNode,
  RecraftVectorizeImageNode,
  ReferenceLatent,
  RegexExtract,
  RegexMatch,
  RegexReplace,
  RenormCFG,
  RepeatImageBatch,
  RepeatLatentBatch,
  ReplaceText,
  ReplaceVideoLatentFrames,
  RescaleCFG,
  ResizeAndPadImage,
  ResizeImagesByLongerEdge,
  ResizeImagesByShorterEdge,
  ResolutionBucket,
  Rodin3D_Detail,
  Rodin3D_Gen2,
  Rodin3D_Regular,
  Rodin3D_Sketch,
  Rodin3D_Smooth,
  RunwayFirstLastFrameNode,
  RunwayImageToVideoNodeGen3a,
  RunwayImageToVideoNodeGen4,
  RunwayTextToImageNode,
  SamplerCustom,
  SamplerCustomAdvanced,
  SamplerDPMAdaptative,
  SamplerDPMPP_2M_SDE,
  SamplerDPMPP_2S_Ancestral,
  SamplerDPMPP_3M_SDE,
  SamplerDPMPP_SDE,
  SamplerER_SDE,
  SamplerEulerAncestral,
  SamplerEulerAncestralCFGPP,
  SamplerEulerCFGpp,
  SamplerLCMUpscale,
  SamplerLMS,
  SamplerSASolver,
  SamplerSEEDS2,
  SamplingPercentToSigma,
  SaveAnimatedPNG,
  SaveAnimatedWEBP,
  SaveAudio,
  SaveAudioMP3,
  SaveAudioOpus,
  SaveGLB,
  SaveImage,
  SaveImageDataSetToFolder,
  SaveImageTextDataSetToFolder,
  SaveImageWebsocket,
  SaveLatent,
  SaveLoRA,
  SaveSVGNode,
  SaveTrainingDataset,
  SaveVideo,
  SaveWEBM,
  ScaleROPE,
  SD_4XUpscale_Conditioning,
  SDTurboScheduler,
  SelfAttentionGuidance,
  SetClipHooks,
  SetFirstSigma,
  SetHookKeyframes,
  SetLatentNoiseMask,
  SetUnionControlNetType,
  ShuffleDataset,
  ShuffleImageTextDataset,
  SkipLayerGuidanceDiT,
  SkipLayerGuidanceDiTSimple,
  SkipLayerGuidanceSD3,
  SolidMask,
  SplitAudioChannels,
  SplitImageWithAlpha,
  SplitSigmas,
  SplitSigmasDenoise,
  StabilityAudioInpaint,
  StabilityAudioToAudio,
  StabilityStableImageSD_3_5Node,
  StabilityStableImageUltraNode,
  StabilityTextToAudio,
  StabilityUpscaleConservativeNode,
  StabilityUpscaleCreativeNode,
  StabilityUpscaleFastNode,
  StableCascade_EmptyLatentImage,
  StableCascade_StageB_Conditioning,
  StableCascade_StageC_VAEEncode,
  StableCascade_SuperResolutionControlnet,
  StableZero123_Conditioning,
  StableZero123_Conditioning_Batched,
  StringCompare,
  StringConcatenate,
  StringContains,
  StringLength,
  StringReplace,
  StringSubstring,
  StringTrim,
  StripWhitespace,
  StyleModelApply,
  StyleModelLoader,
  SV3D_Conditioning,
  SVD_img2vid_Conditioning,
  T5TokenizerOptions,
  TCFG,
  TemporalScoreRescaling,
  TextEncodeAceStepAudio,
  TextEncodeHunyuanVideo_ImageToVideo,
  TextEncodeQwenImageEdit,
  TextEncodeQwenImageEditPlus,
  TextToLowercase,
  TextToUppercase,
  ThresholdMask,
  TomePatchModel,
  TopazImageEnhance,
  TopazVideoEnhance,
  TorchCompileModel,
  TrainLoraNode,
  TrimAudioDuration,
  TrimVideoLatent,
  TripleCLIPLoader,
  TripoConversionNode,
  TripoImageToModelNode,
  TripoMultiviewToModelNode,
  TripoRefineNode,
  TripoRetargetNode,
  TripoRigNode,
  TripoTextToModelNode,
  TripoTextureNode,
  TruncateText,
  unCLIPCheckpointLoader,
  unCLIPConditioning,
  UNetCrossAttentionMultiply,
  UNETLoader,
  UNetSelfAttentionMultiply,
  UNetTemporalAttentionMultiply,
  UpscaleModelLoader,
  USOStyleReference,
  VAEDecode,
  VAEDecodeAudio,
  VAEDecodeHunyuan3D,
  VAEDecodeTiled,
  VAEEncode,
  VAEEncodeAudio,
  VAEEncodeForInpaint,
  VAEEncodeTiled,
  VAELoader,
  VAESave,
  Veo3FirstLastFrameNode,
  Veo3VideoGenerationNode,
  VeoVideoGenerationNode,
  VideoLinearCFGGuidance,
  VideoTriangleCFGGuidance,
  ViduImageToVideoNode,
  ViduReferenceVideoNode,
  ViduStartEndToVideoNode,
  ViduTextToVideoNode,
  VoxelToMesh,
  VoxelToMeshBasic,
  VPScheduler,
  Wan22FunControlToVideo,
  Wan22ImageToVideoLatent,
  WanAnimateToVideo,
  wanBlockSwap,
  WanCameraEmbedding,
  WanCameraImageToVideo,
  WanContextWindowsManual,
  WanFirstLastFrameToVideo,
  WanFunControlToVideo,
  WanFunInpaintToVideo,
  WanHuMoImageToVideo,
  WanImageToImageApi,
  WanImageToVideo,
  WanImageToVideoApi,
  WanMoveConcatTrack,
  WanMoveTracksFromCoords,
  WanMoveTrackToVideo,
  WanMoveVisualizeTracks,
  WanPhantomSubjectToVideo,
  WanSoundImageToVideo,
  WanSoundImageToVideoExtend,
  WanTextToImageApi,
  WanTextToVideoApi,
  WanTrackToVideo,
  WanVaceToVideo,
  WebcamCapture,
  ZImageFunControlnet
};
const Comfy_Canvas_BackgroundImage = { "name": "Canvas background image", "tooltip": 'Image URL for the canvas background. You can right-click an image in the outputs panel and select "Set as Background" to use it, or upload your own image using the upload button.' };
const Comfy_Canvas_LeftMouseClickBehavior = { "name": "Left Mouse Click Behavior", "options": { "Panning": "Panning", "Select": "Select" } };
const Comfy_Canvas_MouseWheelScroll = { "name": "Mouse Wheel Scroll", "options": { "Panning": "Panning", "Zoom in/out": "Zoom in/out" } };
const Comfy_Canvas_NavigationMode = { "name": "Navigation Mode", "options": { "Standard (New)": "Standard (New)", "Drag Navigation": "Drag Navigation", "Custom": "Custom" } };
const Comfy_Canvas_SelectionToolbox = { "name": "Show selection toolbox" };
const Comfy_ConfirmClear = { "name": "Require confirmation when clearing workflow" };
const Comfy_DevMode = { "name": "Enable dev mode options (API save, etc.)" };
const Comfy_DisableFloatRounding = { "name": "Disable default float widget rounding.", "tooltip": "(requires page reload) Cannot disable round when round is set by the node in the backend." };
const Comfy_DisableSliders = { "name": "Disable node widget sliders" };
const Comfy_DOMClippingEnabled = { "name": "Enable DOM element clipping (enabling may reduce performance)" };
const Comfy_EditAttention_Delta = { "name": "Ctrl+up/down precision" };
const Comfy_EnableTooltips = { "name": "Enable Tooltips" };
const Comfy_EnableWorkflowViewRestore = { "name": "Save and restore canvas position and zoom level in workflows" };
const Comfy_Execution_PreviewMethod = { "name": "Live preview method", "tooltip": 'Live preview method during image generation. "default" uses the server CLI setting.', "options": { "default": "default", "none": "none", "auto": "auto", "latent2rgb": "latent2rgb", "taesd": "taesd" } };
const Comfy_FloatRoundingPrecision = { "name": "Float widget rounding decimal places [0 = auto].", "tooltip": "(requires page reload)" };
const Comfy_Graph_CanvasInfo = { "name": "Show canvas info on bottom left corner (fps, etc.)" };
const Comfy_Graph_CanvasMenu = { "name": "Show graph canvas menu" };
const Comfy_Graph_CtrlShiftZoom = { "name": "Enable fast-zoom shortcut (Ctrl + Shift + Drag)" };
const Comfy_Graph_LinkMarkers = { "name": "Link midpoint markers", "options": { "None": "None", "Circle": "Circle", "Arrow": "Arrow" } };
const Comfy_Graph_LiveSelection = { "name": "Live selection", "tooltip": "When enabled, nodes are selected/deselected in real-time as you drag the selection rectangle, similar to other design tools." };
const Comfy_Graph_ZoomSpeed = { "name": "Canvas zoom speed" };
const Comfy_Group_DoubleClickTitleToEdit = { "name": "Double click group title to edit" };
const Comfy_GroupSelectedNodes_Padding = { "name": "Group selected nodes padding" };
const Comfy_LinkRelease_Action = { "name": "Action on link release (No modifier)", "options": { "context menu": "context menu", "search box": "search box", "no action": "no action" } };
const Comfy_LinkRelease_ActionShift = { "name": "Action on link release (Shift)", "options": { "context menu": "context menu", "search box": "search box", "no action": "no action" } };
const Comfy_LinkRenderMode = { "name": "Link Render Mode", "options": { "Straight": "Straight", "Linear": "Linear", "Spline": "Spline", "Hidden": "Hidden" } };
const Comfy_Load3D_3DViewerEnable = { "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." };
const Comfy_Load3D_BackgroundColor = { "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." };
const Comfy_Load3D_CameraType = { "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.", "options": { "perspective": "perspective", "orthographic": "orthographic" } };
const Comfy_Load3D_LightAdjustmentIncrement = { "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." };
const Comfy_Load3D_LightIntensity = { "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." };
const Comfy_Load3D_LightIntensityMaximum = { "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." };
const Comfy_Load3D_LightIntensityMinimum = { "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." };
const Comfy_Load3D_PLYEngine = { "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.', "options": { "threejs": "threejs", "fastply": "fastply", "sparkjs": "sparkjs" } };
const Comfy_Load3D_ShowGrid = { "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." };
const Comfy_Locale = { "name": "Language" };
const Comfy_MaskEditor_BrushAdjustmentSpeed = { "name": "Brush adjustment speed multiplier", "tooltip": "Controls how quickly the brush size and hardness change when adjusting. Higher values mean faster changes." };
const Comfy_MaskEditor_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" };
const Comfy_ModelLibrary_AutoLoadAll = { "name": "Automatically load all model folders", "tooltip": "If true, all folders will load as soon as you open the model library (this may cause delays while it loads). If false, root level model folders will only load once you click on them." };
const Comfy_ModelLibrary_NameFormat = { "name": "What name to display in the model library tree view", "tooltip": 'Select "filename" to render a simplified view of the raw filename (without directory or ".safetensors" extension) in the model list. Select "title" to display the configurable model metadata title.', "options": { "filename": "filename", "title": "title" } };
const Comfy_Node_AllowImageSizeDraw = { "name": "Show width × height below the image preview" };
const Comfy_Node_AutoSnapLinkToSlot = { "name": "Auto snap link to node slot", "tooltip": "When dragging a link over a node, the link automatically snap to a viable input slot on the node" };
const Comfy_Node_BypassAllLinksOnDelete = { "name": "Keep all links when deleting nodes", "tooltip": "When deleting a node, attempt to reconnect all of its input and output links (bypassing the deleted node)" };
const Comfy_Node_DoubleClickTitleToEdit = { "name": "Double click node title to edit" };
const Comfy_Node_MiddleClickRerouteNode = { "name": "Middle-click creates a new Reroute node" };
const Comfy_Node_Opacity = { "name": "Node opacity" };
const Comfy_Node_ShowDeprecated = { "name": "Show deprecated nodes in search", "tooltip": "Deprecated nodes are hidden by default in the UI, but remain functional in existing workflows that use them." };
const Comfy_Node_ShowExperimental = { "name": "Show experimental nodes in search", "tooltip": "Experimental nodes are marked as such in the UI and may be subject to significant changes or removal in future versions. Use with caution in production workflows" };
const Comfy_Node_SnapHighlightsNode = { "name": "Snap highlights node", "tooltip": "When dragging a link over a node with viable input slot, highlight the node" };
const Comfy_NodeBadge_NodeIdBadgeMode = { "name": "Node ID badge mode", "options": { "None": "None", "Show all": "Show all" } };
const Comfy_NodeBadge_NodeLifeCycleBadgeMode = { "name": "Node life cycle badge mode", "options": { "None": "None", "Show all": "Show all" } };
const Comfy_NodeBadge_NodeSourceBadgeMode = { "name": "Node source badge mode", "options": { "None": "None", "Show all": "Show all", "Hide built-in": "Hide built-in" } };
const Comfy_NodeBadge_ShowApiPricing = { "name": "Show API node pricing badge" };
const Comfy_NodeSearchBoxImpl = { "name": "Node search box implementation", "options": { "default": "default", "litegraph (legacy)": "litegraph (legacy)" } };
const Comfy_NodeSearchBoxImpl_NodePreview = { "name": "Node preview", "tooltip": "Only applies to the default implementation" };
const Comfy_NodeSearchBoxImpl_ShowCategory = { "name": "Show node category in search results", "tooltip": "Only applies to the default implementation" };
const Comfy_NodeSearchBoxImpl_ShowIdName = { "name": "Show node id name in search results", "tooltip": "Only applies to the default implementation" };
const Comfy_NodeSearchBoxImpl_ShowNodeFrequency = { "name": "Show node frequency in search results", "tooltip": "Only applies to the default implementation" };
const Comfy_NodeSuggestions_number = { "name": "Number of nodes suggestions", "tooltip": "Only for litegraph searchbox/context menu" };
const Comfy_Notification_ShowVersionUpdates = { "name": "Show version updates", "tooltip": "Show updates for new models, and major new features." };
const Comfy_Pointer_ClickBufferTime = { "name": "Pointer click drift delay", "tooltip": "After pressing a pointer button down, this is the maximum time (in milliseconds) that pointer movement can be ignored for.\n\nHelps prevent objects from being unintentionally nudged if the pointer is moved whilst clicking." };
const Comfy_Pointer_ClickDrift = { "name": "Pointer click drift (maximum distance)", "tooltip": "If the pointer moves more than this distance while holding a button down, it is considered dragging (rather than clicking).\n\nHelps prevent objects from being unintentionally nudged if the pointer is moved whilst clicking." };
const Comfy_Pointer_DoubleClickTime = { "name": "Double click interval (maximum)", "tooltip": "The maximum time in milliseconds between the two clicks of a double-click.  Increasing this value may assist if double-clicks are sometimes not registered." };
const Comfy_PreviewFormat = { "name": "Preview image format", "tooltip": "When displaying a preview in the image widget, convert it to a lightweight image, e.g. webp, jpeg, webp;50, etc." };
const Comfy_PromptFilename = { "name": "Prompt for filename when saving workflow" };
const Comfy_Queue_MaxHistoryItems = { "name": "Queue history size", "tooltip": "The maximum number of tasks that show in the queue history." };
const Comfy_QueueButton_BatchCountLimit = { "name": "Batch count limit", "tooltip": "The maximum number of tasks added to the queue at one button click" };
const Comfy_Sidebar_Location = { "name": "Sidebar location", "options": { "left": "left", "right": "right" } };
const Comfy_Sidebar_Size = { "name": "Sidebar size", "options": { "normal": "normal", "small": "small" } };
const Comfy_Sidebar_Style = { "name": "Sidebar style", "options": { "floating": "floating", "connected": "connected" } };
const Comfy_Sidebar_UnifiedWidth = { "name": "Unified sidebar width" };
const Comfy_SnapToGrid_GridSize = { "name": "Snap to grid size", "tooltip": "When dragging and resizing nodes while holding shift they will be aligned to the grid, this controls the size of that grid." };
const Comfy_TextareaWidget_FontSize = { "name": "Textarea widget font size" };
const Comfy_TextareaWidget_Spellcheck = { "name": "Textarea widget spellcheck" };
const Comfy_TreeExplorer_ItemPadding = { "name": "Tree explorer item padding" };
const Comfy_UseNewMenu = { "name": "Use new menu", "tooltip": "Enable the redesigned top menu bar.", "options": { "Disabled": "Disabled", "Top": "Top" } };
const Comfy_Validation_Workflows = { "name": "Validate workflows" };
const Comfy_VueNodes_AutoScaleLayout = { "name": "Auto-scale layout (Nodes 2.0)", "tooltip": "Automatically scale node positions when switching to Nodes 2.0 rendering to prevent overlap" };
const Comfy_VueNodes_Enabled = { "name": "Modern Node Design (Nodes 2.0)", "tooltip": "Modern: DOM-based rendering with enhanced interactivity, native browser features, and updated visual design. Classic: Traditional canvas rendering." };
const Comfy_WidgetControlMode = { "name": "Widget control mode", "tooltip": "Controls when widget values are updated (randomize/increment/decrement), either before the prompt is queued or after.", "options": { "before": "before", "after": "after" } };
const Comfy_Window_UnloadConfirmation = { "name": "Show confirmation when closing window" };
const Comfy_Workflow_AutoSave = { "name": "Auto Save", "options": { "off": "off", "after delay": "after delay" } };
const Comfy_Workflow_AutoSaveDelay = { "name": "Auto Save Delay (ms)", "tooltip": 'Only applies if Auto Save is set to "after delay".' };
const Comfy_Workflow_ConfirmDelete = { "name": "Show confirmation when deleting workflows" };
const Comfy_Workflow_Persist = { "name": "Persist workflow state and restore on page (re)load" };
const Comfy_Workflow_ShowMissingModelsWarning = { "name": "Show missing models warning" };
const Comfy_Workflow_ShowMissingNodesWarning = { "name": "Show missing nodes warning" };
const Comfy_Workflow_SortNodeIdOnSave = { "name": "Sort node IDs when saving workflow" };
const Comfy_Workflow_WarnBlueprintOverwrite = { "name": "Require confirmation to overwrite an existing subgraph blueprint" };
const Comfy_Workflow_WorkflowTabsPosition = { "name": "Opened workflows position", "options": { "Sidebar": "Sidebar", "Topbar": "Topbar" } };
const LiteGraph_Canvas_MaximumFps = { "name": "Maximum FPS", "tooltip": "The maximum frames per second that the canvas is allowed to render. Caps GPU usage at the cost of smoothness. If 0, the screen refresh rate is used. Default: 0" };
const LiteGraph_Canvas_MinFontSizeForLOD = { "name": "Zoom Node Level of Detail - font size threshold", "tooltip": "Controls when the nodes switch to low quality LOD rendering. Uses font size in pixels to determine when to switch. Set to 0 to disable. Values 1-24 set the minimum font size threshold for LOD - higher values (24px) = switch nodes to simplified rendering sooner when zooming out, lower values (1px) = maintain full node quality longer." };
const LiteGraph_ContextMenu_Scaling = { "name": "Scale node combo widget menus (lists) when zoomed in" };
const LiteGraph_Node_DefaultPadding = { "name": "Always shrink new nodes", "tooltip": "Resize nodes to the smallest possible size when created. When disabled, a newly added node will be widened slightly to show widget values." };
const LiteGraph_Node_TooltipDelay = { "name": "Tooltip Delay" };
const LiteGraph_Reroute_SplineOffset = { "name": "Reroute spline offset", "tooltip": "The bezier control point offset from the reroute centre point" };
const pysssss_SnapToGrid = { "name": "Always snap to grid" };
const enSettings = {
  "Comfy-Desktop_AutoUpdate": { "name": "Automatically check for updates" },
  "Comfy-Desktop_SendStatistics": { "name": "Send anonymous usage metrics" },
  "Comfy-Desktop_UV_PypiInstallMirror": { "name": "Pypi Install Mirror", "tooltip": "Default pip install mirror" },
  "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." },
  "Comfy-Desktop_UV_TorchInstallMirror": { "name": "Torch Install Mirror", "tooltip": "Pip install mirror for pytorch" },
  "Comfy-Desktop_WindowStyle": { "name": "Window Style", "tooltip": "Custom: Replace the system title bar with ComfyUI's Top menu", "options": { "default": "default", "custom": "custom" } },
  Comfy_Canvas_BackgroundImage,
  Comfy_Canvas_LeftMouseClickBehavior,
  Comfy_Canvas_MouseWheelScroll,
  Comfy_Canvas_NavigationMode,
  Comfy_Canvas_SelectionToolbox,
  Comfy_ConfirmClear,
  Comfy_DevMode,
  Comfy_DisableFloatRounding,
  Comfy_DisableSliders,
  Comfy_DOMClippingEnabled,
  Comfy_EditAttention_Delta,
  Comfy_EnableTooltips,
  Comfy_EnableWorkflowViewRestore,
  Comfy_Execution_PreviewMethod,
  Comfy_FloatRoundingPrecision,
  Comfy_Graph_CanvasInfo,
  Comfy_Graph_CanvasMenu,
  Comfy_Graph_CtrlShiftZoom,
  Comfy_Graph_LinkMarkers,
  Comfy_Graph_LiveSelection,
  Comfy_Graph_ZoomSpeed,
  Comfy_Group_DoubleClickTitleToEdit,
  Comfy_GroupSelectedNodes_Padding,
  Comfy_LinkRelease_Action,
  Comfy_LinkRelease_ActionShift,
  Comfy_LinkRenderMode,
  Comfy_Load3D_3DViewerEnable,
  Comfy_Load3D_BackgroundColor,
  Comfy_Load3D_CameraType,
  Comfy_Load3D_LightAdjustmentIncrement,
  Comfy_Load3D_LightIntensity,
  Comfy_Load3D_LightIntensityMaximum,
  Comfy_Load3D_LightIntensityMinimum,
  Comfy_Load3D_PLYEngine,
  Comfy_Load3D_ShowGrid,
  Comfy_Locale,
  Comfy_MaskEditor_BrushAdjustmentSpeed,
  Comfy_MaskEditor_UseDominantAxis,
  Comfy_ModelLibrary_AutoLoadAll,
  Comfy_ModelLibrary_NameFormat,
  Comfy_Node_AllowImageSizeDraw,
  Comfy_Node_AutoSnapLinkToSlot,
  Comfy_Node_BypassAllLinksOnDelete,
  Comfy_Node_DoubleClickTitleToEdit,
  Comfy_Node_MiddleClickRerouteNode,
  Comfy_Node_Opacity,
  Comfy_Node_ShowDeprecated,
  Comfy_Node_ShowExperimental,
  Comfy_Node_SnapHighlightsNode,
  Comfy_NodeBadge_NodeIdBadgeMode,
  Comfy_NodeBadge_NodeLifeCycleBadgeMode,
  Comfy_NodeBadge_NodeSourceBadgeMode,
  Comfy_NodeBadge_ShowApiPricing,
  Comfy_NodeSearchBoxImpl,
  Comfy_NodeSearchBoxImpl_NodePreview,
  Comfy_NodeSearchBoxImpl_ShowCategory,
  Comfy_NodeSearchBoxImpl_ShowIdName,
  Comfy_NodeSearchBoxImpl_ShowNodeFrequency,
  Comfy_NodeSuggestions_number,
  Comfy_Notification_ShowVersionUpdates,
  Comfy_Pointer_ClickBufferTime,
  Comfy_Pointer_ClickDrift,
  Comfy_Pointer_DoubleClickTime,
  Comfy_PreviewFormat,
  Comfy_PromptFilename,
  Comfy_Queue_MaxHistoryItems,
  Comfy_QueueButton_BatchCountLimit,
  Comfy_Sidebar_Location,
  Comfy_Sidebar_Size,
  Comfy_Sidebar_Style,
  Comfy_Sidebar_UnifiedWidth,
  Comfy_SnapToGrid_GridSize,
  Comfy_TextareaWidget_FontSize,
  Comfy_TextareaWidget_Spellcheck,
  Comfy_TreeExplorer_ItemPadding,
  Comfy_UseNewMenu,
  Comfy_Validation_Workflows,
  Comfy_VueNodes_AutoScaleLayout,
  Comfy_VueNodes_Enabled,
  Comfy_WidgetControlMode,
  Comfy_Window_UnloadConfirmation,
  Comfy_Workflow_AutoSave,
  Comfy_Workflow_AutoSaveDelay,
  Comfy_Workflow_ConfirmDelete,
  Comfy_Workflow_Persist,
  Comfy_Workflow_ShowMissingModelsWarning,
  Comfy_Workflow_ShowMissingNodesWarning,
  Comfy_Workflow_SortNodeIdOnSave,
  Comfy_Workflow_WarnBlueprintOverwrite,
  Comfy_Workflow_WorkflowTabsPosition,
  LiteGraph_Canvas_MaximumFps,
  LiteGraph_Canvas_MinFontSizeForLOD,
  LiteGraph_ContextMenu_Scaling,
  LiteGraph_Node_DefaultPadding,
  LiteGraph_Node_TooltipDelay,
  LiteGraph_Reroute_SplineOffset,
  pysssss_SnapToGrid
};
function buildLocale(main, nodes, commands2, settings) {
  return {
    ...main,
    nodeDefs: nodes,
    commands: commands2,
    settings
  };
}
__name(buildLocale, "buildLocale");
const localeLoaders = {
  ar: /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-Bdc58rJq.js"), true ? [] : void 0, import.meta.url), "ar"),
  es: /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-C9ZJBRdI.js"), true ? [] : void 0, import.meta.url), "es"),
  fr: /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-CAL83XT3.js"), true ? [] : void 0, import.meta.url), "fr"),
  ja: /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-DLHyaEcz.js"), true ? [] : void 0, import.meta.url), "ja"),
  ko: /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-CHLLfvpG.js"), true ? [] : void 0, import.meta.url), "ko"),
  ru: /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-OzGsrlqJ.js"), true ? [] : void 0, import.meta.url), "ru"),
  tr: /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-O7KfJeMO.js"), true ? [] : void 0, import.meta.url), "tr"),
  zh: /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-BRKE6VDs.js"), true ? [] : void 0, import.meta.url), "zh"),
  "zh-TW": /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-DDqR5EuX.js"), true ? [] : void 0, import.meta.url), "zh-TW"),
  "pt-BR": /* @__PURE__ */ __name(() => __vitePreload(() => import("./main-Cw9RZWRY.js"), true ? [] : void 0, import.meta.url), "pt-BR")
};
const nodeDefsLoaders = {
  ar: /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-Dz-0ZIBN.js"), true ? [] : void 0, import.meta.url), "ar"),
  es: /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-aW9En70v.js"), true ? [] : void 0, import.meta.url), "es"),
  fr: /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-BiYpVi7D.js"), true ? [] : void 0, import.meta.url), "fr"),
  ja: /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-BIckSVgU.js"), true ? [] : void 0, import.meta.url), "ja"),
  ko: /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-D_wreoPJ.js"), true ? [] : void 0, import.meta.url), "ko"),
  ru: /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-CvmVDWYd.js"), true ? [] : void 0, import.meta.url), "ru"),
  tr: /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-VZsNmhG7.js"), true ? [] : void 0, import.meta.url), "tr"),
  zh: /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-DIAYlJwM.js"), true ? [] : void 0, import.meta.url), "zh"),
  "zh-TW": /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-CCEXtYfM.js"), true ? [] : void 0, import.meta.url), "zh-TW"),
  "pt-BR": /* @__PURE__ */ __name(() => __vitePreload(() => import("./nodeDefs-Bw_Jitw_.js"), true ? [] : void 0, import.meta.url), "pt-BR")
};
const commandsLoaders = {
  ar: /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-COSt-Bjx.js"), true ? [] : void 0, import.meta.url), "ar"),
  es: /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-CisfgZf5.js"), true ? [] : void 0, import.meta.url), "es"),
  fr: /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-CoH2DJa6.js"), true ? [] : void 0, import.meta.url), "fr"),
  ja: /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-CcfGaui5.js"), true ? [] : void 0, import.meta.url), "ja"),
  ko: /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-DfTl0eCm.js"), true ? [] : void 0, import.meta.url), "ko"),
  ru: /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-DalfIW5f.js"), true ? [] : void 0, import.meta.url), "ru"),
  tr: /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-DwSJL865.js"), true ? [] : void 0, import.meta.url), "tr"),
  zh: /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-bTEY9Mp6.js"), true ? [] : void 0, import.meta.url), "zh"),
  "zh-TW": /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-CkU12Foh.js"), true ? [] : void 0, import.meta.url), "zh-TW"),
  "pt-BR": /* @__PURE__ */ __name(() => __vitePreload(() => import("./commands-BWp4HdfU.js"), true ? [] : void 0, import.meta.url), "pt-BR")
};
const settingsLoaders = {
  ar: /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-DkGwvylK.js"), true ? [] : void 0, import.meta.url), "ar"),
  es: /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-Bu3OR-lX.js"), true ? [] : void 0, import.meta.url), "es"),
  fr: /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-ByL6gy5c.js"), true ? [] : void 0, import.meta.url), "fr"),
  ja: /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-BIdKi-OT.js"), true ? [] : void 0, import.meta.url), "ja"),
  ko: /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-xx2Yb6R2.js"), true ? [] : void 0, import.meta.url), "ko"),
  ru: /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-CjlRFMdL.js"), true ? [] : void 0, import.meta.url), "ru"),
  tr: /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-Dyd027Dx.js"), true ? [] : void 0, import.meta.url), "tr"),
  zh: /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-AN2JfZVQ.js"), true ? [] : void 0, import.meta.url), "zh"),
  "zh-TW": /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-wwBxqLH5.js"), true ? [] : void 0, import.meta.url), "zh-TW"),
  "pt-BR": /* @__PURE__ */ __name(() => __vitePreload(() => import("./settings-BhbWhsRg.js"), true ? [] : void 0, import.meta.url), "pt-BR")
};
const loadedLocales = /* @__PURE__ */ new Set(["en"]);
const loadingLocales = /* @__PURE__ */ new Map();
const customNodesI18nData = {};
async function loadLocale(locale) {
  if (loadedLocales.has(locale)) {
    return;
  }
  const existingLoad = loadingLocales.get(locale);
  if (existingLoad) {
    return existingLoad;
  }
  const loader = localeLoaders[locale];
  const nodeDefsLoader = nodeDefsLoaders[locale];
  const commandsLoader = commandsLoaders[locale];
  const settingsLoader = settingsLoaders[locale];
  if (!loader || !nodeDefsLoader || !commandsLoader || !settingsLoader) {
    console.warn(`Locale "${locale}" is not supported`);
    return;
  }
  const loadPromise = (async () => {
    try {
      const [main, nodes, commands2, settings] = await Promise.all([
        loader(),
        nodeDefsLoader(),
        commandsLoader(),
        settingsLoader()
      ]);
      const messages2 = buildLocale(
        main.default,
        nodes.default,
        commands2.default,
        settings.default
      );
      i18n.global.setLocaleMessage(locale, messages2);
      loadedLocales.add(locale);
      if (customNodesI18nData[locale]) {
        i18n.global.mergeLocaleMessage(locale, customNodesI18nData[locale]);
      }
    } catch (error) {
      console.error(`Failed to load locale "${locale}":`, error);
      throw error;
    } finally {
      loadingLocales.delete(locale);
    }
  })();
  loadingLocales.set(locale, loadPromise);
  return loadPromise;
}
__name(loadLocale, "loadLocale");
function mergeCustomNodesI18n(i18nData) {
  for (const key of Object.keys(customNodesI18nData)) {
    delete customNodesI18nData[key];
  }
  Object.assign(customNodesI18nData, i18nData);
  for (const [locale, message] of Object.entries(i18nData)) {
    if (loadedLocales.has(locale)) {
      i18n.global.mergeLocaleMessage(locale, message);
    }
  }
}
__name(mergeCustomNodesI18n, "mergeCustomNodesI18n");
const messages = {
  en: buildLocale(en, enNodes, enCommands, enSettings)
};
const i18n = createI18n({
  // Must set `false`, as Vue I18n Legacy API is for Vue 2
  legacy: false,
  locale: navigator.language.split("-")[0] || "en",
  fallbackLocale: "en",
  escapeParameter: true,
  messages,
  // Ignore warnings for locale options as each option is in its own language.
  // e.g. "English", "中文", "Русский", "日本語", "한국어", "Français", "Español"
  missingWarn: /^(?!settings\.Comfy_Locale\.options\.).+/,
  fallbackWarn: /^(?!settings\.Comfy_Locale\.options\.).+/
});
const { t, te, d } = i18n.global;
function st(key, fallbackMessage) {
  return te(key) ? t(key) : fallbackMessage;
}
__name(st, "st");
function useExternalLink() {
  const locale = computed(() => String(i18n.global.locale.value));
  const isChinese = computed(() => {
    return locale.value === "zh" || locale.value === "zh-TW";
  });
  const platform = computed(() => {
    if (!isElectron()) {
      return null;
    }
    const electronPlatform = electronAPI().getPlatform();
    return electronPlatform === "darwin" ? "macos" : "windows";
  });
  const buildDocsUrl = /* @__PURE__ */ __name((path, options = {}) => {
    const { includeLocale = false, platform: includePlatform = false } = options;
    let url = "https://docs.comfy.org";
    if (includeLocale && isChinese.value) {
      url += "/zh-CN";
    }
    const normalizedPath = path.startsWith("/") ? path : `/${path}`;
    url += normalizedPath;
    if (includePlatform && platform.value) {
      url = url.endsWith("/") ? url : `${url}/`;
      url += platform.value;
    }
    return url;
  }, "buildDocsUrl");
  const staticUrls = {
    // Static external URLs
    discord: "https://www.comfy.org/discord",
    github: "https://github.com/comfyanonymous/ComfyUI",
    githubIssues: "https://github.com/comfyanonymous/ComfyUI/issues",
    githubFrontend: "https://github.com/Comfy-Org/ComfyUI_frontend",
    githubElectron: "https://github.com/Comfy-Org/electron",
    forum: "https://forum.comfy.org/",
    comfyOrg: "https://www.comfy.org/"
  };
  const docsPaths = {
    partnerNodesPricing: "/tutorials/partner-nodes/pricing"
  };
  return {
    buildDocsUrl,
    staticUrls,
    docsPaths
  };
}
__name(useExternalLink, "useExternalLink");
const _hoisted_1$25 = { class: "flex h-110 max-w-96 flex-col gap-4 p-2" };
const _hoisted_2$1D = { class: "mb-2 text-2xl font-medium" };
const _hoisted_3$14 = { class: "mb-4 text-base" };
const _hoisted_4$O = { class: "flex items-center justify-between" };
const _hoisted_5$G = { class: "flex gap-2" };
const _sfc_main$2z = /* @__PURE__ */ defineComponent({
  __name: "ApiNodesSignInContent",
  props: {
    apiNodeNames: {},
    onLogin: { type: Function },
    onCancel: { type: Function }
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const { buildDocsUrl } = useExternalLink();
    const handleLearnMoreClick = /* @__PURE__ */ __name(() => {
      window.open(
        buildDocsUrl("/tutorials/api-nodes/faq", { includeLocale: true }),
        "_blank"
      );
    }, "handleLearnMoreClick");
    return (_ctx, _cache) => {
      const _component_ApiNodesList = resolveComponent("ApiNodesList");
      return openBlock(), createElementBlock("div", _hoisted_1$25, [
        createBaseVNode("div", _hoisted_2$1D, toDisplayString(unref(t2)("apiNodesSignInDialog.title")), 1),
        createBaseVNode("div", _hoisted_3$14, toDisplayString(unref(t2)("apiNodesSignInDialog.message")), 1),
        createVNode(_component_ApiNodesList, { "node-names": _ctx.apiNodeNames }, null, 8, ["node-names"]),
        createBaseVNode("div", _hoisted_4$O, [
          createVNode(_sfc_main$2A, {
            variant: "textonly",
            onClick: handleLearnMoreClick
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(unref(t2)("g.learnMore")), 1)
            ]),
            _: 1
          }),
          createBaseVNode("div", _hoisted_5$G, [
            createVNode(_sfc_main$2A, {
              variant: "secondary",
              onClick: _cache[0] || (_cache[0] = ($event) => _ctx.onCancel?.())
            }, {
              default: withCtx(() => [
                createTextVNode(toDisplayString(unref(t2)("g.cancel")), 1)
              ]),
              _: 1
            }),
            createVNode(_sfc_main$2A, {
              onClick: _cache[1] || (_cache[1] = ($event) => _ctx.onLogin?.())
            }, {
              default: withCtx(() => [
                createTextVNode(toDisplayString(unref(t2)("g.login")), 1)
              ]),
              _: 1
            })
          ])
        ])
      ]);
    };
  }
});
const useSystemStatsStore = defineStore("systemStats", () => {
  const fetchSystemStatsData = /* @__PURE__ */ __name(async () => {
    try {
      return await api.getSystemStats();
    } catch (err) {
      console.error("Error fetching system stats:", err);
      throw err;
    }
  }, "fetchSystemStatsData");
  const {
    state: systemStats,
    isLoading,
    error,
    isReady: isInitialized2,
    execute: refetchSystemStats
  } = useAsyncState(
    fetchSystemStatsData,
    null,
    // initial value
    {
      immediate: true
    }
  );
  function getFormFactor() {
    if (!systemStats.value?.system?.os) {
      return "other";
    }
    const os = systemStats.value.system.os.toLowerCase();
    const isDesktop2 = isElectron();
    if (isDesktop2) {
      if (os.includes("windows")) {
        return "desktop-windows";
      }
      if (os.includes("darwin") || os.includes("mac")) {
        return "desktop-mac";
      }
    } else {
      if (os.includes("windows")) {
        return "git-windows";
      }
      if (os.includes("darwin") || os.includes("mac")) {
        return "git-mac";
      }
      if (os.includes("linux")) {
        return "git-linux";
      }
    }
    return "other";
  }
  __name(getFormFactor, "getFormFactor");
  return {
    systemStats,
    isLoading,
    error,
    isInitialized: isInitialized2,
    refetchSystemStats,
    getFormFactor
  };
});
const _hoisted_1$24 = { class: "flex flex-col gap-2" };
const _hoisted_2$1C = { class: "text-sm font-medium text-surface-600" };
const _hoisted_3$13 = { class: "ml-4 text-sm text-surface-500" };
const _sfc_main$2y = /* @__PURE__ */ defineComponent({
  __name: "MissingCoreNodesMessage",
  props: {
    missingCoreNodes: {}
  },
  setup(__props) {
    const systemStatsStore = useSystemStatsStore();
    const hasMissingCoreNodes = computed(() => {
      return Object.keys(__props.missingCoreNodes).length > 0;
    });
    const currentComfyUIVersion = computed(() => {
      if (!hasMissingCoreNodes.value) return null;
      return systemStatsStore.systemStats?.system?.comfyui_version ?? null;
    });
    const sortedMissingCoreNodes = computed(() => {
      return Object.entries(__props.missingCoreNodes).sort(([a], [b]) => {
        return semverExports.compare(b, a);
      });
    });
    const getUniqueNodeNames = /* @__PURE__ */ __name((nodes) => {
      return nodes.reduce((acc, node2) => {
        if (node2.type && !acc.includes(node2.type)) {
          acc.push(node2.type);
        }
        return acc;
      }, []).sort();
    }, "getUniqueNodeNames");
    return (_ctx, _cache) => {
      return hasMissingCoreNodes.value ? (openBlock(), createBlock(unref(script), {
        key: 0,
        severity: "info",
        icon: "pi pi-info-circle",
        class: "mx-2 my-2",
        pt: {
          root: { class: "flex-col" },
          text: { class: "flex-1" }
        }
      }, {
        default: withCtx(() => [
          createBaseVNode("div", _hoisted_1$24, [
            createBaseVNode("div", null, toDisplayString(currentComfyUIVersion.value ? _ctx.$t("loadWorkflowWarning.outdatedVersion", {
              version: currentComfyUIVersion.value
            }) : _ctx.$t("loadWorkflowWarning.outdatedVersionGeneric")), 1),
            (openBlock(true), createElementBlock(Fragment, null, renderList(sortedMissingCoreNodes.value, ([version, nodes]) => {
              return openBlock(), createElementBlock("div", {
                key: version,
                class: "ml-4"
              }, [
                createBaseVNode("div", _hoisted_2$1C, toDisplayString(_ctx.$t("loadWorkflowWarning.coreNodesFromVersion", {
                  version: version || "unknown"
                })), 1),
                createBaseVNode("div", _hoisted_3$13, toDisplayString(getUniqueNodeNames(nodes).join(", ")), 1)
              ]);
            }), 128))
          ])
        ]),
        _: 1
      })) : createCommentVNode("", true);
    };
  }
});
let sharedConverter = null;
const useCanvasPositionConversion = /* @__PURE__ */ __name((canvasElement, lgCanvas) => {
  const { left, top, update } = useElementBounding(canvasElement);
  const clientPosToCanvasPos = /* @__PURE__ */ __name((pos) => {
    const { offset, scale } = lgCanvas.ds;
    return [
      (pos[0] - left.value) / scale - offset[0],
      (pos[1] - top.value) / scale - offset[1]
    ];
  }, "clientPosToCanvasPos");
  const canvasPosToClientPos = /* @__PURE__ */ __name((pos) => {
    const { offset, scale } = lgCanvas.ds;
    return [
      (pos[0] + offset[0]) * scale + left.value,
      (pos[1] + offset[1]) * scale + top.value
    ];
  }, "canvasPosToClientPos");
  return {
    clientPosToCanvasPos,
    canvasPosToClientPos,
    update
  };
}, "useCanvasPositionConversion");
function useSharedCanvasPositionConversion() {
  if (sharedConverter) return sharedConverter;
  const lgCanvas = useCanvasStore().getCanvas();
  sharedConverter = useCanvasPositionConversion(lgCanvas.canvas, lgCanvas);
  return sharedConverter;
}
__name(useSharedCanvasPositionConversion, "useSharedCanvasPositionConversion");
const proxyWidgetsPropertySchema = z.array(z.tuple([z.string(), z.string()]));
function parseProxyWidgets(property) {
  if (typeof property === "string") property = JSON.parse(property);
  const result = proxyWidgetsPropertySchema.safeParse(
    typeof property === "string" ? JSON.parse(property) : property
  );
  if (result.success) return result.data;
  const error = fromZodError(result.error);
  throw new Error(`Invalid assignment for properties.proxyWidgets:
${error}`);
}
__name(parseProxyWidgets, "parseProxyWidgets");
const DEFAULT_DOWNLOAD_FILENAME = "download.png";
function triggerLinkDownload(href, filename) {
  const link = document.createElement("a");
  link.href = href;
  link.download = filename;
  link.style.display = "none";
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}
__name(triggerLinkDownload, "triggerLinkDownload");
function downloadFile(url, filename) {
  if (!url || typeof url !== "string" || url.trim().length === 0) {
    throw new Error("Invalid URL provided for download");
  }
  const inferredFilename = filename || extractFilenameFromUrl(url) || DEFAULT_DOWNLOAD_FILENAME;
  triggerLinkDownload(url, inferredFilename);
}
__name(downloadFile, "downloadFile");
function downloadBlob(filename, blob) {
  const url = URL.createObjectURL(blob);
  triggerLinkDownload(url, filename);
  queueMicrotask(() => URL.revokeObjectURL(url));
}
__name(downloadBlob, "downloadBlob");
const extractFilenameFromUrl = /* @__PURE__ */ __name((url) => {
  try {
    const urlObj = new URL(url, window.location.origin);
    return urlObj.searchParams.get("filename");
  } catch {
    return null;
  }
}, "extractFilenameFromUrl");
class ContextMenu {
  static {
    __name(this, "ContextMenu");
  }
  options;
  parentMenu;
  root;
  current_submenu;
  lock;
  controller = new AbortController();
  /**
   * @todo Interface for values requires functionality change - currently accepts
   * an array of strings, functions, objects, nulls, or undefined.
   * @param values (allows object { title: "Nice text", callback: function ... })
   * @param options [optional] Some options:\
   * - title: title to show on top of the menu
   * - callback: function to call when an option is clicked, it receives the item information
   * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback
   * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position
   */
  constructor(values, options) {
    options ||= {};
    this.options = options;
    const parent = options.parentMenu;
    if (parent) {
      if (!(parent instanceof ContextMenu)) {
        console.error("parentMenu must be of class ContextMenu, ignoring it");
        options.parentMenu = void 0;
      } else {
        this.parentMenu = parent;
        this.parentMenu.lock = true;
        this.parentMenu.current_submenu = this;
      }
      if (parent.options?.className === "dark") {
        options.className = "dark";
      }
    }
    const eventClass = options.event ? options.event.constructor.name : null;
    if (eventClass !== "MouseEvent" && eventClass !== "CustomEvent" && eventClass !== "PointerEvent") {
      console.error(
        `Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. (${eventClass})`
      );
      options.event = void 0;
    }
    const root = document.createElement("div");
    let classes = "litegraph litecontextmenu litemenubar-panel";
    if (options.className) classes += ` ${options.className}`;
    root.className = classes;
    root.style.minWidth = "100";
    root.style.minHeight = "100";
    const { signal } = this.controller;
    const eventOptions = { capture: true, signal };
    if (!this.parentMenu) {
      document.addEventListener(
        "pointerdown",
        (e2) => {
          if (e2.target instanceof Node && !this.containsNode(e2.target)) {
            this.close();
          }
        },
        eventOptions
      );
    }
    root.addEventListener("pointerup", (e2) => e2.preventDefault(), eventOptions);
    root.addEventListener(
      "contextmenu",
      (e2) => {
        if (e2.button === 2) e2.preventDefault();
      },
      eventOptions
    );
    root.addEventListener(
      "pointerdown",
      (e2) => {
        if (e2.button == 2) {
          this.close();
          e2.preventDefault();
        }
      },
      eventOptions
    );
    this.root = root;
    if (options.title) {
      const element = document.createElement("div");
      element.className = "litemenu-title";
      element.innerHTML = options.title;
      root.append(element);
    }
    for (let i = 0; i < values.length; i++) {
      const value = values[i];
      let name2 = Array.isArray(values) ? value : String(i);
      if (typeof name2 !== "string") {
        name2 = name2 != null ? name2.content === void 0 ? String(name2) : name2.content : name2;
      }
      this.addItem(name2, value, options);
    }
    const ownerDocument = options.event?.target?.ownerDocument;
    const root_document = ownerDocument || document;
    if (root_document.fullscreenElement)
      root_document.fullscreenElement.append(root);
    else root_document.body.append(root);
    let left = options.left || 0;
    let top = options.top || 0;
    if (options.event) {
      left = options.event.clientX - 10;
      top = options.event.clientY - 10;
      if (options.title) top -= 20;
      if (parent) {
        const rect = parent.root.getBoundingClientRect();
        left = rect.left + rect.width;
      }
      const body_rect = document.body.getBoundingClientRect();
      const root_rect = root.getBoundingClientRect();
      if (body_rect.height == 0)
        console.error(
          "document.body height is 0. That is dangerous, set html,body { height: 100%; }"
        );
      if (body_rect.width && left > body_rect.width - root_rect.width - 10)
        left = body_rect.width - root_rect.width - 10;
      if (body_rect.height && top > body_rect.height - root_rect.height - 10)
        top = body_rect.height - root_rect.height - 10;
    }
    root.style.left = `${left}px`;
    root.style.top = `${top}px`;
    if (LiteGraph.context_menu_scaling && options.scale) {
      root.style.transform = `scale(${Math.round(options.scale * 4) * 0.25})`;
    }
  }
  /**
   * Checks if {@link node} is inside this context menu or any of its submenus
   * @param node The {@link Node} to check
   * @param visited A set of visited menus to avoid circular references
   * @returns `true` if {@link node} is inside this context menu or any of its submenus
   */
  containsNode(node2, visited = /* @__PURE__ */ new Set()) {
    if (visited.has(this)) return false;
    visited.add(this);
    return this.current_submenu?.containsNode(node2, visited) || this.root.contains(node2);
  }
  addItem(name2, value, options) {
    options ||= {};
    const element = document.createElement("div");
    element.className = "litemenu-entry submenu";
    let disabled = false;
    if (value === null) {
      element.classList.add("separator");
    } else {
      const innerHtml = name2 === null ? "" : String(name2);
      if (typeof value === "string") {
        element.innerHTML = innerHtml;
      } else {
        element.innerHTML = value?.title ?? innerHtml;
        if (value.disabled) {
          disabled = true;
          element.classList.add("disabled");
          element.setAttribute("aria-disabled", "true");
        }
        if (value.submenu || value.has_submenu) {
          element.classList.add("has_submenu");
          element.setAttribute("aria-haspopup", "true");
          element.setAttribute("aria-expanded", "false");
        }
        if (value.className) element.className += ` ${value.className}`;
      }
      element.value = value;
      element.setAttribute("role", "menuitem");
      if (typeof value === "function") {
        element.dataset["value"] = String(name2);
        element.onclick_callback = value;
      } else {
        element.dataset["value"] = String(value);
      }
    }
    this.root.append(element);
    if (!disabled) element.addEventListener("click", inner_onclick);
    if (!disabled && options.autoopen)
      element.addEventListener("pointerenter", inner_over);
    const setAriaExpanded = /* @__PURE__ */ __name(() => {
      const entries = this.root.querySelectorAll(
        "div.litemenu-entry.has_submenu"
      );
      if (entries) {
        for (const entry of entries) {
          entry.setAttribute("aria-expanded", "false");
        }
      }
      element.setAttribute("aria-expanded", "true");
    }, "setAriaExpanded");
    function inner_over(e2) {
      const value2 = this.value;
      if (!value2 || !value2.has_submenu) return;
      inner_onclick.call(this, e2);
      setAriaExpanded();
    }
    __name(inner_over, "inner_over");
    const that = this;
    function inner_onclick(e2) {
      const value2 = this.value;
      let close_parent = true;
      that.current_submenu?.close(e2);
      if (value2?.has_submenu || value2?.submenu) {
        setAriaExpanded();
      }
      if (options.callback) {
        const r = options.callback.call(
          this,
          value2,
          options,
          e2,
          that,
          options.node
        );
        if (r === true) close_parent = false;
      }
      if (typeof value2 === "object") {
        if (value2.callback && !options.ignore_item_callbacks && value2.disabled !== true) {
          const r = value2.callback.call(
            this,
            value2,
            options,
            e2,
            that,
            options.extra
          );
          if (r === true) close_parent = false;
        }
        if (value2.submenu) {
          if (!value2.submenu.options) throw "ContextMenu submenu needs options";
          new that.constructor(value2.submenu.options, {
            callback: value2.submenu.callback,
            event: e2,
            parentMenu: that,
            ignore_item_callbacks: value2.submenu.ignore_item_callbacks,
            title: value2.submenu.title,
            extra: value2.submenu.extra,
            autoopen: options.autoopen
          });
          close_parent = false;
        }
      }
      if (close_parent && !that.lock) that.close();
    }
    __name(inner_onclick, "inner_onclick");
    return element;
  }
  close(e2, ignore_parent_menu) {
    this.controller.abort();
    this.root.remove();
    if (this.parentMenu && !ignore_parent_menu) {
      this.parentMenu.lock = false;
      this.parentMenu.current_submenu = void 0;
      if (e2 === void 0) {
        this.parentMenu.close();
      } else if (e2 && !ContextMenu.isCursorOverElement(e2, this.parentMenu.root)) {
        ContextMenu.trigger(
          this.parentMenu.root,
          `${LiteGraph.pointerevents_method}leave`,
          e2
        );
      }
    }
    this.current_submenu?.close(e2, true);
  }
  /** @deprecated Likely unused, however code search was inconclusive (too many results to check by hand). */
  // this code is used to trigger events easily (used in the context menu mouseleave
  static trigger(element, event_name, params) {
    const evt = document.createEvent("CustomEvent");
    evt.initCustomEvent(event_name, true, true, params);
    if (element.dispatchEvent) element.dispatchEvent(evt);
    return evt;
  }
  // returns the top most menu
  getTopMenu() {
    return this.options.parentMenu ? this.options.parentMenu.getTopMenu() : this;
  }
  getFirstEvent() {
    return this.options.parentMenu ? this.options.parentMenu.getFirstEvent() : this.options.event;
  }
  /** @deprecated Unused. */
  static isCursorOverElement(event, element) {
    const left = event.clientX;
    const top = event.clientY;
    const rect = element.getBoundingClientRect();
    if (!rect) return false;
    if (top > rect.top && top < rect.top + rect.height && left > rect.left && left < rect.left + rect.width) {
      return true;
    }
    return false;
  }
}
var NodeSlotType = /* @__PURE__ */ ((NodeSlotType2) => {
  NodeSlotType2[NodeSlotType2["INPUT"] = 1] = "INPUT";
  NodeSlotType2[NodeSlotType2["OUTPUT"] = 2] = "OUTPUT";
  return NodeSlotType2;
})(NodeSlotType || {});
var RenderShape = /* @__PURE__ */ ((RenderShape2) => {
  RenderShape2[RenderShape2["BOX"] = 1] = "BOX";
  RenderShape2[RenderShape2["ROUND"] = 2] = "ROUND";
  RenderShape2[RenderShape2["CIRCLE"] = 3] = "CIRCLE";
  RenderShape2[RenderShape2["CARD"] = 4] = "CARD";
  RenderShape2[RenderShape2["ARROW"] = 5] = "ARROW";
  RenderShape2[RenderShape2["GRID"] = 6] = "GRID";
  RenderShape2[RenderShape2["HollowCircle"] = 7] = "HollowCircle";
  return RenderShape2;
})(RenderShape || {});
var CanvasItem = /* @__PURE__ */ ((CanvasItem2) => {
  CanvasItem2[CanvasItem2["Nothing"] = 0] = "Nothing";
  CanvasItem2[CanvasItem2["Node"] = 1] = "Node";
  CanvasItem2[CanvasItem2["Group"] = 2] = "Group";
  CanvasItem2[CanvasItem2["Reroute"] = 4] = "Reroute";
  CanvasItem2[CanvasItem2["Link"] = 8] = "Link";
  CanvasItem2[CanvasItem2["RerouteSlot"] = 32] = "RerouteSlot";
  CanvasItem2[CanvasItem2["SubgraphIoNode"] = 64] = "SubgraphIoNode";
  CanvasItem2[CanvasItem2["SubgraphIoSlot"] = 128] = "SubgraphIoSlot";
  return CanvasItem2;
})(CanvasItem || {});
var LinkDirection = /* @__PURE__ */ ((LinkDirection2) => {
  LinkDirection2[LinkDirection2["NONE"] = 0] = "NONE";
  LinkDirection2[LinkDirection2["UP"] = 1] = "UP";
  LinkDirection2[LinkDirection2["DOWN"] = 2] = "DOWN";
  LinkDirection2[LinkDirection2["LEFT"] = 3] = "LEFT";
  LinkDirection2[LinkDirection2["RIGHT"] = 4] = "RIGHT";
  LinkDirection2[LinkDirection2["CENTER"] = 5] = "CENTER";
  return LinkDirection2;
})(LinkDirection || {});
var LinkRenderType = /* @__PURE__ */ ((LinkRenderType2) => {
  LinkRenderType2[LinkRenderType2["HIDDEN_LINK"] = -1] = "HIDDEN_LINK";
  LinkRenderType2[LinkRenderType2["STRAIGHT_LINK"] = 0] = "STRAIGHT_LINK";
  LinkRenderType2[LinkRenderType2["LINEAR_LINK"] = 1] = "LINEAR_LINK";
  LinkRenderType2[LinkRenderType2["SPLINE_LINK"] = 2] = "SPLINE_LINK";
  return LinkRenderType2;
})(LinkRenderType || {});
var LinkMarkerShape = /* @__PURE__ */ ((LinkMarkerShape2) => {
  LinkMarkerShape2[LinkMarkerShape2["None"] = 0] = "None";
  LinkMarkerShape2[LinkMarkerShape2["Circle"] = 1] = "Circle";
  LinkMarkerShape2[LinkMarkerShape2["Arrow"] = 2] = "Arrow";
  return LinkMarkerShape2;
})(LinkMarkerShape || {});
var TitleMode = /* @__PURE__ */ ((TitleMode2) => {
  TitleMode2[TitleMode2["NORMAL_TITLE"] = 0] = "NORMAL_TITLE";
  TitleMode2[TitleMode2["NO_TITLE"] = 1] = "NO_TITLE";
  TitleMode2[TitleMode2["TRANSPARENT_TITLE"] = 2] = "TRANSPARENT_TITLE";
  TitleMode2[TitleMode2["AUTOHIDE_TITLE"] = 3] = "AUTOHIDE_TITLE";
  return TitleMode2;
})(TitleMode || {});
var LGraphEventMode = /* @__PURE__ */ ((LGraphEventMode2) => {
  LGraphEventMode2[LGraphEventMode2["ALWAYS"] = 0] = "ALWAYS";
  LGraphEventMode2[LGraphEventMode2["ON_EVENT"] = 1] = "ON_EVENT";
  LGraphEventMode2[LGraphEventMode2["NEVER"] = 2] = "NEVER";
  LGraphEventMode2[LGraphEventMode2["ON_TRIGGER"] = 3] = "ON_TRIGGER";
  LGraphEventMode2[LGraphEventMode2["BYPASS"] = 4] = "BYPASS";
  return LGraphEventMode2;
})(LGraphEventMode || {});
var EaseFunction = /* @__PURE__ */ ((EaseFunction2) => {
  EaseFunction2["EASE_IN_OUT_QUAD"] = "easeInOutQuad";
  return EaseFunction2;
})(EaseFunction || {});
var Alignment = /* @__PURE__ */ ((Alignment2) => {
  Alignment2[Alignment2["None"] = 0] = "None";
  Alignment2[Alignment2["Top"] = 1] = "Top";
  Alignment2[Alignment2["Bottom"] = 2] = "Bottom";
  Alignment2[Alignment2["Middle"] = 4] = "Middle";
  Alignment2[Alignment2["Left"] = 8] = "Left";
  Alignment2[Alignment2["Right"] = 16] = "Right";
  Alignment2[Alignment2["Centre"] = 32] = "Centre";
  Alignment2[Alignment2["TopLeft"] = 9] = "TopLeft";
  Alignment2[Alignment2["TopCentre"] = 33] = "TopCentre";
  Alignment2[Alignment2["TopRight"] = 17] = "TopRight";
  Alignment2[Alignment2["MidLeft"] = 12] = "MidLeft";
  Alignment2[Alignment2["MidCentre"] = 36] = "MidCentre";
  Alignment2[Alignment2["MidRight"] = 20] = "MidRight";
  Alignment2[Alignment2["BottomLeft"] = 10] = "BottomLeft";
  Alignment2[Alignment2["BottomCentre"] = 34] = "BottomCentre";
  Alignment2[Alignment2["BottomRight"] = 18] = "BottomRight";
  return Alignment2;
})(Alignment || {});
function hasFlag(flagSet, flag) {
  return (flagSet & flag) === flag;
}
__name(hasFlag, "hasFlag");
function distance(a, b) {
  return Math.sqrt(
    (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])
  );
}
__name(distance, "distance");
function dist2(x1, y1, x2, y2) {
  return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
}
__name(dist2, "dist2");
function isInRectangle(x2, y, left, top, width2, height) {
  return x2 >= left && x2 < left + width2 && y >= top && y < top + height;
}
__name(isInRectangle, "isInRectangle");
function isPointInRect(point, rect) {
  return point[0] >= rect[0] && point[0] < rect[0] + rect[2] && point[1] >= rect[1] && point[1] < rect[1] + rect[3];
}
__name(isPointInRect, "isPointInRect");
function isInRect(x2, y, rect) {
  return x2 >= rect[0] && x2 < rect[0] + rect[2] && y >= rect[1] && y < rect[1] + rect[3];
}
__name(isInRect, "isInRect");
function isInsideRectangle(x2, y, left, top, width2, height) {
  return left < x2 && left + width2 > x2 && top < y && top + height > y;
}
__name(isInsideRectangle, "isInsideRectangle");
function overlapBounding(a, b) {
  const aRight = a[0] + a[2];
  const aBottom = a[1] + a[3];
  const bRight = b[0] + b[2];
  const bBottom = b[1] + b[3];
  return a[0] > bRight || a[1] > bBottom || aRight < b[0] || aBottom < b[1] ? false : true;
}
__name(overlapBounding, "overlapBounding");
function getCentre(rect) {
  return [rect[0] + rect[2] * 0.5, rect[1] + rect[3] * 0.5];
}
__name(getCentre, "getCentre");
function containsCentre(a, b) {
  const centreX = b[0] + b[2] * 0.5;
  const centreY = b[1] + b[3] * 0.5;
  return isInRect(centreX, centreY, a);
}
__name(containsCentre, "containsCentre");
function containsRect(a, b) {
  const aRight = a[0] + a[2];
  const aBottom = a[1] + a[3];
  const bRight = b[0] + b[2];
  const bBottom = b[1] + b[3];
  const identical = a[0] === b[0] && a[1] === b[1] && aRight === bRight && aBottom === bBottom;
  return !identical && a[0] <= b[0] && a[1] <= b[1] && aRight >= bRight && aBottom >= bBottom;
}
__name(containsRect, "containsRect");
function createBounds(objects, padding = 10) {
  const bounds = [Infinity, Infinity, -Infinity, -Infinity];
  for (const obj of objects) {
    const rect = obj.boundingRect;
    bounds[0] = Math.min(bounds[0], rect[0]);
    bounds[1] = Math.min(bounds[1], rect[1]);
    bounds[2] = Math.max(bounds[2], rect[0] + rect[2]);
    bounds[3] = Math.max(bounds[3], rect[1] + rect[3]);
  }
  if (!bounds.every((x2) => isFinite(x2))) return null;
  return [
    bounds[0] - padding,
    bounds[1] - padding,
    bounds[2] - bounds[0] + 2 * padding,
    bounds[3] - bounds[1] + 2 * padding
  ];
}
__name(createBounds, "createBounds");
function snapPoint(pos, snapTo) {
  if (!snapTo) return false;
  pos[0] = snapTo * Math.round(pos[0] / snapTo);
  pos[1] = snapTo * Math.round(pos[1] / snapTo);
  return true;
}
__name(snapPoint, "snapPoint");
function alignToContainer(rect, anchors, [containerX, containerY, containerWidth, containerHeight], [insetX, insetY] = [0, 0]) {
  if (hasFlag(anchors, Alignment.Left)) {
    rect[0] = containerX + insetX;
  } else if (hasFlag(anchors, Alignment.Right)) {
    rect[0] = containerX + containerWidth - insetX - rect[2];
  } else if (hasFlag(anchors, Alignment.Centre)) {
    rect[0] = containerX + containerWidth * 0.5 - rect[2] * 0.5;
  }
  if (hasFlag(anchors, Alignment.Top)) {
    rect[1] = containerY + insetY;
  } else if (hasFlag(anchors, Alignment.Bottom)) {
    rect[1] = containerY + containerHeight - insetY - rect[3];
  } else if (hasFlag(anchors, Alignment.Middle)) {
    rect[1] = containerY + containerHeight * 0.5 - rect[3] * 0.5;
  }
  return rect;
}
__name(alignToContainer, "alignToContainer");
function alignOutsideContainer(rect, anchors, [otherX, otherY, otherWidth, otherHeight], [outsetX, outsetY] = [0, 0]) {
  if (hasFlag(anchors, Alignment.Left)) {
    rect[0] = otherX - outsetX - rect[2];
  } else if (hasFlag(anchors, Alignment.Right)) {
    rect[0] = otherX + otherWidth + outsetX;
  } else if (hasFlag(anchors, Alignment.Centre)) {
    rect[0] = otherX + otherWidth * 0.5 - rect[2] * 0.5;
  }
  if (hasFlag(anchors, Alignment.Top)) {
    rect[1] = otherY - outsetY - rect[3];
  } else if (hasFlag(anchors, Alignment.Bottom)) {
    rect[1] = otherY + otherHeight + outsetY;
  } else if (hasFlag(anchors, Alignment.Middle)) {
    rect[1] = otherY + otherHeight * 0.5 - rect[3] * 0.5;
  }
  return rect;
}
__name(alignOutsideContainer, "alignOutsideContainer");
class CurveEditor {
  static {
    __name(this, "CurveEditor");
  }
  points;
  selected;
  nearest;
  size;
  must_update;
  margin;
  _nearest;
  constructor(points) {
    this.points = points;
    this.selected = -1;
    this.nearest = -1;
    this.size = null;
    this.must_update = true;
    this.margin = 5;
  }
  static sampleCurve(f, points) {
    if (!points) return;
    for (let i = 0; i < points.length - 1; ++i) {
      const p = points[i];
      const pn = points[i + 1];
      if (pn[0] < f) continue;
      const r = pn[0] - p[0];
      if (Math.abs(r) < 1e-5) return p[1];
      const local_f = (f - p[0]) / r;
      return p[1] * (1 - local_f) + pn[1] * local_f;
    }
    return 0;
  }
  draw(ctx, size, graphcanvas, background_color, line_color, inactive = false) {
    const points = this.points;
    if (!points) return;
    this.size = size;
    const w = size[0] - this.margin * 2;
    const h2 = size[1] - this.margin * 2;
    line_color = line_color || "#666";
    ctx.save();
    ctx.translate(this.margin, this.margin);
    if (background_color) {
      ctx.fillStyle = "#111";
      ctx.fillRect(0, 0, w, h2);
      ctx.fillStyle = "#222";
      ctx.fillRect(w * 0.5, 0, 1, h2);
      ctx.strokeStyle = "#333";
      ctx.strokeRect(0, 0, w, h2);
    }
    ctx.strokeStyle = line_color;
    if (inactive) ctx.globalAlpha = 0.5;
    ctx.beginPath();
    for (const p of points) {
      ctx.lineTo(p[0] * w, (1 - p[1]) * h2);
    }
    ctx.stroke();
    ctx.globalAlpha = 1;
    if (!inactive) {
      for (const [i, p] of points.entries()) {
        ctx.fillStyle = this.selected == i ? "#FFF" : this.nearest == i ? "#DDD" : "#AAA";
        ctx.beginPath();
        ctx.arc(p[0] * w, (1 - p[1]) * h2, 2, 0, Math.PI * 2);
        ctx.fill();
      }
    }
    ctx.restore();
  }
  // localpos is mouse in curve editor space
  onMouseDown(localpos, graphcanvas) {
    const points = this.points;
    if (!points) return;
    if (localpos[1] < 0) return;
    if (this.size == null)
      throw new Error("CurveEditor.size was null or undefined.");
    const w = this.size[0] - this.margin * 2;
    const h2 = this.size[1] - this.margin * 2;
    const x2 = localpos[0] - this.margin;
    const y = localpos[1] - this.margin;
    const pos = [x2, y];
    const max_dist = 30 / graphcanvas.ds.scale;
    this.selected = this.getCloserPoint(pos, max_dist);
    if (this.selected == -1) {
      const point = [x2 / w, 1 - y / h2];
      points.push(point);
      points.sort(function(a, b) {
        return a[0] - b[0];
      });
      this.selected = points.indexOf(point);
      this.must_update = true;
    }
    if (this.selected != -1) return true;
  }
  onMouseMove(localpos, graphcanvas) {
    const points = this.points;
    if (!points) return;
    const s = this.selected;
    if (s < 0) return;
    if (this.size == null)
      throw new Error("CurveEditor.size was null or undefined.");
    const x2 = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2);
    const y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2);
    const curvepos = [
      localpos[0] - this.margin,
      localpos[1] - this.margin
    ];
    const max_dist = 30 / graphcanvas.ds.scale;
    this._nearest = this.getCloserPoint(curvepos, max_dist);
    const point = points[s];
    if (point) {
      const is_edge_point = s == 0 || s == points.length - 1;
      if (!is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10)) {
        points.splice(s, 1);
        this.selected = -1;
        return;
      }
      if (!is_edge_point) point[0] = clamp(x2, 0, 1);
      else point[0] = s == 0 ? 0 : 1;
      point[1] = 1 - clamp(y, 0, 1);
      points.sort(function(a, b) {
        return a[0] - b[0];
      });
      this.selected = points.indexOf(point);
      this.must_update = true;
    }
  }
  // Former params: localpos, graphcanvas
  onMouseUp() {
    this.selected = -1;
    return false;
  }
  getCloserPoint(pos, max_dist) {
    const points = this.points;
    if (!points) return -1;
    max_dist = max_dist || 30;
    if (this.size == null)
      throw new Error("CurveEditor.size was null or undefined.");
    const w = this.size[0] - this.margin * 2;
    const h2 = this.size[1] - this.margin * 2;
    const num = points.length;
    const p2 = [0, 0];
    let min_dist = 1e6;
    let closest = -1;
    for (let i = 0; i < num; ++i) {
      const p = points[i];
      p2[0] = p[0] * w;
      p2[1] = (1 - p[1]) * h2;
      const dist = distance(pos, p2);
      if (dist > min_dist || dist > max_dist) continue;
      closest = i;
      min_dist = dist;
    }
    return closest;
  }
}
class DragAndScale {
  static {
    __name(this, "DragAndScale");
  }
  /**
   * The state of this DragAndScale instance.
   *
   * Implemented as a POCO that can be proxied without side-effects.
   */
  state;
  lastState = {
    offset: [0, 0],
    scale: 0
  };
  /** Maximum scale (zoom in) */
  max_scale;
  /** Minimum scale (zoom out) */
  min_scale;
  enabled;
  last_mouse;
  element;
  visible_area;
  dragging;
  viewport;
  get offset() {
    return this.state.offset;
  }
  set offset(value) {
    this.state.offset[0] = value[0];
    this.state.offset[1] = value[1];
  }
  get scale() {
    return this.state.scale;
  }
  set scale(value) {
    this.state.scale = value;
  }
  constructor(element) {
    this.state = {
      offset: [0, 0],
      scale: 1
    };
    this.max_scale = 10;
    this.min_scale = 0.1;
    this.enabled = true;
    this.last_mouse = [0, 0];
    this.visible_area = new Rectangle();
    this.element = element;
  }
  /**
   * Returns `true` if the current state has changed from the previous state.
   * @returns `true` if the current state has changed from the previous state, otherwise `false`.
   */
  #stateHasChanged() {
    const current = this.state;
    const previous = this.lastState;
    return current.scale !== previous.scale || current.offset[0] !== previous.offset[0] || current.offset[1] !== previous.offset[1];
  }
  computeVisibleArea(viewport) {
    const { scale, offset, visible_area } = this;
    if (this.#stateHasChanged()) {
      this.onChanged?.(scale, offset);
      copyState(this.state, this.lastState);
    }
    if (!this.element) {
      visible_area[0] = visible_area[1] = visible_area[2] = visible_area[3] = 0;
      return;
    }
    let { width: width2, height } = this.element;
    let startx = -offset[0];
    let starty = -offset[1];
    if (viewport) {
      startx += viewport[0] / scale;
      starty += viewport[1] / scale;
      width2 = viewport[2];
      height = viewport[3];
    }
    const endx = startx + width2 / scale;
    const endy = starty + height / scale;
    visible_area[0] = startx;
    visible_area[1] = starty;
    visible_area.resizeBottomRight(endx, endy);
  }
  toCanvasContext(ctx) {
    ctx.scale(this.scale, this.scale);
    ctx.translate(this.offset[0], this.offset[1]);
  }
  convertOffsetToCanvas(pos) {
    return [
      (pos[0] + this.offset[0]) * this.scale,
      (pos[1] + this.offset[1]) * this.scale
    ];
  }
  convertCanvasToOffset(pos, out) {
    out = out || [0, 0];
    out[0] = pos[0] / this.scale - this.offset[0];
    out[1] = pos[1] / this.scale - this.offset[1];
    return out;
  }
  /** @deprecated Has not been kept up to date */
  mouseDrag(x2, y) {
    this.offset[0] += x2 / this.scale;
    this.offset[1] += y / this.scale;
    this.onredraw?.(this);
  }
  changeScale(value, zooming_center, roundToScaleOne = true) {
    if (value < this.min_scale) {
      value = this.min_scale;
    } else if (value > this.max_scale) {
      value = this.max_scale;
    }
    if (value == this.scale) return;
    const rect = this.element.getBoundingClientRect();
    if (!rect) return;
    zooming_center = zooming_center ?? [rect.width * 0.5, rect.height * 0.5];
    const normalizedCenter = [
      zooming_center[0] - rect.x,
      zooming_center[1] - rect.y
    ];
    const center = this.convertCanvasToOffset(normalizedCenter);
    this.scale = value;
    if (roundToScaleOne && Math.abs(this.scale - 1) < 0.01) this.scale = 1;
    const new_center = this.convertCanvasToOffset(normalizedCenter);
    const delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];
    this.offset[0] += delta_offset[0];
    this.offset[1] += delta_offset[1];
    this.onredraw?.(this);
  }
  changeDeltaScale(value, zooming_center) {
    this.changeScale(this.scale * value, zooming_center);
  }
  /**
   * Fits the view to the specified bounds.
   * @param bounds The bounds to fit the view to, defined by a rectangle.
   */
  fitToBounds(bounds, { zoom = 0.75 } = {}) {
    const [width2, height] = this.element.width === 300 && this.element.height === 150 ? [1920, 1080] : [this.element.width, this.element.height];
    const cw = width2 / window.devicePixelRatio;
    const ch = height / window.devicePixelRatio;
    let targetScale = this.scale;
    if (zoom > 0) {
      const targetScaleX = zoom * cw / Math.max(bounds[2], 300);
      const targetScaleY = zoom * ch / Math.max(bounds[3], 300);
      targetScale = Math.min(targetScaleX, targetScaleY, this.max_scale);
    }
    const scaledWidth = cw / targetScale;
    const scaledHeight = ch / targetScale;
    const targetX = -bounds[0] - bounds[2] * 0.5 + scaledWidth * 0.5;
    const targetY = -bounds[1] - bounds[3] * 0.5 + scaledHeight * 0.5;
    this.offset[0] = targetX;
    this.offset[1] = targetY;
    this.scale = targetScale;
  }
  /**
   * Starts an animation to fit the view around the specified selection of nodes.
   * @param bounds The bounds to animate the view to, defined by a rectangle.
   */
  animateToBounds(bounds, setDirty, {
    duration = 350,
    zoom = 0.75,
    easing = EaseFunction.EASE_IN_OUT_QUAD
  } = {}) {
    if (!(duration > 0)) throw new RangeError("Duration must be greater than 0");
    const easeFunctions = {
      linear: /* @__PURE__ */ __name((t2) => t2, "linear"),
      easeInQuad: /* @__PURE__ */ __name((t2) => t2 * t2, "easeInQuad"),
      easeOutQuad: /* @__PURE__ */ __name((t2) => t2 * (2 - t2), "easeOutQuad"),
      easeInOutQuad: /* @__PURE__ */ __name((t2) => t2 < 0.5 ? 2 * t2 * t2 : -1 + (4 - 2 * t2) * t2, "easeInOutQuad")
    };
    const easeFunction = easeFunctions[easing] ?? easeFunctions.linear;
    const startTimestamp = performance.now();
    const cw = this.element.width / window.devicePixelRatio;
    const ch = this.element.height / window.devicePixelRatio;
    const startX = this.offset[0];
    const startY = this.offset[1];
    const startX2 = startX - cw / this.scale;
    const startY2 = startY - ch / this.scale;
    const startScale = this.scale;
    let targetScale = startScale;
    if (zoom > 0) {
      const targetScaleX = zoom * cw / Math.max(bounds[2], 300);
      const targetScaleY = zoom * ch / Math.max(bounds[3], 300);
      targetScale = Math.min(targetScaleX, targetScaleY, this.max_scale);
    }
    const scaledWidth = cw / targetScale;
    const scaledHeight = ch / targetScale;
    const targetX = -bounds[0] - bounds[2] * 0.5 + scaledWidth * 0.5;
    const targetY = -bounds[1] - bounds[3] * 0.5 + scaledHeight * 0.5;
    const targetX2 = targetX - scaledWidth;
    const targetY2 = targetY - scaledHeight;
    const animate = /* @__PURE__ */ __name((timestamp) => {
      const elapsed = timestamp - startTimestamp;
      const progress = Math.min(elapsed / duration, 1);
      const easedProgress = easeFunction(progress);
      const currentX = startX + (targetX - startX) * easedProgress;
      const currentY = startY + (targetY - startY) * easedProgress;
      this.offset[0] = currentX;
      this.offset[1] = currentY;
      if (zoom > 0) {
        const currentX2 = startX2 + (targetX2 - startX2) * easedProgress;
        const currentY2 = startY2 + (targetY2 - startY2) * easedProgress;
        const currentWidth = Math.abs(currentX2 - currentX);
        const currentHeight = Math.abs(currentY2 - currentY);
        this.scale = Math.min(cw / currentWidth, ch / currentHeight);
      }
      setDirty();
      if (progress < 1) {
        animationId = requestAnimationFrame(animate);
      } else {
        cancelAnimationFrame(animationId);
      }
    }, "animate");
    let animationId = requestAnimationFrame(animate);
  }
  reset() {
    this.scale = 1;
    this.offset[0] = 0;
    this.offset[1] = 0;
  }
}
function copyState(from, to) {
  to.scale = from.scale;
  to.offset[0] = from.offset[0];
  to.offset[1] = from.offset[1];
}
__name(copyState, "copyState");
const SUBGRAPH_INPUT_ID = -10;
const SUBGRAPH_OUTPUT_ID = -20;
const zeroUuid = "00000000-0000-0000-0000-000000000000";
const randomStorage = new Uint32Array(31);
function createUuidv4() {
  if (typeof crypto?.randomUUID === "function") return crypto.randomUUID();
  if (typeof crypto?.getRandomValues === "function") {
    const random = crypto.getRandomValues(randomStorage);
    let i = 0;
    return "10000000-1000-4000-8000-100000000000".replaceAll(
      /[018]/g,
      (a) => (Number(a) ^ random[i++] * 3725290298461914e-24 >> Number(a) * 0.25).toString(16)
    );
  }
  return "10000000-1000-4000-8000-100000000000".replaceAll(
    /[018]/g,
    (a) => (Number(a) ^ Math.random() * 16 >> Number(a) * 0.25).toString(16)
  );
}
__name(createUuidv4, "createUuidv4");
const removeNodeTitleHeight = /* @__PURE__ */ __name((height) => Math.max(0, height - (LiteGraph.NODE_TITLE_HEIGHT || 0)), "removeNodeTitleHeight");
var LayoutSource = /* @__PURE__ */ ((LayoutSource2) => {
  LayoutSource2["Canvas"] = "canvas";
  LayoutSource2["Vue"] = "vue";
  LayoutSource2["DOM"] = "dom";
  LayoutSource2["External"] = "external";
  return LayoutSource2;
})(LayoutSource || {});
const QUADTREE_CONFIG = {
  /** Default bounds for the QuadTree - covers a large canvas area */
  DEFAULT_BOUNDS: {
    x: -1e4,
    y: -1e4,
    width: 2e4,
    height: 2e4
  },
  /** Maximum tree depth to prevent excessive subdivision */
  MAX_DEPTH: 6,
  /** Maximum items per node before subdivision */
  MAX_ITEMS_PER_NODE: 4
};
const PERFORMANCE_CONFIG = {
  /** Spatial query cache TTL in milliseconds */
  SPATIAL_CACHE_TTL: 1e3,
  /** Maximum cache size for spatial queries */
  SPATIAL_CACHE_MAX_SIZE: 100
};
const ACTOR_CONFIG = {
  /** Prefix for auto-generated actor IDs */
  USER_PREFIX: "user-",
  /** Length of random suffix for actor IDs */
  ID_LENGTH: 9,
  /** Default source when not specified */
  DEFAULT_SOURCE: LayoutSource.External
};
function toPoint(x2, y) {
  return { x: x2, y };
}
__name(toPoint, "toPoint");
function isPointEqual(a, b) {
  return a.x === b.x && a.y === b.y;
}
__name(isPointEqual, "isPointEqual");
function isBoundsEqual(a, b) {
  return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
}
__name(isBoundsEqual, "isBoundsEqual");
function isSizeEqual(a, b) {
  return a.width === b.width && a.height === b.height;
}
__name(isSizeEqual, "isSizeEqual");
const REROUTE_RADIUS = 8;
function pointInBounds(point, bounds) {
  return point.x >= bounds.x && point.x <= bounds.x + bounds.width && point.y >= bounds.y && point.y <= bounds.y + bounds.height;
}
__name(pointInBounds, "pointInBounds");
function boundsIntersect(a, b) {
  return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);
}
__name(boundsIntersect, "boundsIntersect");
function makeLinkSegmentKey(linkId, rerouteId) {
  return `${linkId}:${rerouteId ?? "final"}`;
}
__name(makeLinkSegmentKey, "makeLinkSegmentKey");
const NODE_LAYOUT_DEFAULTS = {
  id: "unknown-node",
  position: { x: 0, y: 0 },
  size: { width: 100, height: 50 },
  zIndex: 0,
  visible: true,
  bounds: { x: 0, y: 0, width: 100, height: 50 }
};
function layoutToYNode(layout) {
  const ynode = new YMap();
  ynode.set("id", layout.id);
  ynode.set("position", layout.position);
  ynode.set("size", layout.size);
  ynode.set("zIndex", layout.zIndex);
  ynode.set("visible", layout.visible);
  ynode.set("bounds", layout.bounds);
  return ynode;
}
__name(layoutToYNode, "layoutToYNode");
function getOr(map2, key, fallback) {
  const v2 = map2.get(key);
  return v2 ?? fallback;
}
__name(getOr, "getOr");
function yNodeToLayout(ynode) {
  return {
    id: getOr(ynode, "id", NODE_LAYOUT_DEFAULTS.id),
    position: getOr(ynode, "position", NODE_LAYOUT_DEFAULTS.position),
    size: getOr(ynode, "size", NODE_LAYOUT_DEFAULTS.size),
    zIndex: getOr(ynode, "zIndex", NODE_LAYOUT_DEFAULTS.zIndex),
    visible: getOr(ynode, "visible", NODE_LAYOUT_DEFAULTS.visible),
    bounds: getOr(ynode, "bounds", NODE_LAYOUT_DEFAULTS.bounds)
  };
}
__name(yNodeToLayout, "yNodeToLayout");
class QuadNode {
  static {
    __name(this, "QuadNode");
  }
  bounds;
  depth;
  maxDepth;
  maxItems;
  items = [];
  children = null;
  divided = false;
  constructor(bounds, depth = 0, maxDepth = 5, maxItems = 4) {
    this.bounds = bounds;
    this.depth = depth;
    this.maxDepth = maxDepth;
    this.maxItems = maxItems;
  }
  insert(item) {
    if (!this.contains(item.bounds)) {
      return false;
    }
    if (this.items.length < this.maxItems && !this.divided) {
      this.items.push(item);
      return true;
    }
    if (!this.divided && this.depth < this.maxDepth) {
      this.subdivide();
    }
    if (this.divided && this.children) {
      for (const child of this.children) {
        if (child.insert(item)) {
          return true;
        }
      }
    }
    this.items.push(item);
    return true;
  }
  remove(item) {
    const index2 = this.items.findIndex((i) => i.id === item.id);
    if (index2 !== -1) {
      this.items.splice(index2, 1);
      return true;
    }
    if (this.divided && this.children) {
      for (const child of this.children) {
        if (child.remove(item)) {
          return true;
        }
      }
    }
    return false;
  }
  query(searchBounds, found = []) {
    if (!this.intersects(searchBounds)) {
      return found;
    }
    for (const item of this.items) {
      if (this.boundsIntersect(item.bounds, searchBounds)) {
        found.push(item);
      }
    }
    if (this.divided && this.children) {
      for (const child of this.children) {
        child.query(searchBounds, found);
      }
    }
    return found;
  }
  subdivide() {
    const { x: x2, y, width: width2, height } = this.bounds;
    const halfWidth = width2 / 2;
    const halfHeight = height / 2;
    this.children = [
      // Top-left
      new QuadNode(
        { x: x2, y, width: halfWidth, height: halfHeight },
        this.depth + 1,
        this.maxDepth,
        this.maxItems
      ),
      // Top-right
      new QuadNode(
        { x: x2 + halfWidth, y, width: halfWidth, height: halfHeight },
        this.depth + 1,
        this.maxDepth,
        this.maxItems
      ),
      // Bottom-left
      new QuadNode(
        { x: x2, y: y + halfHeight, width: halfWidth, height: halfHeight },
        this.depth + 1,
        this.maxDepth,
        this.maxItems
      ),
      // Bottom-right
      new QuadNode(
        {
          x: x2 + halfWidth,
          y: y + halfHeight,
          width: halfWidth,
          height: halfHeight
        },
        this.depth + 1,
        this.maxDepth,
        this.maxItems
      )
    ];
    this.divided = true;
    const itemsToRedistribute = [...this.items];
    this.items = [];
    for (const item of itemsToRedistribute) {
      let inserted = false;
      for (const child of this.children) {
        if (child.insert(item)) {
          inserted = true;
          break;
        }
      }
      if (!inserted) {
        this.items.push(item);
      }
    }
  }
  contains(itemBounds) {
    return itemBounds.x >= this.bounds.x && itemBounds.y >= this.bounds.y && itemBounds.x + itemBounds.width <= this.bounds.x + this.bounds.width && itemBounds.y + itemBounds.height <= this.bounds.y + this.bounds.height;
  }
  intersects(searchBounds) {
    return this.boundsIntersect(this.bounds, searchBounds);
  }
  boundsIntersect(a, b) {
    return !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y);
  }
  // Debug helper to get tree structure
  getDebugInfo() {
    return {
      bounds: this.bounds,
      depth: this.depth,
      itemCount: this.items.length,
      divided: this.divided,
      children: this.children?.map((child) => child.getDebugInfo())
    };
  }
}
class QuadTree {
  static {
    __name(this, "QuadTree");
  }
  root;
  itemMap = /* @__PURE__ */ new Map();
  options;
  constructor(bounds, options = {}) {
    this.options = {
      maxDepth: options.maxDepth ?? 5,
      maxItemsPerNode: options.maxItemsPerNode ?? 4,
      minNodeSize: options.minNodeSize ?? 50
    };
    this.root = new QuadNode(
      bounds,
      0,
      this.options.maxDepth,
      this.options.maxItemsPerNode
    );
  }
  insert(id2, bounds, data) {
    const item = { id: id2, bounds, data };
    if (this.itemMap.has(id2)) {
      this.remove(id2);
    }
    const success = this.root.insert(item);
    if (success) {
      this.itemMap.set(id2, item);
    }
    return success;
  }
  remove(id2) {
    const item = this.itemMap.get(id2);
    if (!item) return false;
    const success = this.root.remove(item);
    if (success) {
      this.itemMap.delete(id2);
    }
    return success;
  }
  update(id2, newBounds) {
    const item = this.itemMap.get(id2);
    if (!item) return false;
    const data = item.data;
    this.remove(id2);
    return this.insert(id2, newBounds, data);
  }
  query(searchBounds) {
    const items = this.root.query(searchBounds);
    return items.map((item) => item.data);
  }
  clear() {
    this.root = new QuadNode(
      this.root["bounds"],
      0,
      this.options.maxDepth,
      this.options.maxItemsPerNode
    );
    this.itemMap.clear();
  }
  get size() {
    return this.itemMap.size;
  }
  getDebugInfo() {
    return {
      size: this.size,
      tree: this.root.getDebugInfo()
    };
  }
}
class SpatialIndexManager {
  static {
    __name(this, "SpatialIndexManager");
  }
  quadTree;
  queryCache;
  cacheSize = 0;
  constructor(bounds) {
    this.quadTree = new QuadTree(
      bounds ?? QUADTREE_CONFIG.DEFAULT_BOUNDS,
      {
        maxDepth: QUADTREE_CONFIG.MAX_DEPTH,
        maxItemsPerNode: QUADTREE_CONFIG.MAX_ITEMS_PER_NODE
      }
    );
    this.queryCache = /* @__PURE__ */ new Map();
  }
  /**
   * Insert a node into the spatial index
   */
  insert(nodeId, bounds) {
    this.quadTree.insert(nodeId, bounds, nodeId);
    this.invalidateCache();
  }
  /**
   * Update a node's bounds in the spatial index
   */
  update(nodeId, bounds) {
    this.quadTree.update(nodeId, bounds);
    this.invalidateCache();
  }
  /**
   * Batch update multiple nodes' bounds in the spatial index
   * More efficient than calling update() multiple times as it only invalidates cache once
   */
  batchUpdate(updates) {
    for (const { nodeId, bounds } of updates) {
      this.quadTree.update(nodeId, bounds);
    }
    this.invalidateCache();
  }
  /**
   * Remove a node from the spatial index
   */
  remove(nodeId) {
    this.quadTree.remove(nodeId);
    this.invalidateCache();
  }
  /**
   * Query nodes within the given bounds
   */
  query(bounds) {
    const cacheKey = this.getCacheKey(bounds);
    const cached = this.queryCache.get(cacheKey);
    if (cached) {
      const age = Date.now() - cached.timestamp;
      if (age < PERFORMANCE_CONFIG.SPATIAL_CACHE_TTL) {
        return cached.result;
      }
      this.queryCache.delete(cacheKey);
      this.cacheSize--;
    }
    const result = this.quadTree.query(bounds);
    this.addToCache(cacheKey, result);
    return result;
  }
  /**
   * Clear all nodes from the spatial index
   */
  clear() {
    this.quadTree.clear();
    this.invalidateCache();
  }
  /**
   * Get the current size of the index
   */
  get size() {
    return this.quadTree.size;
  }
  /**
   * Get debug information about the spatial index
   */
  getDebugInfo() {
    return {
      quadTreeInfo: this.quadTree.getDebugInfo(),
      cacheSize: this.cacheSize,
      cacheEntries: this.queryCache.size
    };
  }
  /**
   * Generate cache key for bounds
   */
  getCacheKey(bounds) {
    return `${bounds.x},${bounds.y},${bounds.width},${bounds.height}`;
  }
  /**
   * Add result to cache with LRU eviction
   */
  addToCache(key, result) {
    if (this.cacheSize >= PERFORMANCE_CONFIG.SPATIAL_CACHE_MAX_SIZE) {
      const oldestKey = this.findOldestCacheEntry();
      if (oldestKey) {
        this.queryCache.delete(oldestKey);
        this.cacheSize--;
      }
    }
    this.queryCache.set(key, {
      result,
      timestamp: Date.now()
    });
    this.cacheSize++;
  }
  /**
   * Find oldest cache entry for LRU eviction
   */
  findOldestCacheEntry() {
    let oldestKey = null;
    let oldestTime = Infinity;
    for (const [key, entry] of this.queryCache) {
      if (entry.timestamp < oldestTime) {
        oldestTime = entry.timestamp;
        oldestKey = key;
      }
    }
    return oldestKey;
  }
  /**
   * Invalidate all cached queries
   */
  invalidateCache() {
    this.queryCache.clear();
    this.cacheSize = 0;
  }
}
const logger$2 = log.getLogger("LayoutStore");
function asRerouteId(id2) {
  return Number(id2);
}
__name(asRerouteId, "asRerouteId");
function asLinkId(id2) {
  return Number(id2);
}
__name(asLinkId, "asLinkId");
class LayoutStoreImpl {
  static {
    __name(this, "LayoutStoreImpl");
  }
  static REROUTE_DEFAULTS = {
    id: 0,
    position: { x: 0, y: 0 },
    parentId: 0,
    linkIds: []
  };
  // Yjs document and shared data structures
  ydoc = new Doc();
  ynodes;
  // Maps nodeId -> NodeLayoutMap containing NodeLayout data
  ylinks;
  // Maps linkId -> Y.Map containing link data
  yreroutes;
  // Maps rerouteId -> Y.Map containing reroute data
  yoperations;
  // Operation log
  // Vue reactivity layer
  version = 0;
  currentSource = ACTOR_CONFIG.DEFAULT_SOURCE;
  currentActor = `${ACTOR_CONFIG.USER_PREFIX}${Math.random().toString(36).substring(2, 2 + ACTOR_CONFIG.ID_LENGTH)}`;
  // Change listeners
  changeListeners = /* @__PURE__ */ new Set();
  // CustomRef cache and trigger functions
  nodeRefs = /* @__PURE__ */ new Map();
  nodeTriggers = /* @__PURE__ */ new Map();
  // New data structures for hit testing
  linkLayouts = /* @__PURE__ */ new Map();
  linkSegmentLayouts = /* @__PURE__ */ new Map();
  // Internal string key: ${linkId}:${rerouteId ?? 'final'}
  slotLayouts = /* @__PURE__ */ new Map();
  rerouteLayouts = /* @__PURE__ */ new Map();
  // Spatial index managers
  spatialIndex;
  // For nodes
  linkSegmentSpatialIndex;
  // For link segments (single index for all link geometry)
  slotSpatialIndex;
  // For slots
  rerouteSpatialIndex;
  // For reroutes
  // Vue dragging state for selection toolbox (public ref for direct mutation)
  isDraggingVueNodes = ref(false);
  // Vue resizing state to prevent drag from activating during resize
  isResizingVueNodes = ref(false);
  constructor() {
    this.ynodes = this.ydoc.getMap("nodes");
    this.ylinks = this.ydoc.getMap("links");
    this.yreroutes = this.ydoc.getMap("reroutes");
    this.yoperations = this.ydoc.getArray("operations");
    this.spatialIndex = new SpatialIndexManager();
    this.linkSegmentSpatialIndex = new SpatialIndexManager();
    this.slotSpatialIndex = new SpatialIndexManager();
    this.rerouteSpatialIndex = new SpatialIndexManager();
    this.ynodes.observe((event) => {
      this.version++;
      event.changes.keys.forEach((_change, key) => {
        const trigger = this.nodeTriggers.get(key);
        if (trigger) {
          trigger();
        }
      });
    });
    this.ylinks.observe((event) => {
      this.version++;
      event.changes.keys.forEach((change, linkIdStr) => {
        this.handleLinkChange(change, linkIdStr);
      });
    });
    this.yreroutes.observe((event) => {
      this.version++;
      event.changes.keys.forEach((change, rerouteIdStr) => {
        this.handleRerouteChange(change, rerouteIdStr);
      });
    });
  }
  getLinkField(ylink, field) {
    const typedLink = ylink;
    return typedLink.get(field);
  }
  getRerouteField(yreroute, field, defaultValue = LayoutStoreImpl.REROUTE_DEFAULTS[field]) {
    const typedReroute = yreroute;
    const value = typedReroute.get(field);
    return value ?? defaultValue;
  }
  /**
   * Get or create a customRef for a node layout
   */
  getNodeLayoutRef(nodeId) {
    let nodeRef = this.nodeRefs.get(nodeId);
    if (!nodeRef) {
      nodeRef = customRef((track, trigger) => {
        this.nodeTriggers.set(nodeId, trigger);
        return {
          get: /* @__PURE__ */ __name(() => {
            track();
            const ynode = this.ynodes.get(nodeId);
            const layout = ynode ? yNodeToLayout(ynode) : null;
            return layout;
          }, "get"),
          set: /* @__PURE__ */ __name((newLayout) => {
            if (newLayout === null) {
              const existing = this.ynodes.get(nodeId);
              if (existing) {
                this.applyOperation({
                  type: "deleteNode",
                  entity: "node",
                  nodeId,
                  timestamp: Date.now(),
                  source: this.currentSource,
                  actor: this.currentActor,
                  previousLayout: yNodeToLayout(existing)
                });
              }
            } else {
              const existing = this.ynodes.get(nodeId);
              if (!existing) {
                this.applyOperation({
                  type: "createNode",
                  entity: "node",
                  nodeId,
                  layout: newLayout,
                  timestamp: Date.now(),
                  source: this.currentSource,
                  actor: this.currentActor
                });
              } else {
                const existingLayout = yNodeToLayout(existing);
                if (existingLayout.position.x !== newLayout.position.x || existingLayout.position.y !== newLayout.position.y) {
                  this.applyOperation({
                    type: "moveNode",
                    entity: "node",
                    nodeId,
                    position: newLayout.position,
                    previousPosition: existingLayout.position,
                    timestamp: Date.now(),
                    source: this.currentSource,
                    actor: this.currentActor
                  });
                }
                if (existingLayout.size.width !== newLayout.size.width || existingLayout.size.height !== newLayout.size.height) {
                  this.applyOperation({
                    type: "resizeNode",
                    entity: "node",
                    nodeId,
                    size: newLayout.size,
                    previousSize: existingLayout.size,
                    timestamp: Date.now(),
                    source: this.currentSource,
                    actor: this.currentActor
                  });
                }
                if (existingLayout.zIndex !== newLayout.zIndex) {
                  this.applyOperation({
                    type: "setNodeZIndex",
                    entity: "node",
                    nodeId,
                    zIndex: newLayout.zIndex,
                    previousZIndex: existingLayout.zIndex,
                    timestamp: Date.now(),
                    source: this.currentSource,
                    actor: this.currentActor
                  });
                }
              }
            }
            trigger();
          }, "set")
        };
      });
      this.nodeRefs.set(nodeId, nodeRef);
    }
    return nodeRef;
  }
  /**
   * Get nodes within bounds (reactive)
   */
  getNodesInBounds(bounds) {
    return computed(() => {
      void this.version;
      const result = [];
      for (const [nodeId] of this.ynodes) {
        const ynode = this.ynodes.get(nodeId);
        if (ynode) {
          const layout = yNodeToLayout(ynode);
          if (layout && boundsIntersect(layout.bounds, bounds)) {
            result.push(nodeId);
          }
        }
      }
      return result;
    });
  }
  /**
   * Get all nodes as a reactive map
   */
  getAllNodes() {
    return computed(() => {
      void this.version;
      const result = /* @__PURE__ */ new Map();
      for (const [nodeId] of this.ynodes) {
        const ynode = this.ynodes.get(nodeId);
        if (ynode) {
          const layout = yNodeToLayout(ynode);
          if (layout) {
            result.set(nodeId, layout);
          }
        }
      }
      return result;
    });
  }
  /**
   * Get current version for change detection
   */
  getVersion() {
    return computed(() => this.version);
  }
  /**
   * Query node at point (non-reactive for performance)
   */
  queryNodeAtPoint(point) {
    const nodes = [];
    for (const [nodeId] of this.ynodes) {
      const ynode = this.ynodes.get(nodeId);
      if (ynode) {
        const layout = yNodeToLayout(ynode);
        if (layout) {
          nodes.push([nodeId, layout]);
        }
      }
    }
    nodes.sort(([, a], [, b]) => b.zIndex - a.zIndex);
    for (const [nodeId, layout] of nodes) {
      if (pointInBounds(point, layout.bounds)) {
        return nodeId;
      }
    }
    return null;
  }
  /**
   * Query nodes in bounds (non-reactive for performance)
   */
  queryNodesInBounds(bounds) {
    return this.spatialIndex.query(bounds);
  }
  /**
   * Update link layout data (for geometry/debug, no separate spatial index)
   */
  updateLinkLayout(linkId, layout) {
    const existing = this.linkLayouts.get(linkId);
    if (existing && isBoundsEqual(existing.bounds, layout.bounds) && isPointEqual(existing.centerPos, layout.centerPos)) {
      if (layout.path) {
        existing.path = layout.path;
      }
      return;
    }
    this.linkLayouts.set(linkId, layout);
  }
  /**
   * Delete link layout data
   */
  deleteLinkLayout(linkId) {
    const deleted = this.linkLayouts.delete(linkId);
    if (deleted) {
      const keysToDelete = [];
      for (const [key] of this.linkSegmentLayouts) {
        if (key.startsWith(`${linkId}:`)) {
          keysToDelete.push(key);
        }
      }
      for (const key of keysToDelete) {
        this.linkSegmentLayouts.delete(key);
        this.linkSegmentSpatialIndex.remove(key);
      }
    }
  }
  /**
   * Update slot layout data
   */
  updateSlotLayout(key, layout) {
    const existing = this.slotLayouts.get(key);
    if (existing) {
      if (isPointEqual(existing.position, layout.position) && isBoundsEqual(existing.bounds, layout.bounds)) {
        return;
      }
      this.slotSpatialIndex.update(key, layout.bounds);
    } else {
      this.slotSpatialIndex.insert(key, layout.bounds);
    }
    this.slotLayouts.set(key, layout);
  }
  /**
   * Batch update slot layouts and spatial index in one pass
   */
  batchUpdateSlotLayouts(updates) {
    if (!updates.length) return;
    for (const { key, layout } of updates) {
      const existing = this.slotLayouts.get(key);
      if (existing) {
        if (isPointEqual(existing.position, layout.position) && isBoundsEqual(existing.bounds, layout.bounds)) {
          continue;
        }
        this.slotSpatialIndex.update(key, layout.bounds);
      } else {
        this.slotSpatialIndex.insert(key, layout.bounds);
      }
      this.slotLayouts.set(key, layout);
    }
  }
  /**
   * Delete slot layout data
   */
  deleteSlotLayout(key) {
    const deleted = this.slotLayouts.delete(key);
    if (deleted) {
      this.slotSpatialIndex.remove(key);
    }
  }
  /**
   * Delete all slot layouts for a node
   */
  deleteNodeSlotLayouts(nodeId) {
    const keysToDelete = [];
    for (const [key, layout] of this.slotLayouts) {
      if (layout.nodeId === nodeId) {
        keysToDelete.push(key);
      }
    }
    for (const key of keysToDelete) {
      this.slotLayouts.delete(key);
      this.slotSpatialIndex.remove(key);
    }
  }
  /**
   * Clear all slot layouts and their spatial index (O(1) operations)
   * Used when switching rendering modes (Vue ↔ LiteGraph)
   */
  clearAllSlotLayouts() {
    this.slotLayouts.clear();
    this.slotSpatialIndex.clear();
  }
  /**
   * Update reroute layout data
   */
  updateRerouteLayout(rerouteId, layout) {
    const existing = this.rerouteLayouts.get(rerouteId);
    if (!existing) {
      logger$2.debug("Adding reroute layout:", {
        rerouteId,
        position: layout.position,
        bounds: layout.bounds
      });
    }
    if (existing) {
      this.rerouteSpatialIndex.update(String(rerouteId), layout.bounds);
    } else {
      this.rerouteSpatialIndex.insert(String(rerouteId), layout.bounds);
    }
    this.rerouteLayouts.set(rerouteId, layout);
  }
  /**
   * Delete reroute layout data
   */
  deleteRerouteLayout(rerouteId) {
    const deleted = this.rerouteLayouts.delete(rerouteId);
    if (deleted) {
      this.rerouteSpatialIndex.remove(String(rerouteId));
    }
  }
  /**
   * Get link layout data
   */
  getLinkLayout(linkId) {
    return this.linkLayouts.get(linkId) || null;
  }
  /**
   * Get slot layout data
   */
  getSlotLayout(key) {
    return this.slotLayouts.get(key) || null;
  }
  /**
   * Get reroute layout data
   */
  getRerouteLayout(rerouteId) {
    return this.rerouteLayouts.get(rerouteId) || null;
  }
  /**
   * Returns all slot layout keys currently tracked by the store.
   * Useful for global passes without relying on spatial queries.
   */
  getAllSlotKeys() {
    return Array.from(this.slotLayouts.keys());
  }
  /**
   * Update link segment layout data
   */
  updateLinkSegmentLayout(linkId, rerouteId, layout) {
    const key = makeLinkSegmentKey(linkId, rerouteId);
    const existing = this.linkSegmentLayouts.get(key);
    if (existing && isBoundsEqual(existing.bounds, layout.bounds) && isPointEqual(existing.centerPos, layout.centerPos)) {
      if (layout.path) {
        existing.path = layout.path;
      }
      return;
    }
    const fullLayout = {
      ...layout,
      linkId,
      rerouteId
    };
    if (!existing) {
      logger$2.debug("Adding link segment:", {
        linkId,
        rerouteId,
        bounds: layout.bounds,
        hasPath: !!layout.path
      });
    }
    if (existing) {
      this.linkSegmentSpatialIndex.update(key, layout.bounds);
    } else {
      this.linkSegmentSpatialIndex.insert(key, layout.bounds);
    }
    this.linkSegmentLayouts.set(key, fullLayout);
  }
  /**
   * Delete link segment layout data
   */
  deleteLinkSegmentLayout(linkId, rerouteId) {
    const key = makeLinkSegmentKey(linkId, rerouteId);
    const deleted = this.linkSegmentLayouts.delete(key);
    if (deleted) {
      this.linkSegmentSpatialIndex.remove(key);
    }
  }
  /**
   * Query link segment at point (returns structured data)
   */
  queryLinkSegmentAtPoint(point, ctx) {
    const hitWidth = ctx?.lineWidth ?? 10;
    const halfSize = Math.max(10, hitWidth);
    const searchArea = {
      x: point.x - halfSize,
      y: point.y - halfSize,
      width: halfSize * 2,
      height: halfSize * 2
    };
    const candidateKeys = this.linkSegmentSpatialIndex.query(searchArea);
    if (candidateKeys.length > 0) {
      logger$2.debug("Checking link segments at point:", {
        point,
        candidateCount: candidateKeys.length,
        tolerance: hitWidth
      });
    }
    for (const key of candidateKeys) {
      const segmentLayout = this.linkSegmentLayouts.get(key);
      if (!segmentLayout) continue;
      if (ctx && segmentLayout.path) {
        const dpi = typeof window !== "undefined" && window?.devicePixelRatio || 1;
        const hit = ctx.isPointInStroke(
          segmentLayout.path,
          point.x * dpi,
          point.y * dpi
        );
        if (hit) {
          logger$2.debug("Link segment hit:", {
            linkId: segmentLayout.linkId,
            rerouteId: segmentLayout.rerouteId,
            point
          });
          return {
            linkId: segmentLayout.linkId,
            rerouteId: segmentLayout.rerouteId
          };
        }
      } else if (pointInBounds(point, segmentLayout.bounds)) {
        return {
          linkId: segmentLayout.linkId,
          rerouteId: segmentLayout.rerouteId
        };
      }
    }
    return null;
  }
  /**
   * Query link at point (derived from segment query)
   */
  queryLinkAtPoint(point, ctx) {
    const segment = this.queryLinkSegmentAtPoint(point, ctx);
    return segment ? segment.linkId : null;
  }
  /**
   * Query slot at point
   */
  querySlotAtPoint(point) {
    const searchArea = {
      x: point.x - 10,
      // Tolerance for slot size
      y: point.y - 10,
      width: 20,
      height: 20
    };
    const candidateSlotKeys = this.slotSpatialIndex.query(searchArea);
    for (const key of candidateSlotKeys) {
      const slotLayout = this.slotLayouts.get(key);
      if (slotLayout && pointInBounds(point, slotLayout.bounds)) {
        return slotLayout;
      }
    }
    return null;
  }
  /**
   * Query reroute at point
   */
  queryRerouteAtPoint(point) {
    const maxRadius = 20;
    const searchArea = {
      x: point.x - maxRadius,
      y: point.y - maxRadius,
      width: maxRadius * 2,
      height: maxRadius * 2
    };
    const candidateRerouteKeys = this.rerouteSpatialIndex.query(searchArea);
    if (candidateRerouteKeys.length > 0) {
      logger$2.debug("Checking reroutes at point:", {
        point,
        candidateCount: candidateRerouteKeys.length
      });
    }
    for (const rerouteKey of candidateRerouteKeys) {
      const rerouteId = asRerouteId(rerouteKey);
      const rerouteLayout = this.rerouteLayouts.get(rerouteId);
      if (rerouteLayout) {
        const dx = point.x - rerouteLayout.position.x;
        const dy = point.y - rerouteLayout.position.y;
        const distance2 = Math.sqrt(dx * dx + dy * dy);
        if (distance2 <= rerouteLayout.radius) {
          logger$2.debug("Reroute hit:", {
            rerouteId: rerouteLayout.id,
            position: rerouteLayout.position,
            distance: distance2
          });
          return rerouteLayout;
        }
      }
    }
    return null;
  }
  /**
   * Query all items in bounds
   */
  queryItemsInBounds(bounds) {
    const segmentKeys = this.linkSegmentSpatialIndex.query(bounds);
    const linkIds = /* @__PURE__ */ new Set();
    for (const key of segmentKeys) {
      const segment = this.linkSegmentLayouts.get(key);
      if (segment) {
        linkIds.add(segment.linkId);
      }
    }
    return {
      nodes: this.queryNodesInBounds(bounds),
      links: Array.from(linkIds),
      slots: this.slotSpatialIndex.query(bounds),
      reroutes: this.rerouteSpatialIndex.query(bounds).map((key) => asRerouteId(key))
    };
  }
  /**
   * Apply a layout operation using Yjs transactions
   */
  applyOperation(operation) {
    const change = {
      type: "update",
      nodeIds: [],
      timestamp: operation.timestamp,
      source: operation.source,
      operation
    };
    this.ydoc.transact(() => {
      this.yoperations.push([operation]);
      this.applyOperationInTransaction(operation, change);
    }, this.currentActor);
    this.finalizeOperation(change);
  }
  /**
   * Apply operation within a transaction
   */
  applyOperationInTransaction(operation, change) {
    switch (operation.type) {
      case "moveNode":
        this.handleMoveNode(operation, change);
        break;
      case "resizeNode":
        this.handleResizeNode(operation, change);
        break;
      case "setNodeZIndex":
        this.handleSetNodeZIndex(operation, change);
        break;
      case "createNode":
        this.handleCreateNode(operation, change);
        break;
      case "deleteNode":
        this.handleDeleteNode(operation, change);
        break;
      case "batchUpdateBounds":
        this.handleBatchUpdateBounds(
          operation,
          change
        );
        break;
      case "createLink":
        this.handleCreateLink(operation, change);
        break;
      case "deleteLink":
        this.handleDeleteLink(operation, change);
        break;
      case "createReroute":
        this.handleCreateReroute(operation, change);
        break;
      case "deleteReroute":
        this.handleDeleteReroute(operation, change);
        break;
      case "moveReroute":
        this.handleMoveReroute(operation, change);
        break;
    }
  }
  /**
   * Finalize operation after transaction
   */
  finalizeOperation(change) {
    this.version++;
    change.nodeIds.forEach((nodeId) => {
      const trigger = this.nodeTriggers.get(nodeId);
      if (trigger) {
        trigger();
      }
    });
    setTimeout(() => this.notifyChange(change), 0);
  }
  /**
   * Subscribe to layout changes
   */
  onChange(callback) {
    this.changeListeners.add(callback);
    return () => this.changeListeners.delete(callback);
  }
  /**
   * Set the current operation source
   */
  setSource(source) {
    this.currentSource = source;
  }
  /**
   * Set the current actor (for CRDT)
   */
  setActor(actor) {
    this.currentActor = actor;
  }
  /**
   * Get the current operation source
   */
  getCurrentSource() {
    return this.currentSource;
  }
  /**
   * Get the current actor
   */
  getCurrentActor() {
    return this.currentActor;
  }
  /**
   * Clean up refs and triggers for a node when its Vue component unmounts.
   * This should be called from the component's onUnmounted hook.
   */
  cleanupNodeRef(nodeId) {
    this.nodeRefs.delete(nodeId);
    this.nodeTriggers.delete(nodeId);
  }
  /**
   * Initialize store with existing nodes
   */
  initializeFromLiteGraph(nodes) {
    this.ydoc.transact(() => {
      this.ynodes.clear();
      this.spatialIndex.clear();
      this.linkSegmentSpatialIndex.clear();
      this.slotSpatialIndex.clear();
      this.rerouteSpatialIndex.clear();
      this.linkLayouts.clear();
      this.linkSegmentLayouts.clear();
      this.slotLayouts.clear();
      this.rerouteLayouts.clear();
      nodes.forEach((node2, index2) => {
        const layout = {
          id: node2.id.toString(),
          position: { x: node2.pos[0], y: node2.pos[1] },
          size: { width: node2.size[0], height: node2.size[1] },
          zIndex: index2,
          visible: true,
          bounds: {
            x: node2.pos[0],
            y: node2.pos[1],
            width: node2.size[0],
            height: node2.size[1]
          }
        };
        this.ynodes.set(layout.id, layoutToYNode(layout));
        this.spatialIndex.insert(layout.id, layout.bounds);
      });
      this.nodeTriggers.forEach((trigger) => trigger());
    }, "initialization");
  }
  // Operation handlers
  handleMoveNode(operation, change) {
    const ynode = this.ynodes.get(operation.nodeId);
    if (!ynode) {
      return;
    }
    const size = yNodeToLayout(ynode).size;
    const newBounds = {
      x: operation.position.x,
      y: operation.position.y,
      width: size.width,
      height: size.height
    };
    this.spatialIndex.update(operation.nodeId, newBounds);
    ynode.set("position", operation.position);
    this.updateNodeBounds(ynode, operation.position, size);
    change.nodeIds.push(operation.nodeId);
  }
  handleResizeNode(operation, change) {
    const ynode = this.ynodes.get(operation.nodeId);
    if (!ynode) return;
    const position = yNodeToLayout(ynode).position;
    const newBounds = {
      x: position.x,
      y: position.y,
      width: operation.size.width,
      height: operation.size.height
    };
    this.spatialIndex.update(operation.nodeId, newBounds);
    ynode.set("size", operation.size);
    this.updateNodeBounds(ynode, position, operation.size);
    change.nodeIds.push(operation.nodeId);
  }
  handleSetNodeZIndex(operation, change) {
    const ynode = this.ynodes.get(operation.nodeId);
    if (!ynode) return;
    ynode.set("zIndex", operation.zIndex);
    change.nodeIds.push(operation.nodeId);
  }
  handleCreateNode(operation, change) {
    const ynode = layoutToYNode(operation.layout);
    this.ynodes.set(operation.nodeId, ynode);
    this.spatialIndex.insert(operation.nodeId, operation.layout.bounds);
    change.type = "create";
    change.nodeIds.push(operation.nodeId);
  }
  handleDeleteNode(operation, change) {
    if (!this.ynodes.has(operation.nodeId)) return;
    this.ynodes.delete(operation.nodeId);
    this.spatialIndex.remove(operation.nodeId);
    this.deleteNodeSlotLayouts(operation.nodeId);
    const linksToDelete = this.findLinksConnectedToNode(operation.nodeId);
    for (const linkId of linksToDelete) {
      this.ylinks.delete(String(linkId));
      this.linkLayouts.delete(linkId);
      this.cleanupLinkSegments(linkId);
    }
    change.type = "delete";
    change.nodeIds.push(operation.nodeId);
  }
  handleBatchUpdateBounds(operation, change) {
    const spatialUpdates = [];
    for (const nodeId of operation.nodeIds) {
      const data = operation.bounds[nodeId];
      const ynode = this.ynodes.get(nodeId);
      if (!ynode || !data) continue;
      ynode.set("position", { x: data.bounds.x, y: data.bounds.y });
      ynode.set("size", {
        width: data.bounds.width,
        height: data.bounds.height
      });
      ynode.set("bounds", data.bounds);
      spatialUpdates.push({ nodeId, bounds: data.bounds });
      change.nodeIds.push(nodeId);
    }
    if (spatialUpdates.length > 0) {
      this.spatialIndex.batchUpdate(spatialUpdates);
    }
    if (change.nodeIds.length) {
      change.type = "update";
    }
  }
  handleCreateLink(operation, change) {
    const linkData = new YMap();
    linkData.set("id", operation.linkId);
    linkData.set("sourceNodeId", operation.sourceNodeId);
    linkData.set("sourceSlot", operation.sourceSlot);
    linkData.set("targetNodeId", operation.targetNodeId);
    linkData.set("targetSlot", operation.targetSlot);
    this.ylinks.set(String(operation.linkId), linkData);
    change.type = "create";
  }
  handleDeleteLink(operation, change) {
    if (!this.ylinks.has(String(operation.linkId))) return;
    this.ylinks.delete(String(operation.linkId));
    this.linkLayouts.delete(operation.linkId);
    this.cleanupLinkSegments(operation.linkId);
    change.type = "delete";
  }
  handleCreateReroute(operation, change) {
    const rerouteData = new YMap();
    rerouteData.set("id", operation.rerouteId);
    rerouteData.set("position", operation.position);
    rerouteData.set("parentId", operation.parentId);
    rerouteData.set("linkIds", operation.linkIds);
    this.yreroutes.set(String(operation.rerouteId), rerouteData);
    change.type = "create";
  }
  handleDeleteReroute(operation, change) {
    if (!this.yreroutes.has(String(operation.rerouteId))) return;
    this.yreroutes.delete(String(operation.rerouteId));
    this.rerouteLayouts.delete(operation.rerouteId);
    this.rerouteSpatialIndex.remove(String(operation.rerouteId));
    change.type = "delete";
  }
  handleMoveReroute(operation, change) {
    const yreroute = this.yreroutes.get(String(operation.rerouteId));
    if (!yreroute) return;
    yreroute.set("position", operation.position);
    const pos = operation.position;
    const layout = {
      id: operation.rerouteId,
      position: pos,
      radius: 8,
      bounds: {
        x: pos.x - 8,
        y: pos.y - 8,
        width: 16,
        height: 16
      }
    };
    this.updateRerouteLayout(operation.rerouteId, layout);
    change.type = "update";
  }
  /**
   * Update node bounds helper
   */
  updateNodeBounds(ynode, position, size) {
    ynode.set("bounds", {
      x: position.x,
      y: position.y,
      width: size.width,
      height: size.height
    });
  }
  /**
   * Find all links connected to a specific node
   */
  findLinksConnectedToNode(nodeId) {
    const connectedLinks = [];
    this.ylinks.forEach((linkData, linkIdStr) => {
      const linkId = asLinkId(linkIdStr);
      const sourceNodeId = this.getLinkField(linkData, "sourceNodeId");
      const targetNodeId = this.getLinkField(linkData, "targetNodeId");
      if (sourceNodeId === nodeId || targetNodeId === nodeId) {
        connectedLinks.push(linkId);
      }
    });
    return connectedLinks;
  }
  /**
   * Handle link change events
   */
  handleLinkChange(change, linkIdStr) {
    if (change.action === "delete") {
      const linkId = asLinkId(linkIdStr);
      this.cleanupLinkData(linkId);
    }
  }
  /**
   * Clean up all data associated with a link
   */
  cleanupLinkData(linkId) {
    this.linkLayouts.delete(linkId);
    this.cleanupLinkSegments(linkId);
  }
  /**
   * Clean up all segment layouts for a link
   */
  cleanupLinkSegments(linkId) {
    const keysToDelete = [];
    for (const [key] of this.linkSegmentLayouts) {
      if (key.startsWith(`${linkId}:`)) {
        keysToDelete.push(key);
      }
    }
    for (const key of keysToDelete) {
      this.linkSegmentLayouts.delete(key);
      this.linkSegmentSpatialIndex.remove(key);
    }
  }
  /**
   * Handle reroute change events
   */
  handleRerouteChange(change, rerouteIdStr) {
    const rerouteId = asRerouteId(rerouteIdStr);
    if (change.action === "delete") {
      this.handleRerouteDelete(rerouteId);
    } else {
      this.handleRerouteUpsert(rerouteId);
    }
  }
  /**
   * Handle reroute deletion
   */
  handleRerouteDelete(rerouteId) {
    this.rerouteLayouts.delete(rerouteId);
    this.rerouteSpatialIndex.remove(String(rerouteId));
  }
  /**
   * Handle reroute upsert (update if exists, create if not)
   */
  handleRerouteUpsert(rerouteId) {
    const rerouteData = this.yreroutes.get(String(rerouteId));
    if (!rerouteData) return;
    const position = this.getRerouteField(rerouteData, "position");
    if (!position) return;
    const layout = this.createRerouteLayout(rerouteId, position);
    this.updateRerouteLayout(rerouteId, layout);
  }
  /**
   * Create reroute layout from position
   */
  createRerouteLayout(rerouteId, position) {
    return {
      id: rerouteId,
      position,
      radius: REROUTE_RADIUS,
      bounds: {
        x: position.x - REROUTE_RADIUS,
        y: position.y - REROUTE_RADIUS,
        width: REROUTE_RADIUS * 2,
        height: REROUTE_RADIUS * 2
      }
    };
  }
  // Helper methods
  notifyChange(change) {
    this.changeListeners.forEach((listener) => {
      try {
        listener(change);
      } catch (error) {
        console.error("Error in layout change listener:", error);
      }
    });
  }
  // CRDT-specific methods
  getOperationsSince(timestamp) {
    const operations = [];
    this.yoperations.forEach((op) => {
      if (op && op.timestamp > timestamp) {
        operations.push(op);
      }
    });
    return operations;
  }
  getOperationsByActor(actor) {
    const operations = [];
    this.yoperations.forEach((op) => {
      if (op && op.actor === actor) {
        operations.push(op);
      }
    });
    return operations;
  }
  /**
   * Get the Yjs document for network sync (future feature)
   */
  getYDoc() {
    return this.ydoc;
  }
  /**
   * Apply updates from remote peers (future feature)
   */
  applyUpdate(update) {
    applyUpdate(this.ydoc, update);
  }
  /**
   * Get state as update for sending to peers (future feature)
   */
  getStateAsUpdate() {
    return encodeStateAsUpdate(this.ydoc);
  }
  /**
   * Batch update node bounds using Yjs transaction for atomicity.
   */
  batchUpdateNodeBounds(updates) {
    if (updates.length === 0) return;
    const originalSource = this.currentSource;
    const shouldNormalizeHeights = originalSource === LayoutSource.DOM;
    this.currentSource = LayoutSource.Vue;
    const nodeIds = [];
    const boundsRecord = {};
    for (const { nodeId, bounds } of updates) {
      const ynode = this.ynodes.get(nodeId);
      if (!ynode) continue;
      const currentLayout = yNodeToLayout(ynode);
      const normalizedBounds = shouldNormalizeHeights ? {
        ...bounds,
        height: removeNodeTitleHeight(bounds.height)
      } : bounds;
      boundsRecord[nodeId] = {
        bounds: normalizedBounds,
        previousBounds: currentLayout.bounds
      };
      nodeIds.push(nodeId);
    }
    if (!nodeIds.length) {
      this.currentSource = originalSource;
      return;
    }
    const operation = {
      type: "batchUpdateBounds",
      entity: "node",
      nodeIds,
      bounds: boundsRecord,
      timestamp: Date.now(),
      source: this.currentSource,
      actor: this.currentActor
    };
    this.applyOperation(operation);
    this.currentSource = originalSource;
  }
}
const layoutStore = new LayoutStoreImpl();
const logger$1 = log.getLogger("LayoutMutations");
function useLayoutMutations() {
  const setSource = /* @__PURE__ */ __name((source) => {
    layoutStore.setSource(source);
  }, "setSource");
  const setActor = /* @__PURE__ */ __name((actor) => {
    layoutStore.setActor(actor);
  }, "setActor");
  const moveNode = /* @__PURE__ */ __name((nodeId, position) => {
    const normalizedNodeId = String(nodeId);
    const existing = layoutStore.getNodeLayoutRef(normalizedNodeId).value;
    if (!existing) return;
    layoutStore.applyOperation({
      type: "moveNode",
      entity: "node",
      nodeId: normalizedNodeId,
      position,
      previousPosition: existing.position,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "moveNode");
  const resizeNode = /* @__PURE__ */ __name((nodeId, size) => {
    const normalizedNodeId = String(nodeId);
    const existing = layoutStore.getNodeLayoutRef(normalizedNodeId).value;
    if (!existing) return;
    layoutStore.applyOperation({
      type: "resizeNode",
      entity: "node",
      nodeId: normalizedNodeId,
      size,
      previousSize: existing.size,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "resizeNode");
  const setNodeZIndex = /* @__PURE__ */ __name((nodeId, zIndex) => {
    const normalizedNodeId = String(nodeId);
    const existing = layoutStore.getNodeLayoutRef(normalizedNodeId).value;
    if (!existing) return;
    layoutStore.applyOperation({
      type: "setNodeZIndex",
      entity: "node",
      nodeId: normalizedNodeId,
      zIndex,
      previousZIndex: existing.zIndex,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "setNodeZIndex");
  const createNode = /* @__PURE__ */ __name((nodeId, layout) => {
    const normalizedNodeId = String(nodeId);
    const fullLayout = {
      id: normalizedNodeId,
      position: layout.position ?? { x: 0, y: 0 },
      size: layout.size ?? { width: 200, height: 100 },
      zIndex: layout.zIndex ?? 0,
      visible: layout.visible ?? true,
      bounds: {
        x: layout.position?.x ?? 0,
        y: layout.position?.y ?? 0,
        width: layout.size?.width ?? 200,
        height: layout.size?.height ?? 100
      }
    };
    layoutStore.applyOperation({
      type: "createNode",
      entity: "node",
      nodeId: normalizedNodeId,
      layout: fullLayout,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "createNode");
  const deleteNode = /* @__PURE__ */ __name((nodeId) => {
    const normalizedNodeId = String(nodeId);
    const existing = layoutStore.getNodeLayoutRef(normalizedNodeId).value;
    if (!existing) return;
    layoutStore.applyOperation({
      type: "deleteNode",
      entity: "node",
      nodeId: normalizedNodeId,
      previousLayout: existing,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "deleteNode");
  const bringNodeToFront = /* @__PURE__ */ __name((nodeId) => {
    const allNodes = layoutStore.getAllNodes().value;
    let maxZIndex = 0;
    for (const [, layout] of allNodes) {
      if (layout.zIndex > maxZIndex) {
        maxZIndex = layout.zIndex;
      }
    }
    setNodeZIndex(nodeId, maxZIndex + 1);
  }, "bringNodeToFront");
  const createLink = /* @__PURE__ */ __name((linkId, sourceNodeId, sourceSlot, targetNodeId, targetSlot) => {
    const normalizedSourceNodeId = String(sourceNodeId);
    const normalizedTargetNodeId = String(targetNodeId);
    logger$1.debug("Creating link:", {
      linkId,
      from: `${normalizedSourceNodeId}[${sourceSlot}]`,
      to: `${normalizedTargetNodeId}[${targetSlot}]`
    });
    layoutStore.applyOperation({
      type: "createLink",
      entity: "link",
      linkId,
      sourceNodeId: normalizedSourceNodeId,
      sourceSlot,
      targetNodeId: normalizedTargetNodeId,
      targetSlot,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "createLink");
  const deleteLink = /* @__PURE__ */ __name((linkId) => {
    logger$1.debug("Deleting link:", linkId);
    layoutStore.applyOperation({
      type: "deleteLink",
      entity: "link",
      linkId,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "deleteLink");
  const createReroute = /* @__PURE__ */ __name((rerouteId, position, parentId, linkIds = []) => {
    logger$1.debug("Creating reroute:", {
      rerouteId,
      position,
      parentId,
      linkCount: linkIds.length
    });
    layoutStore.applyOperation({
      type: "createReroute",
      entity: "reroute",
      rerouteId,
      position,
      parentId,
      linkIds,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "createReroute");
  const deleteReroute = /* @__PURE__ */ __name((rerouteId) => {
    logger$1.debug("Deleting reroute:", rerouteId);
    layoutStore.applyOperation({
      type: "deleteReroute",
      entity: "reroute",
      rerouteId,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "deleteReroute");
  const moveReroute = /* @__PURE__ */ __name((rerouteId, position, previousPosition) => {
    logger$1.debug("Moving reroute:", {
      rerouteId,
      from: previousPosition,
      to: position
    });
    layoutStore.applyOperation({
      type: "moveReroute",
      entity: "reroute",
      rerouteId,
      position,
      previousPosition,
      timestamp: Date.now(),
      source: layoutStore.getCurrentSource(),
      actor: layoutStore.getCurrentActor()
    });
  }, "moveReroute");
  return {
    setSource,
    setActor,
    moveNode,
    resizeNode,
    setNodeZIndex,
    createNode,
    deleteNode,
    bringNodeToFront,
    createLink,
    deleteLink,
    createReroute,
    deleteReroute,
    moveReroute
  };
}
__name(useLayoutMutations, "useLayoutMutations");
const PREFIX = "workflow";
const SEPARATOR = ">";
class CanvasPathRenderer {
  static {
    __name(this, "CanvasPathRenderer");
  }
  /**
   * Draw a link between two points
   * Returns a Path2D object for hit detection
   */
  drawLink(ctx, link, context) {
    const path = new Path2D();
    const isHighlighted = context.highlightedIds?.has(link.id) ?? false;
    const color2 = this.determineLinkColor(link, context, isHighlighted);
    ctx.save();
    if (link.disabled && context.patterns?.disabled) {
      ctx.strokeStyle = context.patterns.disabled;
    } else {
      ctx.strokeStyle = color2;
    }
    ctx.lineWidth = context.style.connectionWidth;
    ctx.lineJoin = "round";
    if (context.style.borderWidth && !context.style.lowQuality) {
      this.drawLinkPath(
        ctx,
        path,
        link,
        context,
        context.style.connectionWidth + context.style.borderWidth,
        "rgba(0,0,0,0.5)"
      );
    }
    this.drawLinkPath(
      ctx,
      path,
      link,
      context,
      context.style.connectionWidth,
      color2
    );
    this.calculateCenterPoint(link, context);
    if (context.style.showArrows) {
      this.drawArrows(ctx, link, context, color2);
    }
    if (context.style.showCenterMarker && context.scale && context.scale >= 0.6 && context.style.highQuality) {
      this.drawCenterMarker(ctx, link, context, color2);
    }
    if (link.flow && context.animation) {
      this.drawFlowAnimation(ctx, path, link, context);
    }
    ctx.restore();
    return path;
  }
  determineLinkColor(link, context, isHighlighted) {
    if (isHighlighted) {
      return context.colors.highlighted;
    }
    if (link.color) {
      return link.color;
    }
    if (link.type && context.colors.byType[link.type]) {
      return context.colors.byType[link.type];
    }
    return context.colors.default;
  }
  drawLinkPath(ctx, path, link, context, lineWidth, color2) {
    ctx.strokeStyle = color2;
    ctx.lineWidth = lineWidth;
    const start = link.startPoint;
    const end = link.endPoint;
    if (context.style.mode === "linear") {
      this.buildLinearPath(
        path,
        start,
        end,
        link.startDirection,
        link.endDirection
      );
    } else if (context.style.mode === "straight") {
      this.buildStraightPath(
        path,
        start,
        end,
        link.startDirection,
        link.endDirection
      );
    } else {
      this.buildSplinePath(
        path,
        start,
        end,
        link.startDirection,
        link.endDirection,
        link.controlPoints
      );
    }
    ctx.stroke(path);
  }
  buildLinearPath(path, start, end, startDir, endDir) {
    const l = 15;
    const innerA = { x: start.x, y: start.y };
    const innerB = { x: end.x, y: end.y };
    switch (startDir) {
      case "left":
        innerA.x -= l;
        break;
      case "right":
        innerA.x += l;
        break;
      case "up":
        innerA.y -= l;
        break;
      case "down":
        innerA.y += l;
        break;
    }
    switch (endDir) {
      case "left":
        innerB.x -= l;
        break;
      case "right":
        innerB.x += l;
        break;
      case "up":
        innerB.y -= l;
        break;
      case "down":
        innerB.y += l;
        break;
    }
    path.moveTo(start.x, start.y);
    path.lineTo(innerA.x, innerA.y);
    path.lineTo(innerB.x, innerB.y);
    path.lineTo(end.x, end.y);
  }
  buildStraightPath(path, start, end, startDir, endDir) {
    const l = 10;
    const innerA = { x: start.x, y: start.y };
    const innerB = { x: end.x, y: end.y };
    switch (startDir) {
      case "left":
        innerA.x -= l;
        break;
      case "right":
        innerA.x += l;
        break;
      case "up":
        innerA.y -= l;
        break;
      case "down":
        innerA.y += l;
        break;
    }
    switch (endDir) {
      case "left":
        innerB.x -= l;
        break;
      case "right":
        innerB.x += l;
        break;
      case "up":
        innerB.y -= l;
        break;
      case "down":
        innerB.y += l;
        break;
    }
    const midX = (innerA.x + innerB.x) * 0.5;
    path.moveTo(start.x, start.y);
    path.lineTo(innerA.x, innerA.y);
    path.lineTo(midX, innerA.y);
    path.lineTo(midX, innerB.y);
    path.lineTo(innerB.x, innerB.y);
    path.lineTo(end.x, end.y);
  }
  buildSplinePath(path, start, end, startDir, endDir, controlPoints) {
    path.moveTo(start.x, start.y);
    const controls = controlPoints || this.calculateControlPoints(start, end, startDir, endDir);
    if (controls.length >= 2) {
      path.bezierCurveTo(
        controls[0].x,
        controls[0].y,
        controls[1].x,
        controls[1].y,
        end.x,
        end.y
      );
    } else if (controls.length === 1) {
      path.quadraticCurveTo(controls[0].x, controls[0].y, end.x, end.y);
    } else {
      path.lineTo(end.x, end.y);
    }
  }
  calculateControlPoints(start, end, startDir, endDir) {
    const dist = Math.sqrt(
      Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)
    );
    const controlDist = Math.max(30, dist * 0.25);
    const startControl = this.getDirectionOffset(startDir, controlDist);
    const endControl = this.getDirectionOffset(endDir, controlDist);
    return [
      { x: start.x + startControl.x, y: start.y + startControl.y },
      { x: end.x + endControl.x, y: end.y + endControl.y }
    ];
  }
  getDirectionOffset(direction, distance2) {
    switch (direction) {
      case "left":
        return { x: -distance2, y: 0 };
      case "right":
        return { x: distance2, y: 0 };
      case "up":
        return { x: 0, y: -distance2 };
      case "down":
        return { x: 0, y: distance2 };
      case "none":
      default:
        return { x: 0, y: 0 };
    }
  }
  drawArrows(ctx, link, context, color2) {
    if (!context.style.showArrows) return;
    const positions = [0.25, 0.75];
    for (const t2 of positions) {
      const posA = this.computeConnectionPoint(link, t2, context);
      const posB = this.computeConnectionPoint(link, t2 + 0.01, context);
      const angle = Math.atan2(posB.y - posA.y, posB.x - posA.x);
      const transform = ctx.getTransform();
      ctx.translate(posA.x, posA.y);
      ctx.rotate(angle);
      ctx.fillStyle = color2;
      ctx.beginPath();
      ctx.moveTo(-5, -3);
      ctx.lineTo(0, 7);
      ctx.lineTo(5, -3);
      ctx.fill();
      ctx.setTransform(transform);
    }
  }
  /**
   * Compute a point along the link path at position t (0 to 1)
   * For backward compatibility with original litegraph, this always uses
   * bezier calculation with spline offsets, regardless of render mode.
   * This ensures arrow positions match the original implementation.
   */
  computeConnectionPoint(link, t2, _context) {
    const { startPoint, endPoint, startDirection, endDirection } = link;
    const dist = Math.sqrt(
      Math.pow(endPoint.x - startPoint.x, 2) + Math.pow(endPoint.y - startPoint.y, 2)
    );
    const factor = 0.25;
    const pa = { x: startPoint.x, y: startPoint.y };
    const pb = { x: endPoint.x, y: endPoint.y };
    switch (startDirection) {
      case "left":
        pa.x -= dist * factor;
        break;
      case "right":
        pa.x += dist * factor;
        break;
      case "up":
        pa.y -= dist * factor;
        break;
      case "down":
        pa.y += dist * factor;
        break;
    }
    switch (endDirection) {
      case "left":
        pb.x -= dist * factor;
        break;
      case "right":
        pb.x += dist * factor;
        break;
      case "up":
        pb.y -= dist * factor;
        break;
      case "down":
        pb.y += dist * factor;
        break;
    }
    const c1 = (1 - t2) * (1 - t2) * (1 - t2);
    const c2 = 3 * ((1 - t2) * (1 - t2)) * t2;
    const c3 = 3 * (1 - t2) * (t2 * t2);
    const c4 = t2 * t2 * t2;
    return {
      x: c1 * startPoint.x + c2 * pa.x + c3 * pb.x + c4 * endPoint.x,
      y: c1 * startPoint.y + c2 * pa.y + c3 * pb.y + c4 * endPoint.y
    };
  }
  drawFlowAnimation(ctx, _path, link, context) {
    if (!context.animation) return;
    const time = context.animation.time;
    const linkColor = this.determineLinkColor(link, context, false);
    ctx.save();
    ctx.fillStyle = linkColor;
    for (let i = 0; i < 5; ++i) {
      const f = (time + i * 0.2) % 1;
      const flowPos = this.computeConnectionPoint(link, f, context);
      ctx.beginPath();
      ctx.arc(flowPos.x, flowPos.y, 5, 0, 2 * Math.PI);
      ctx.fill();
    }
    ctx.restore();
  }
  /**
   * Utility to find a point on a bezier curve (for hit detection)
   */
  findPointOnBezier(t2, p0, p1, p2, p3) {
    const mt = 1 - t2;
    const mt2 = mt * mt;
    const mt3 = mt2 * mt;
    const t22 = t2 * t2;
    const t3 = t22 * t2;
    return {
      x: mt3 * p0.x + 3 * mt2 * t2 * p1.x + 3 * mt * t22 * p2.x + t3 * p3.x,
      y: mt3 * p0.y + 3 * mt2 * t2 * p1.y + 3 * mt * t22 * p2.y + t3 * p3.y
    };
  }
  /**
   * Draw a link being dragged from a slot to the mouse position
   * Returns a Path2D object for potential hit detection
   */
  drawDraggingLink(ctx, dragData, context) {
    const linkData = dragData.fromInput ? {
      id: "dragging",
      startPoint: dragData.dragPoint,
      endPoint: dragData.fixedPoint,
      startDirection: dragData.dragDirection || this.getOppositeDirection(dragData.fixedDirection),
      endDirection: dragData.fixedDirection,
      color: dragData.color,
      type: dragData.type,
      disabled: dragData.disabled
    } : {
      id: "dragging",
      startPoint: dragData.fixedPoint,
      endPoint: dragData.dragPoint,
      startDirection: dragData.fixedDirection,
      endDirection: dragData.dragDirection || this.getOppositeDirection(dragData.fixedDirection),
      color: dragData.color,
      type: dragData.type,
      disabled: dragData.disabled
    };
    return this.drawLink(ctx, linkData, context);
  }
  /**
   * Get the opposite direction (for drag preview)
   */
  getOppositeDirection(direction) {
    switch (direction) {
      case "left":
        return "right";
      case "right":
        return "left";
      case "up":
        return "down";
      case "down":
        return "up";
      case "none":
      default:
        return "none";
    }
  }
  /**
   * Get the center point of a link (useful for labels, debugging)
   */
  getLinkCenter(link) {
    return {
      x: (link.startPoint.x + link.endPoint.x) / 2,
      y: (link.startPoint.y + link.endPoint.y) / 2
    };
  }
  /**
   * Calculate and store the center point and angle of a link
   * Mimics the original litegraph center point calculation
   */
  calculateCenterPoint(link, context) {
    const { startPoint, endPoint, controlPoints } = link;
    if (context.style.mode === "spline" && controlPoints && controlPoints.length >= 2) {
      const centerPos = this.findPointOnBezier(
        0.5,
        startPoint,
        controlPoints[0],
        controlPoints[1],
        endPoint
      );
      link.centerPos = centerPos;
      if (context.style.centerMarkerShape === "arrow") {
        const justPastCenter = this.findPointOnBezier(
          0.51,
          startPoint,
          controlPoints[0],
          controlPoints[1],
          endPoint
        );
        link.centerAngle = Math.atan2(
          justPastCenter.y - centerPos.y,
          justPastCenter.x - centerPos.x
        );
      }
    } else if (context.style.mode === "linear") {
      const l = 15;
      const innerA = { x: startPoint.x, y: startPoint.y };
      const innerB = { x: endPoint.x, y: endPoint.y };
      switch (link.startDirection) {
        case "left":
          innerA.x -= l;
          break;
        case "right":
          innerA.x += l;
          break;
        case "up":
          innerA.y -= l;
          break;
        case "down":
          innerA.y += l;
          break;
      }
      switch (link.endDirection) {
        case "left":
          innerB.x -= l;
          break;
        case "right":
          innerB.x += l;
          break;
        case "up":
          innerB.y -= l;
          break;
        case "down":
          innerB.y += l;
          break;
      }
      link.centerPos = {
        x: (innerA.x + innerB.x) * 0.5,
        y: (innerA.y + innerB.y) * 0.5
      };
      if (context.style.centerMarkerShape === "arrow") {
        link.centerAngle = Math.atan2(innerB.y - innerA.y, innerB.x - innerA.x);
      }
    } else if (context.style.mode === "straight") {
      const l = 10;
      const innerA = { x: startPoint.x, y: startPoint.y };
      const innerB = { x: endPoint.x, y: endPoint.y };
      switch (link.startDirection) {
        case "left":
          innerA.x -= l;
          break;
        case "right":
          innerA.x += l;
          break;
        case "up":
          innerA.y -= l;
          break;
        case "down":
          innerA.y += l;
          break;
      }
      switch (link.endDirection) {
        case "left":
          innerB.x -= l;
          break;
        case "right":
          innerB.x += l;
          break;
        case "up":
          innerB.y -= l;
          break;
        case "down":
          innerB.y += l;
          break;
      }
      const midX = (innerA.x + innerB.x) * 0.5;
      link.centerPos = {
        x: midX,
        y: (innerA.y + innerB.y) * 0.5
      };
      if (context.style.centerMarkerShape === "arrow") {
        const diff2 = innerB.y - innerA.y;
        if (Math.abs(diff2) < 4) {
          link.centerAngle = 0;
        } else if (diff2 > 0) {
          link.centerAngle = Math.PI * 0.5;
        } else {
          link.centerAngle = -(Math.PI * 0.5);
        }
      }
    } else {
      link.centerPos = this.getLinkCenter(link);
      if (context.style.centerMarkerShape === "arrow") {
        link.centerAngle = Math.atan2(
          endPoint.y - startPoint.y,
          endPoint.x - startPoint.x
        );
      }
    }
  }
  /**
   * Draw the center marker on a link (for menu interaction)
   * Matches the original litegraph center marker rendering
   */
  drawCenterMarker(ctx, link, context, color2) {
    if (!link.centerPos) return;
    ctx.beginPath();
    if (context.style.centerMarkerShape === "arrow" && link.centerAngle !== void 0) {
      const transform = ctx.getTransform();
      ctx.translate(link.centerPos.x, link.centerPos.y);
      ctx.rotate(link.centerAngle);
      ctx.moveTo(-3.2, -5);
      ctx.lineTo(7, 0);
      ctx.lineTo(-3.2, 5);
      ctx.setTransform(transform);
    } else {
      ctx.arc(link.centerPos.x, link.centerPos.y, 5, 0, Math.PI * 2);
    }
    if (link.disabled && context.patterns?.disabled) {
      const { fillStyle, globalAlpha } = ctx;
      ctx.fillStyle = context.patterns.disabled;
      ctx.globalAlpha = 0.75;
      ctx.fill();
      ctx.globalAlpha = globalAlpha;
      ctx.fillStyle = fillStyle;
    } else {
      ctx.fillStyle = color2;
      ctx.fill();
    }
  }
}
class LitegraphLinkAdapter {
  static {
    __name(this, "LitegraphLinkAdapter");
  }
  constructor(enableLayoutStoreWrites = true) {
    this.enableLayoutStoreWrites = enableLayoutStoreWrites;
  }
  pathRenderer = new CanvasPathRenderer();
  /**
   * Convert LinkDirection enum to Direction string
   */
  convertDirection(dir) {
    switch (dir) {
      case LinkDirection.LEFT:
        return "left";
      case LinkDirection.RIGHT:
        return "right";
      case LinkDirection.UP:
        return "up";
      case LinkDirection.DOWN:
        return "down";
      case LinkDirection.CENTER:
        return "none";
      default:
        return "right";
    }
  }
  /**
   * Convert LinkRenderContext to PathRenderContext
   */
  convertToPathRenderContext(context) {
    const shouldShowArrows = context.scale >= 0.6 && context.highQualityRender && context.renderConnectionArrows;
    const shouldShowCenterMarker = context.linkMarkerShape !== LinkMarkerShape.None;
    return {
      style: {
        mode: this.convertRenderMode(context.renderMode),
        connectionWidth: context.connectionWidth,
        borderWidth: context.renderBorder ? 4 : void 0,
        arrowShape: this.convertArrowShape(context.linkMarkerShape),
        showArrows: shouldShowArrows,
        lowQuality: context.lowQuality,
        // Center marker settings (matches original litegraph behavior)
        showCenterMarker: shouldShowCenterMarker,
        centerMarkerShape: context.linkMarkerShape === LinkMarkerShape.Arrow ? "arrow" : "circle",
        highQuality: context.highQualityRender
      },
      colors: {
        default: String(context.defaultLinkColor),
        byType: this.convertColorMap(context.linkTypeColors),
        highlighted: "#FFF"
      },
      patterns: {
        disabled: context.disabledPattern
      },
      animation: {
        time: LiteGraph.getTime() * 1e-3
      },
      scale: context.scale,
      highlightedIds: new Set(Array.from(context.highlightedLinks).map(String))
    };
  }
  /**
   * Convert LinkRenderType to RenderMode
   */
  convertRenderMode(mode) {
    switch (mode) {
      case LinkRenderType.LINEAR_LINK:
        return "linear";
      case LinkRenderType.STRAIGHT_LINK:
        return "straight";
      case LinkRenderType.SPLINE_LINK:
      default:
        return "spline";
    }
  }
  /**
   * Convert LinkMarkerShape to ArrowShape
   */
  convertArrowShape(shape2) {
    switch (shape2) {
      case LinkMarkerShape.Circle:
        return "circle";
      case LinkMarkerShape.Arrow:
      default:
        return "triangle";
    }
  }
  /**
   * Convert color map to ensure all values are strings
   */
  convertColorMap(colors2) {
    const result = {};
    for (const [key, value] of Object.entries(colors2)) {
      result[key] = String(value);
    }
    return result;
  }
  /**
   * Apply spline offset to a point, mimicking original #addSplineOffset behavior
   * Critically: does nothing for CENTER/NONE directions (no case for them)
   */
  applySplineOffset(point, direction, distance2) {
    switch (direction) {
      case LinkDirection.LEFT:
        point.x -= distance2;
        break;
      case LinkDirection.RIGHT:
        point.x += distance2;
        break;
      case LinkDirection.UP:
        point.y -= distance2;
        break;
      case LinkDirection.DOWN:
        point.y += distance2;
        break;
    }
  }
  /**
   * Direct rendering method compatible with LGraphCanvas
   * Converts data and delegates to pure renderer
   */
  renderLinkDirect(ctx, a, b, link, skip_border, flow, color2, start_dir, end_dir, context, extras = {}) {
    const startDir = start_dir || LinkDirection.RIGHT;
    const endDir = end_dir || LinkDirection.LEFT;
    const flowBool = flow === true || typeof flow === "number" && flow > 0;
    const linkData = {
      id: link ? String(link.id) : "temp",
      startPoint: { x: a[0], y: a[1] },
      endPoint: { x: b[0], y: b[1] },
      startDirection: this.convertDirection(startDir),
      endDirection: this.convertDirection(endDir),
      color: color2 !== null && color2 !== void 0 ? String(color2) : void 0,
      type: link?.type !== void 0 ? String(link.type) : void 0,
      flow: flowBool,
      disabled: extras.disabled || false
    };
    if (context.renderMode === LinkRenderType.SPLINE_LINK) {
      const hasStartCtrl = !!extras.startControl;
      const hasEndCtrl = !!extras.endControl;
      const dist = Math.sqrt(
        (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])
      );
      const factor = 0.25;
      const cps = [];
      if (hasStartCtrl && hasEndCtrl) {
        cps.push(
          {
            x: a[0] + (extras.startControl[0] || 0),
            y: a[1] + (extras.startControl[1] || 0)
          },
          {
            x: b[0] + (extras.endControl[0] || 0),
            y: b[1] + (extras.endControl[1] || 0)
          }
        );
        linkData.controlPoints = cps;
      } else if (hasStartCtrl && !hasEndCtrl) {
        const start = {
          x: a[0] + (extras.startControl[0] || 0),
          y: a[1] + (extras.startControl[1] || 0)
        };
        const end = { x: b[0], y: b[1] };
        this.applySplineOffset(end, endDir, dist * factor);
        cps.push(start, end);
        linkData.controlPoints = cps;
      } else if (!hasStartCtrl && hasEndCtrl) {
        const start = { x: a[0], y: a[1] };
        this.applySplineOffset(start, startDir, dist * factor);
        const end = {
          x: b[0] + (extras.endControl[0] || 0),
          y: b[1] + (extras.endControl[1] || 0)
        };
        cps.push(start, end);
        linkData.controlPoints = cps;
      } else {
        const start = { x: a[0], y: a[1] };
        const end = { x: b[0], y: b[1] };
        this.applySplineOffset(start, startDir, dist * factor);
        this.applySplineOffset(end, endDir, dist * factor);
        cps.push(start, end);
        linkData.controlPoints = cps;
      }
    }
    const pathContext = this.convertToPathRenderContext(context);
    if (skip_border) {
      pathContext.style.borderWidth = void 0;
    }
    const path = this.pathRenderer.drawLink(ctx, linkData, pathContext);
    const linkSegment = extras.reroute ?? link;
    if (linkSegment) {
      linkSegment.path = path;
      if (linkData.centerPos) {
        linkSegment._pos = linkSegment._pos || [0, 0];
        linkSegment._pos[0] = linkData.centerPos.x;
        linkSegment._pos[1] = linkData.centerPos.y;
        if (linkData.centerAngle !== void 0) {
          linkSegment._centreAngle = linkData.centerAngle;
        }
      }
      if (this.enableLayoutStoreWrites && link && link.id !== -1) {
        const bounds = this.calculateLinkBounds(
          [linkData.startPoint.x, linkData.startPoint.y],
          [linkData.endPoint.x, linkData.endPoint.y],
          linkData
        );
        const centerPos = linkData.centerPos || {
          x: (linkData.startPoint.x + linkData.endPoint.x) / 2,
          y: (linkData.startPoint.y + linkData.endPoint.y) / 2
        };
        if (!extras.reroute) {
          layoutStore.updateLinkLayout(link.id, {
            id: link.id,
            path,
            bounds,
            centerPos,
            sourceNodeId: String(link.origin_id),
            targetNodeId: String(link.target_id),
            sourceSlot: link.origin_slot,
            targetSlot: link.target_slot
          });
        }
        const rerouteId = extras.reroute ? extras.reroute.id : null;
        layoutStore.updateLinkSegmentLayout(link.id, rerouteId, {
          path,
          bounds,
          centerPos
        });
      }
    }
  }
  renderDraggingLink(ctx, from, to, colour, startDir, endDir, context) {
    this.renderLinkDirect(
      ctx,
      from,
      to,
      null,
      false,
      null,
      colour,
      startDir,
      endDir,
      {
        ...context,
        linkMarkerShape: LinkMarkerShape.None
      },
      {
        disabled: false
      }
    );
  }
  /**
   * Calculate bounding box for a link
   * Includes padding for line width and control points
   */
  calculateLinkBounds(startPos, endPos, linkData) {
    let minX = Math.min(startPos[0], endPos[0]);
    let maxX = Math.max(startPos[0], endPos[0]);
    let minY = Math.min(startPos[1], endPos[1]);
    let maxY = Math.max(startPos[1], endPos[1]);
    if (linkData.controlPoints) {
      for (const cp of linkData.controlPoints) {
        minX = Math.min(minX, cp.x);
        maxX = Math.max(maxX, cp.x);
        minY = Math.min(minY, cp.y);
        maxY = Math.max(maxY, cp.y);
      }
    }
    const padding = 20;
    return {
      x: minX - padding,
      y: minY - padding,
      width: maxX - minX + 2 * padding,
      height: maxY - minY + 2 * padding
    };
  }
}
function shallowCloneCommonProps(slot) {
  const {
    color_off,
    color_on,
    dir,
    label,
    localized_name,
    locked,
    name: name2,
    nameLocked,
    removable,
    shape: shape2,
    type
  } = slot;
  return {
    color_off,
    color_on,
    dir,
    label,
    localized_name,
    locked,
    name: name2,
    nameLocked,
    removable,
    shape: shape2,
    type
  };
}
__name(shallowCloneCommonProps, "shallowCloneCommonProps");
function inputAsSerialisable(slot) {
  const { link } = slot;
  const widgetOrPos = slot.widget ? { widget: { name: slot.widget.name } } : { pos: slot.pos };
  return {
    ...shallowCloneCommonProps(slot),
    ...widgetOrPos,
    link
  };
}
__name(inputAsSerialisable, "inputAsSerialisable");
function outputAsSerialisable(slot) {
  const { pos, slot_index, links, widget } = slot;
  const outputWidget = widget ? { widget: { name: widget.name } } : null;
  return {
    ...shallowCloneCommonProps(slot),
    ...outputWidget,
    pos,
    slot_index,
    links
  };
}
__name(outputAsSerialisable, "outputAsSerialisable");
function isINodeInputSlot(slot) {
  return "link" in slot;
}
__name(isINodeInputSlot, "isINodeInputSlot");
function isWidgetInputSlot(slot) {
  return !!slot.widget;
}
__name(isWidgetInputSlot, "isWidgetInputSlot");
function getSlotKey(nodeIdOrIdentifier, index2, isInput) {
  if (typeof nodeIdOrIdentifier === "object") {
    const { nodeId, index: index22, isInput: isInput2 } = nodeIdOrIdentifier;
    return `${nodeId}-${isInput2 ? "in" : "out"}-${index22}`;
  }
  if (index2 === void 0 || isInput === void 0) {
    throw new Error("Missing required parameters for slot key generation");
  }
  return `${nodeIdOrIdentifier}-${isInput ? "in" : "out"}-${index2}`;
}
__name(getSlotKey, "getSlotKey");
function calculateInputSlotPos(context, slot) {
  const input = context.inputs[slot];
  if (!input) return [context.nodeX, context.nodeY];
  return calculateInputSlotPosFromSlot(context, input);
}
__name(calculateInputSlotPos, "calculateInputSlotPos");
function calculateInputSlotPosFromSlot(context, input) {
  const { nodeX, nodeY, collapsed } = context;
  if (collapsed) {
    const halfTitle = LiteGraph.NODE_TITLE_HEIGHT * 0.5;
    return [nodeX, nodeY - halfTitle];
  }
  const { pos } = input;
  if (pos) return [nodeX + pos[0], nodeY + pos[1]];
  const offsetX = LiteGraph.NODE_SLOT_HEIGHT * 0.5;
  const nodeOffsetY = context.slotStartY || 0;
  const defaultVerticalInputs = getDefaultVerticalInputs(context);
  const slotIndex = defaultVerticalInputs.indexOf(input);
  const slotY = (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT;
  return [nodeX + offsetX, nodeY + slotY + nodeOffsetY];
}
__name(calculateInputSlotPosFromSlot, "calculateInputSlotPosFromSlot");
function calculateOutputSlotPos(context, slot) {
  const { nodeX, nodeY, nodeWidth, collapsed, collapsedWidth, outputs } = context;
  if (collapsed) {
    const width2 = collapsedWidth || LiteGraph.NODE_COLLAPSED_WIDTH;
    const halfTitle = LiteGraph.NODE_TITLE_HEIGHT * 0.5;
    return [nodeX + width2, nodeY - halfTitle];
  }
  const outputSlot = outputs[slot];
  if (!outputSlot) return [nodeX + nodeWidth, nodeY];
  const outputPos = outputSlot.pos;
  if (outputPos) return [nodeX + outputPos[0], nodeY + outputPos[1]];
  const offsetX = LiteGraph.NODE_SLOT_HEIGHT * 0.5;
  const nodeOffsetY = context.slotStartY || 0;
  const defaultVerticalOutputs = getDefaultVerticalOutputs(context);
  const slotIndex = defaultVerticalOutputs.indexOf(outputSlot);
  const slotY = (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT;
  return [nodeX + nodeWidth + 1 - offsetX, nodeY + slotY + nodeOffsetY];
}
__name(calculateOutputSlotPos, "calculateOutputSlotPos");
function getSlotPosition(node2, slotIndex, isInput) {
  const slotKey = getSlotKey(String(node2.id), slotIndex, isInput);
  const slotLayout = layoutStore.getSlotLayout(slotKey);
  if (slotLayout) {
    return [slotLayout.position.x, slotLayout.position.y];
  }
  const nodeLayout = layoutStore.getNodeLayoutRef(String(node2.id)).value;
  if (nodeLayout) {
    const context2 = {
      nodeX: nodeLayout.position.x,
      nodeY: nodeLayout.position.y,
      nodeWidth: nodeLayout.size.width,
      nodeHeight: nodeLayout.size.height,
      collapsed: node2.flags.collapsed || false,
      collapsedWidth: node2._collapsed_width,
      slotStartY: node2.constructor.slot_start_y,
      inputs: node2.inputs,
      outputs: node2.outputs,
      widgets: node2.widgets
    };
    return isInput ? calculateInputSlotPos(context2, slotIndex) : calculateOutputSlotPos(context2, slotIndex);
  }
  const context = {
    nodeX: node2.pos[0],
    nodeY: node2.pos[1],
    nodeWidth: node2.size[0],
    nodeHeight: node2.size[1],
    collapsed: node2.flags.collapsed || false,
    collapsedWidth: node2._collapsed_width,
    slotStartY: node2.constructor.slot_start_y,
    inputs: node2.inputs,
    outputs: node2.outputs,
    widgets: node2.widgets
  };
  return isInput ? calculateInputSlotPos(context, slotIndex) : calculateOutputSlotPos(context, slotIndex);
}
__name(getSlotPosition, "getSlotPosition");
function getDefaultVerticalInputs(context) {
  return context.inputs.filter(
    (slot) => !slot.pos && !(context.widgets?.length && isWidgetInputSlot(slot))
  );
}
__name(getDefaultVerticalInputs, "getDefaultVerticalInputs");
function getDefaultVerticalOutputs(context) {
  return context.outputs.filter((slot) => !slot.pos);
}
__name(getDefaultVerticalOutputs, "getDefaultVerticalOutputs");
class CanvasPointer {
  static {
    __name(this, "CanvasPointer");
  }
  /** Maximum time in milliseconds to ignore click drift */
  static bufferTime = 150;
  /** Maximum gap between pointerup and pointerdown events to be considered as a double click */
  static doubleClickTime = 300;
  /** Maximum offset from click location */
  static get maxClickDrift() {
    return this.#maxClickDrift;
  }
  static set maxClickDrift(value) {
    this.#maxClickDrift = value;
    this.#maxClickDrift2 = value * value;
  }
  static #maxClickDrift = 6;
  /** {@link maxClickDrift} squared.  Used to calculate click drift without `sqrt`. */
  static #maxClickDrift2 = this.#maxClickDrift ** 2;
  /** Assume that "wheel" events with both deltaX and deltaY less than this value are trackpad gestures. */
  static trackpadThreshold = 60;
  /**
   * The minimum time between "wheel" events to allow switching between trackpad
   * and mouse modes.
   *
   * This prevents trackpad "flick" panning from registering as regular mouse wheel.
   * After a flick gesture is complete, the automatic wheel events are sent with
   * reduced frequency, but much higher deltaX and deltaY values.
   */
  static trackpadMaxGap = 500;
  /** The maximum time in milliseconds to buffer a high-res wheel event. */
  static maxHighResBufferTime = 10;
  /** The element this PointerState should capture input against when dragging. */
  element;
  /** Pointer ID used by drag capture. */
  pointerId;
  /** Set to true when if the pointer moves far enough after a down event, before the corresponding up event is fired. */
  dragStarted = false;
  /** The {@link eUp} from the last successful click */
  eLastDown;
  /** Used downstream for touch event support. */
  isDouble = false;
  /** Used downstream for touch event support. */
  isDown = false;
  /** The resize handle currently being hovered or dragged */
  resizeDirection;
  /**
   * If `true`, {@link eDown}, {@link eMove}, and {@link eUp} will be set to
   * `undefined` when {@link reset} is called.
   *
   * Default: `true`
   */
  clearEventsOnReset = true;
  /** The last pointerdown event for the primary button */
  eDown;
  /** The last pointermove event for the primary button */
  eMove;
  /** The last pointerup event for the primary button */
  eUp;
  /** Currently detected input device type */
  detectedDevice = "mouse";
  /** Timestamp of last wheel event for cooldown tracking */
  lastWheelEventTime = 0;
  /** Flag to track if we've received the first wheel event */
  hasReceivedWheelEvent = false;
  /** Buffered Linux wheel event awaiting confirmation */
  bufferedLinuxEvent;
  /** Timestamp when Linux event was buffered */
  bufferedLinuxEventTime = 0;
  /** Timer ID for Linux buffer clearing */
  linuxBufferTimeoutId;
  /**
   * Run-once callback, called at the end of any click or drag, whether or not it was successful in any way.
   *
   * The setter of this callback will call the existing value before replacing it.
   * Therefore, simply setting this value twice will execute the first callback.
   */
  get finally() {
    return this.#finally;
  }
  set finally(value) {
    try {
      this.#finally?.();
    } finally {
      this.#finally = value;
    }
  }
  #finally;
  constructor(element) {
    this.element = element;
  }
  /**
   * Callback for `pointerdown` events.  To be used as the event handler (or called by it).
   * @param e The `pointerdown` event
   */
  down(e2) {
    this.reset();
    this.eDown = e2;
    this.pointerId = e2.pointerId;
    this.element.setPointerCapture(e2.pointerId);
  }
  /**
   * Callback for `pointermove` events.  To be used as the event handler (or called by it).
   * @param e The `pointermove` event
   */
  move(e2) {
    const { eDown } = this;
    if (!eDown) return;
    if (!e2.buttons) {
      this.reset();
      return;
    }
    if (!(e2.buttons & eDown.buttons)) {
      this.#completeClick(e2);
      this.reset();
      return;
    }
    this.eMove = e2;
    this.onDrag?.(e2);
    if (this.dragStarted) return;
    const longerThanBufferTime = e2.timeStamp - eDown.timeStamp > CanvasPointer.bufferTime;
    if (longerThanBufferTime || !this.#hasSamePosition(e2, eDown)) {
      this.#setDragStarted(e2);
    }
  }
  /**
   * Callback for `pointerup` events.  To be used as the event handler (or called by it).
   * @param e The `pointerup` event
   */
  up(e2) {
    if (e2.button !== this.eDown?.button) return false;
    this.#completeClick(e2);
    const { dragStarted } = this;
    this.reset();
    return !dragStarted;
  }
  #completeClick(e2) {
    const { eDown } = this;
    if (!eDown) return;
    this.eUp = e2;
    if (this.dragStarted) {
      this.onDragEnd?.(e2);
    } else if (!this.#hasSamePosition(e2, eDown)) {
      this.#setDragStarted();
      this.onDragEnd?.(e2);
    } else if (this.onDoubleClick && this.#isDoubleClick()) {
      this.onDoubleClick(e2);
      this.eLastDown = void 0;
    } else {
      this.onClick?.(e2);
      this.eLastDown = eDown;
    }
  }
  /**
   * Checks if two events occurred near each other - not further apart than the maximum click drift.
   * @param a The first event to compare
   * @param b The second event to compare
   * @param tolerance2 The maximum distance (squared) before the positions are considered different
   * @returns `true` if the two events were no more than {@link maxClickDrift} apart, otherwise `false`
   */
  #hasSamePosition(a, b, tolerance2 = CanvasPointer.#maxClickDrift2) {
    const drift = dist2(a.clientX, a.clientY, b.clientX, b.clientY);
    return drift <= tolerance2;
  }
  /**
   * Checks whether the pointer is currently past the max click drift threshold.
   * @returns `true` if the latest pointer event is past the the click drift threshold
   */
  #isDoubleClick() {
    const { eDown, eLastDown } = this;
    if (!eDown || !eLastDown) return false;
    const tolerance2 = (3 * CanvasPointer.#maxClickDrift) ** 2;
    const diff2 = eDown.timeStamp - eLastDown.timeStamp;
    return diff2 > 0 && diff2 < CanvasPointer.doubleClickTime && this.#hasSamePosition(eDown, eLastDown, tolerance2);
  }
  #setDragStarted(eMove) {
    this.dragStarted = true;
    this.onDragStart?.(this, eMove);
    delete this.onDragStart;
  }
  /**
   * Checks if the given wheel event is part of a trackpad gesture.
   * This method now uses the new device detection internally for improved accuracy.
   * @param e The wheel event to check
   * @returns `true` if the event is part of a trackpad gesture, otherwise `false`
   */
  isTrackpadGesture(e2) {
    const now = performance.now();
    const timeSinceLastEvent = Math.max(0, now - this.lastWheelEventTime);
    this.lastWheelEventTime = now;
    if (this.#isHighResWheelEvent(e2, now)) {
      this.detectedDevice = "mouse";
    } else if (this.#isWithinCooldown(timeSinceLastEvent)) {
      if (this.#shouldBufferLinuxEvent(e2)) {
        this.#bufferLinuxEvent(e2, now);
      }
    } else {
      this.#updateDeviceMode(e2, now);
      this.hasReceivedWheelEvent = true;
    }
    return this.detectedDevice === "trackpad";
  }
  /**
   * Validates buffered high res wheel events and switches to mouse mode if pattern matches.
   * @returns `true` if switched to mouse mode
   */
  #isHighResWheelEvent(event, now) {
    if (!this.bufferedLinuxEvent || this.bufferedLinuxEventTime <= 0) {
      return false;
    }
    const timeSinceBuffer = now - this.bufferedLinuxEventTime;
    if (timeSinceBuffer > CanvasPointer.maxHighResBufferTime) {
      this.#clearLinuxBuffer();
      return false;
    }
    if (event.deltaX === 0 && this.#isLinuxWheelPattern(this.bufferedLinuxEvent.deltaY, event.deltaY)) {
      this.#clearLinuxBuffer();
      return true;
    }
    return false;
  }
  /**
   * Checks if we're within the cooldown period where mode switching is disabled.
   */
  #isWithinCooldown(timeSinceLastEvent) {
    const isFirstEvent = !this.hasReceivedWheelEvent;
    const cooldownExpired = timeSinceLastEvent >= CanvasPointer.trackpadMaxGap;
    return !isFirstEvent && !cooldownExpired;
  }
  /**
   * Updates the device mode based on event patterns.
   */
  #updateDeviceMode(event, now) {
    if (this.#isTrackpadPattern(event)) {
      this.detectedDevice = "trackpad";
    } else if (this.#isMousePattern(event)) {
      this.detectedDevice = "mouse";
    } else if (this.detectedDevice === "trackpad" && this.#shouldBufferLinuxEvent(event)) {
      this.#bufferLinuxEvent(event, now);
    }
  }
  /**
   * Clears the buffered Linux wheel event and associated timer.
   */
  #clearLinuxBuffer() {
    this.bufferedLinuxEvent = void 0;
    this.bufferedLinuxEventTime = 0;
    if (this.linuxBufferTimeoutId !== void 0) {
      clearTimeout(this.linuxBufferTimeoutId);
      this.linuxBufferTimeoutId = void 0;
    }
  }
  /**
   * Checks if the event matches trackpad input patterns.
   * @param event The wheel event to check
   */
  #isTrackpadPattern(event) {
    if (event.deltaX !== 0 && event.deltaY !== 0) return true;
    if (event.ctrlKey && Math.abs(event.deltaY) < 10) return true;
    return false;
  }
  /**
   * Checks if the event matches mouse wheel input patterns.
   * @param event The wheel event to check
   */
  #isMousePattern(event) {
    const absoluteDeltaY = Math.abs(event.deltaY);
    if (absoluteDeltaY > 80) return true;
    return absoluteDeltaY >= 60 && event.deltaX === 0 && this.detectedDevice === "mouse";
  }
  /**
   * Checks if the event should be buffered as a potential Linux wheel event.
   * @param event The wheel event to check
   */
  #shouldBufferLinuxEvent(event) {
    const absoluteDeltaY = Math.abs(event.deltaY);
    const isInLinuxRange = absoluteDeltaY >= 10 && absoluteDeltaY < 60;
    const isVerticalOnly = event.deltaX === 0;
    const hasIntegerDelta = Number.isInteger(event.deltaY);
    return this.detectedDevice === "trackpad" && isInLinuxRange && isVerticalOnly && hasIntegerDelta;
  }
  /**
   * Buffers a potential Linux wheel event for later confirmation.
   * @param event The event to buffer
   * @param now The current timestamp
   */
  #bufferLinuxEvent(event, now) {
    if (this.linuxBufferTimeoutId !== void 0) {
      clearTimeout(this.linuxBufferTimeoutId);
    }
    this.bufferedLinuxEvent = event;
    this.bufferedLinuxEventTime = now;
    this.linuxBufferTimeoutId = setTimeout(() => {
      this.#clearLinuxBuffer();
    }, CanvasPointer.maxHighResBufferTime);
  }
  /**
   * Checks if two deltaY values follow a Linux wheel pattern (divisibility).
   * @param deltaY1 The first deltaY value
   * @param deltaY2 The second deltaY value
   */
  #isLinuxWheelPattern(deltaY1, deltaY2) {
    const absolute1 = Math.abs(deltaY1);
    const absolute2 = Math.abs(deltaY2);
    if (absolute1 === 0 || absolute2 === 0) return false;
    if (absolute1 === absolute2) return true;
    return absolute1 % absolute2 === 0 || absolute2 % absolute1 === 0;
  }
  /**
   * Resets the state of this {@link CanvasPointer} instance.
   *
   * The {@link finally} callback is first executed, then all callbacks and intra-click
   * state is cleared.
   */
  reset() {
    this.finally = void 0;
    delete this.onClick;
    delete this.onDoubleClick;
    delete this.onDragStart;
    delete this.onDrag;
    delete this.onDragEnd;
    this.isDown = false;
    this.isDouble = false;
    this.dragStarted = false;
    this.resizeDirection = void 0;
    if (this.clearEventsOnReset) {
      this.eDown = void 0;
      this.eMove = void 0;
      this.eUp = void 0;
    }
    const { element, pointerId } = this;
    this.pointerId = void 0;
    if (typeof pointerId === "number" && element.hasPointerCapture(pointerId)) {
      element.releasePointerCapture(pointerId);
    }
  }
}
class NullGraphError extends Error {
  static {
    __name(this, "NullGraphError");
  }
  constructor(message = "Attempted to access LGraph reference that was null or undefined.", cause) {
    super(message, { cause });
    this.name = "NullGraphError";
  }
}
const DEFAULT_TRACKED_PROPERTIES = [
  "title",
  "flags.collapsed",
  "flags.pinned",
  "mode",
  "color",
  "bgcolor"
];
class LGraphNodeProperties {
  static {
    __name(this, "LGraphNodeProperties");
  }
  /** The node this property manager belongs to */
  node;
  /** Set of property paths that have been instrumented */
  #instrumentedPaths = /* @__PURE__ */ new Set();
  constructor(node2) {
    this.node = node2;
    this.#setupInstrumentation();
  }
  /**
   * Sets up property instrumentation for all tracked properties
   */
  #setupInstrumentation() {
    for (const path of DEFAULT_TRACKED_PROPERTIES) {
      this.#instrumentProperty(path);
    }
  }
  /**
   * Instruments a single property to track changes
   */
  #instrumentProperty(path) {
    const parts2 = path.split(".");
    if (parts2.length > 1) {
      this.#ensureNestedPath(path);
    }
    let targetObject = this.node;
    let propertyName = parts2[0];
    if (parts2.length > 1) {
      for (let i = 0; i < parts2.length - 1; i++) {
        targetObject = targetObject[parts2[i]];
      }
      propertyName = parts2.at(-1);
    }
    const hasProperty = Object.prototype.hasOwnProperty.call(
      targetObject,
      propertyName
    );
    const currentValue = targetObject[propertyName];
    if (!hasProperty) {
      let value = void 0;
      Object.defineProperty(targetObject, propertyName, {
        get: /* @__PURE__ */ __name(() => value, "get"),
        set: /* @__PURE__ */ __name((newValue2) => {
          const oldValue = value;
          value = newValue2;
          this.#emitPropertyChange(path, oldValue, newValue2);
          const shouldBeEnumerable = newValue2 !== void 0;
          const currentDescriptor = Object.getOwnPropertyDescriptor(
            targetObject,
            propertyName
          );
          if (currentDescriptor && currentDescriptor.enumerable !== shouldBeEnumerable) {
            Object.defineProperty(targetObject, propertyName, {
              ...currentDescriptor,
              enumerable: shouldBeEnumerable
            });
          }
        }, "set"),
        enumerable: false,
        configurable: true
      });
    } else {
      Object.defineProperty(
        targetObject,
        propertyName,
        this.#createInstrumentedDescriptor(path, currentValue)
      );
    }
    this.#instrumentedPaths.add(path);
  }
  /**
   * Creates a property descriptor that emits change events
   */
  #createInstrumentedDescriptor(propertyPath, initialValue) {
    let value = initialValue;
    return {
      get: /* @__PURE__ */ __name(() => value, "get"),
      set: /* @__PURE__ */ __name((newValue2) => {
        const oldValue = value;
        value = newValue2;
        this.#emitPropertyChange(propertyPath, oldValue, newValue2);
      }, "set"),
      enumerable: true,
      configurable: true
    };
  }
  /**
   * Emits a property change event if the node is connected to a graph
   */
  #emitPropertyChange(propertyPath, oldValue, newValue2) {
    this.node.graph?.trigger("node:property:changed", {
      nodeId: this.node.id,
      property: propertyPath,
      oldValue,
      newValue: newValue2
    });
  }
  /**
   * Ensures parent objects exist for nested properties
   */
  #ensureNestedPath(path) {
    const parts2 = path.split(".");
    let current = this.node;
    for (let i = 0; i < parts2.length - 1; i++) {
      const part = parts2[i];
      if (!current[part]) {
        current[part] = {};
      }
      current = current[part];
    }
  }
  /**
   * Checks if a property is being tracked
   */
  isTracked(path) {
    return this.#instrumentedPaths.has(path);
  }
  /**
   * Gets the list of tracked properties
   */
  getTrackedProperties() {
    return [...DEFAULT_TRACKED_PROPERTIES];
  }
  /**
   * Custom toJSON method for JSON.stringify
   * Returns undefined to exclude from serialization since we only use defaults
   */
  toJSON() {
    return void 0;
  }
}
function isTransparent(color2) {
  if (color2 === "transparent") return true;
  if (color2[0] === "#") {
    if (color2.length === 5) return color2[4] === "0";
    if (color2.length === 9) return color2.substring(7) === "00";
  }
  return false;
}
__name(isTransparent, "isTransparent");
function rgbToHsl({ r, g: g2, b }) {
  r /= 255;
  g2 /= 255;
  b /= 255;
  const max = Math.max(r, g2, b), min = Math.min(r, g2, b);
  let h2 = 0, s = 0;
  const l = (max + min) / 2;
  if (max !== min) {
    const d2 = max - min;
    s = l > 0.5 ? d2 / (2 - max - min) : d2 / (max + min);
    switch (max) {
      case r:
        h2 = (g2 - b) / d2 + (g2 < b ? 6 : 0);
        break;
      case g2:
        h2 = (b - r) / d2 + 2;
        break;
      case b:
        h2 = (r - g2) / d2 + 4;
        break;
    }
    h2 /= 6;
  }
  return { h: h2, s, l };
}
__name(rgbToHsl, "rgbToHsl");
function hexToRgb(hex) {
  let r = 0, g2 = 0, b = 0;
  if (hex.length == 4) {
    r = parseInt(hex[1] + hex[1], 16);
    g2 = parseInt(hex[2] + hex[2], 16);
    b = parseInt(hex[3] + hex[3], 16);
  } else if (hex.length == 7) {
    r = parseInt(hex.slice(1, 3), 16);
    g2 = parseInt(hex.slice(3, 5), 16);
    b = parseInt(hex.slice(5, 7), 16);
  }
  return { r, g: g2, b };
}
__name(hexToRgb, "hexToRgb");
function rgbToHex({ r, g: g2, b }) {
  const toHex = /* @__PURE__ */ __name((n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0"), "toHex");
  return `#${toHex(r)}${toHex(g2)}${toHex(b)}`;
}
__name(rgbToHex, "rgbToHex");
function hsbToRgb({ h: h2, s, b }) {
  const hh = (h2 % 360 + 360) % 360;
  const ss = Math.max(0, Math.min(100, s)) / 100;
  const vv = Math.max(0, Math.min(100, b)) / 100;
  const c2 = vv * ss;
  const x2 = c2 * (1 - Math.abs(hh / 60 % 2 - 1));
  const m = vv - c2;
  let rp = 0, gp = 0, bp = 0;
  if (hh < 60) {
    rp = c2;
    gp = x2;
    bp = 0;
  } else if (hh < 120) {
    rp = x2;
    gp = c2;
    bp = 0;
  } else if (hh < 180) {
    rp = 0;
    gp = c2;
    bp = x2;
  } else if (hh < 240) {
    rp = 0;
    gp = x2;
    bp = c2;
  } else if (hh < 300) {
    rp = x2;
    gp = 0;
    bp = c2;
  } else {
    rp = c2;
    gp = 0;
    bp = x2;
  }
  return {
    r: Math.floor((rp + m) * 255),
    g: Math.floor((gp + m) * 255),
    b: Math.floor((bp + m) * 255)
  };
}
__name(hsbToRgb, "hsbToRgb");
function parseToRgb(color2) {
  const format2 = identifyColorFormat(color2);
  if (!format2) return { r: 0, g: 0, b: 0 };
  const hsla = parseToHSLA(color2, format2);
  if (!isHSLA(hsla)) return { r: 0, g: 0, b: 0 };
  const h2 = hsla.h / 360;
  const s = hsla.s / 100;
  const l = hsla.l / 100;
  const c2 = (1 - Math.abs(2 * l - 1)) * s;
  const x2 = c2 * (1 - Math.abs(h2 * 6 % 2 - 1));
  const m = l - c2 / 2;
  let r = 0, g2 = 0, b = 0;
  if (h2 < 1 / 6) {
    r = c2;
    g2 = x2;
    b = 0;
  } else if (h2 < 2 / 6) {
    r = x2;
    g2 = c2;
    b = 0;
  } else if (h2 < 3 / 6) {
    r = 0;
    g2 = c2;
    b = x2;
  } else if (h2 < 4 / 6) {
    r = 0;
    g2 = x2;
    b = c2;
  } else if (h2 < 5 / 6) {
    r = x2;
    g2 = 0;
    b = c2;
  } else {
    r = c2;
    g2 = 0;
    b = x2;
  }
  return {
    r: Math.round((r + m) * 255),
    g: Math.round((g2 + m) * 255),
    b: Math.round((b + m) * 255)
  };
}
__name(parseToRgb, "parseToRgb");
const identifyColorFormat = /* @__PURE__ */ __name((color2) => {
  if (!color2) return null;
  if (color2.startsWith("#") && (color2.length === 4 || color2.length === 7))
    return "hex";
  if (/rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*/.test(color2))
    return color2.includes("rgba") ? "rgba" : "rgb";
  if (/hsla?\(\s*\d+(\.\d+)?\s*,\s*\d+(\.\d+)?%\s*,\s*\d+(\.\d+)?%/.test(color2))
    return color2.includes("hsla") ? "hsla" : "hsl";
  return null;
}, "identifyColorFormat");
const isHSLA = /* @__PURE__ */ __name((color2) => {
  if (typeof color2 !== "object" || color2 === null) return false;
  return ["h", "s", "l", "a"].every(
    (key) => typeof color2[key] === "number" && !isNaN(color2[key])
  );
}, "isHSLA");
function isColorFormat(v2) {
  return v2 === "hex" || v2 === "rgb" || v2 === "hsb";
}
__name(isColorFormat, "isColorFormat");
function isHSBObject(v2) {
  if (!v2 || typeof v2 !== "object") return false;
  const rec = v2;
  return typeof rec.h === "number" && Number.isFinite(rec.h) && typeof rec.s === "number" && Number.isFinite(rec.s) && typeof rec.b === "number" && Number.isFinite(rec.b);
}
__name(isHSBObject, "isHSBObject");
function isHSVObject(v2) {
  if (!v2 || typeof v2 !== "object") return false;
  const rec = v2;
  return typeof rec.h === "number" && Number.isFinite(rec.h) && typeof rec.s === "number" && Number.isFinite(rec.s) && typeof rec.v === "number" && Number.isFinite(rec.v);
}
__name(isHSVObject, "isHSVObject");
function toHexFromFormat(val, format2) {
  if (format2 === "hex" && typeof val === "string") {
    const raw = val.trim().toLowerCase();
    if (!raw) return "#000000";
    if (/^[0-9a-f]{3}$/.test(raw)) return `#${raw}`;
    if (/^#[0-9a-f]{3}$/.test(raw)) return raw;
    if (/^[0-9a-f]{6}$/.test(raw)) return `#${raw}`;
    if (/^#[0-9a-f]{6}$/.test(raw)) return raw;
    return "#000000";
  }
  if (format2 === "rgb" && typeof val === "string") {
    const rgb = parseToRgb(val);
    return rgbToHex(rgb).toLowerCase();
  }
  if (format2 === "hsb") {
    if (isHSBObject(val)) {
      return rgbToHex(hsbToRgb(val)).toLowerCase();
    }
    if (isHSVObject(val)) {
      const { h: h2, s, v: v2 } = val;
      return rgbToHex(hsbToRgb({ h: h2, s, b: v2 })).toLowerCase();
    }
    if (typeof val === "string") {
      const nums = val.match(/\d+(?:\.\d+)?/g)?.map(Number) || [];
      if (nums.length >= 3) {
        return rgbToHex(
          hsbToRgb({ h: nums[0], s: nums[1], b: nums[2] })
        ).toLowerCase();
      }
    }
  }
  return "#000000";
}
__name(toHexFromFormat, "toHexFromFormat");
function parseToHSLA(color2, format2) {
  let match;
  switch (format2) {
    case "hex": {
      const hsl = rgbToHsl(hexToRgb(color2));
      return {
        h: Math.round(hsl.h * 360),
        s: +(hsl.s * 100).toFixed(1),
        l: +(hsl.l * 100).toFixed(1),
        a: 1
      };
    }
    case "rgb":
    case "rgba": {
      match = color2.match(/\d+(\.\d+)?/g);
      if (!match || match.length < 3) return null;
      const [r, g2, b] = match.map(Number);
      const hsl = rgbToHsl({ r, g: g2, b });
      const a = format2 === "rgba" && match[3] ? parseFloat(match[3]) : 1;
      return {
        h: Math.round(hsl.h * 360),
        s: +(hsl.s * 100).toFixed(1),
        l: +(hsl.l * 100).toFixed(1),
        a
      };
    }
    case "hsl":
    case "hsla": {
      match = color2.match(/\d+(\.\d+)?/g);
      if (!match || match.length < 3) return null;
      const [h2, s, l] = match.map(Number);
      const a = format2 === "hsla" && match[3] ? parseFloat(match[3]) : 1;
      return { h: h2, s, l, a };
    }
    default:
      return null;
  }
}
__name(parseToHSLA, "parseToHSLA");
const applyColorAdjustments = /* @__PURE__ */ __name((color2, options) => {
  if (!Object.keys(options).length) return color2;
  const format2 = identifyColorFormat(color2);
  if (!format2) {
    console.warn(`Unsupported color format in color palette: ${color2}`);
    return color2;
  }
  const hsla = parseToHSLA(color2, format2);
  if (!isHSLA(hsla)) {
    console.warn(`Invalid color values in color palette: ${color2}`);
    return color2;
  }
  if (options.lightness) {
    hsla.l = Math.max(0, Math.min(100, hsla.l + options.lightness * 100));
  }
  if (options.opacity) {
    hsla.a = Math.max(0, Math.min(1, options.opacity));
  }
  return `hsla(${hsla.h}, ${hsla.s}%, ${hsla.l}%, ${hsla.a})`;
}, "applyColorAdjustments");
const adjustColor = memoize(
  applyColorAdjustments,
  (color2, options) => `${color2}-${JSON.stringify(options)}`
);
function toClass(cls, ...args) {
  return args[0] instanceof cls ? args[0] : new cls(...args);
}
__name(toClass, "toClass");
function isColorable(obj) {
  return typeof obj === "object" && obj !== null && "setColorOption" in obj && "getColorOption" in obj;
}
__name(isColorable, "isColorable");
function commonType(...types) {
  if (!isStrings(types)) return void 0;
  const withoutWildcards = without(types, "*");
  if (withoutWildcards.length === 0) return "*";
  const typeLists = withoutWildcards.map((type) => type.split(","));
  const combinedTypes = intersection(...typeLists);
  if (combinedTypes.length === 0) return void 0;
  return combinedTypes.join(",");
}
__name(commonType, "commonType");
function intersection(...sets) {
  const itemCounts = {};
  for (const set of sets)
    for (const item of new Set(set))
      itemCounts[item] = (itemCounts[item] ?? 0) + 1;
  return Object.entries(itemCounts).filter(([, count]) => count === sets.length).map(([key]) => key);
}
__name(intersection, "intersection");
function isStrings(types) {
  return types.every((t2) => typeof t2 === "string");
}
__name(isStrings, "isStrings");
class LGraphIcon {
  static {
    __name(this, "LGraphIcon");
  }
  unicode;
  fontFamily;
  image;
  color;
  bgColor;
  fontSize;
  size;
  circlePadding;
  xOffset;
  yOffset;
  constructor({
    unicode,
    fontFamily = "PrimeIcons",
    image,
    color: color2 = "#e6c200",
    bgColor,
    fontSize = 16,
    size,
    circlePadding = 2,
    xOffset = 0,
    yOffset = 0
  }) {
    this.unicode = unicode;
    this.fontFamily = fontFamily;
    this.image = image;
    this.color = color2;
    this.bgColor = bgColor;
    this.fontSize = fontSize;
    this.size = size ?? fontSize;
    this.circlePadding = circlePadding;
    this.xOffset = xOffset;
    this.yOffset = yOffset;
  }
  draw(ctx, x2, y) {
    x2 += this.xOffset;
    y += this.yOffset;
    if (this.image) {
      const iconSize = this.size;
      const iconRadius = iconSize / 2 + this.circlePadding;
      if (this.bgColor) {
        const { fillStyle } = ctx;
        ctx.beginPath();
        ctx.arc(x2 + iconRadius, y, iconRadius, 0, 2 * Math.PI);
        ctx.fillStyle = this.bgColor;
        ctx.fill();
        ctx.fillStyle = fillStyle;
      }
      const imageX = x2 + this.circlePadding;
      const imageY = y - iconSize / 2;
      ctx.drawImage(this.image, imageX, imageY, iconSize, iconSize);
    } else if (this.unicode) {
      const { font, textBaseline, textAlign, fillStyle } = ctx;
      ctx.font = `${this.fontSize}px '${this.fontFamily}'`;
      ctx.textBaseline = "middle";
      ctx.textAlign = "center";
      const iconRadius = this.fontSize / 2 + this.circlePadding;
      if (this.bgColor) {
        ctx.beginPath();
        ctx.arc(x2 + iconRadius, y, iconRadius, 0, 2 * Math.PI);
        ctx.fillStyle = this.bgColor;
        ctx.fill();
      }
      ctx.fillStyle = this.color;
      ctx.fillText(this.unicode, x2 + iconRadius, y);
      ctx.font = font;
      ctx.textBaseline = textBaseline;
      ctx.textAlign = textAlign;
      ctx.fillStyle = fillStyle;
    }
  }
}
var BadgePosition = /* @__PURE__ */ ((BadgePosition2) => {
  BadgePosition2["TopLeft"] = "top-left";
  BadgePosition2["TopRight"] = "top-right";
  return BadgePosition2;
})(BadgePosition || {});
class LGraphBadge {
  static {
    __name(this, "LGraphBadge");
  }
  text;
  fgColor;
  bgColor;
  fontSize;
  padding;
  height;
  cornerRadius;
  icon;
  xOffset;
  yOffset;
  constructor({
    text,
    fgColor = "white",
    bgColor = "#0F1F0F",
    fontSize = 12,
    padding = 6,
    height = 20,
    cornerRadius = 5,
    iconOptions,
    xOffset = 0,
    yOffset = 0
  }) {
    this.text = text;
    this.fgColor = fgColor;
    this.bgColor = bgColor;
    this.fontSize = fontSize;
    this.padding = padding;
    this.height = height;
    this.cornerRadius = cornerRadius;
    if (iconOptions) {
      this.icon = new LGraphIcon(iconOptions);
    }
    this.xOffset = xOffset;
    this.yOffset = yOffset;
  }
  get visible() {
    return (this.text?.length ?? 0) > 0 || !!this.icon;
  }
  getWidth(ctx) {
    if (!this.visible) return 0;
    const { font } = ctx;
    let iconWidth = 0;
    if (this.icon) {
      if (this.icon.image) {
        iconWidth = this.icon.size + this.padding;
      } else if (this.icon.unicode) {
        ctx.font = `${this.icon.fontSize}px '${this.icon.fontFamily}'`;
        iconWidth = ctx.measureText(this.icon.unicode).width + this.padding;
      }
    }
    ctx.font = `${this.fontSize}px sans-serif`;
    const textWidth = this.text ? ctx.measureText(this.text).width : 0;
    ctx.font = font;
    return iconWidth + textWidth + this.padding * 2;
  }
  draw(ctx, x2, y) {
    if (!this.visible) return;
    x2 += this.xOffset;
    y += this.yOffset;
    const { font, fillStyle, textBaseline, textAlign } = ctx;
    ctx.font = `${this.fontSize}px sans-serif`;
    const badgeWidth = this.getWidth(ctx);
    const badgeX = 0;
    ctx.fillStyle = this.bgColor;
    ctx.beginPath();
    if (ctx.roundRect) {
      ctx.roundRect(x2 + badgeX, y, badgeWidth, this.height, this.cornerRadius);
    } else {
      ctx.rect(x2 + badgeX, y, badgeWidth, this.height);
    }
    ctx.fill();
    let drawX = x2 + badgeX + this.padding;
    const centerY = y + this.height / 2;
    if (this.icon) {
      this.icon.draw(ctx, drawX, centerY);
      const iconWidth = this.icon.image ? this.icon.size : this.icon.fontSize;
      drawX += iconWidth + this.padding / 2 + 4;
    }
    if (this.text) {
      ctx.fillStyle = this.fgColor;
      ctx.textBaseline = "middle";
      ctx.textAlign = "left";
      ctx.fillText(this.text, drawX, centerY + 1);
    }
    ctx.font = font;
    ctx.fillStyle = fillStyle;
    ctx.textBaseline = textBaseline;
    ctx.textAlign = textAlign;
  }
}
class Rectangle extends Float64Array {
  static {
    __name(this, "Rectangle");
  }
  #pos;
  #size;
  constructor(x2 = 0, y = 0, width2 = 0, height = 0) {
    super(4);
    this[0] = x2;
    this[1] = y;
    this[2] = width2;
    this[3] = height;
  }
  static from([x2, y, width2, height]) {
    return new Rectangle(x2, y, width2, height);
  }
  /**
   * Creates a new rectangle positioned at the given centre, with the given width/height.
   * @param centre The centre of the rectangle, as an `[x, y]` point
   * @param width The width of the rectangle
   * @param height The height of the rectangle.  Default: {@link width}
   * @returns A new rectangle whose centre is at {@link x}
   */
  static fromCentre([x2, y], width2, height = width2) {
    const left = x2 - width2 * 0.5;
    const top = y - height * 0.5;
    return new Rectangle(left, top, width2, height);
  }
  static ensureRect(rect) {
    return rect instanceof Rectangle ? rect : new Rectangle(rect[0], rect[1], rect[2], rect[3]);
  }
  subarray(begin = 0, end) {
    const byteOffset = begin << 3;
    const length = end === void 0 ? end : end - begin;
    return new Float64Array(this.buffer, byteOffset, length);
  }
  /**
   * A reference to the position of the top-left corner of this rectangle.
   *
   * Updating the values of the returned object will update this rectangle.
   */
  get pos() {
    this.#pos ??= this.subarray(0, 2);
    return this.#pos;
  }
  set pos(value) {
    this[0] = value[0];
    this[1] = value[1];
  }
  /**
   * A reference to the size of this rectangle.
   *
   * Updating the values of the returned object will update this rectangle.
   */
  get size() {
    this.#size ??= this.subarray(2, 4);
    return this.#size;
  }
  set size(value) {
    this[2] = value[0];
    this[3] = value[1];
  }
  // #region Property accessors
  /** The x co-ordinate of the top-left corner of this rectangle. */
  get x() {
    return this[0];
  }
  set x(value) {
    this[0] = value;
  }
  /** The y co-ordinate of the top-left corner of this rectangle. */
  get y() {
    return this[1];
  }
  set y(value) {
    this[1] = value;
  }
  /** The width of this rectangle. */
  get width() {
    return this[2];
  }
  set width(value) {
    this[2] = value;
  }
  /** The height of this rectangle. */
  get height() {
    return this[3];
  }
  set height(value) {
    this[3] = value;
  }
  /** The x co-ordinate of the left edge of this rectangle. */
  get left() {
    return this[0];
  }
  set left(value) {
    this[0] = value;
  }
  /** The y co-ordinate of the top edge of this rectangle. */
  get top() {
    return this[1];
  }
  set top(value) {
    this[1] = value;
  }
  /** The x co-ordinate of the right edge of this rectangle. */
  get right() {
    return this[0] + this[2];
  }
  set right(value) {
    this[0] = value - this[2];
  }
  /** The y co-ordinate of the bottom edge of this rectangle. */
  get bottom() {
    return this[1] + this[3];
  }
  set bottom(value) {
    this[1] = value - this[3];
  }
  /** The x co-ordinate of the centre of this rectangle. */
  get centreX() {
    return this[0] + this[2] * 0.5;
  }
  /** The y co-ordinate of the centre of this rectangle. */
  get centreY() {
    return this[1] + this[3] * 0.5;
  }
  // #endregion Property accessors
  /**
   * Updates the rectangle to the values of {@link rect}.
   * @param rect The rectangle to update to.
   */
  updateTo(rect) {
    this[0] = rect[0];
    this[1] = rect[1];
    this[2] = rect[2];
    this[3] = rect[3];
  }
  /**
   * Checks if the point [{@link x}, {@link y}] is inside this rectangle.
   * @param x The x-coordinate to check
   * @param y The y-coordinate to check
   * @returns `true` if the point is inside this rectangle, otherwise `false`.
   */
  containsXy(x2, y) {
    const [left, top, width2, height] = this;
    return x2 >= left && x2 < left + width2 && y >= top && y < top + height;
  }
  /**
   * Checks if {@link point} is inside this rectangle.
   * @param point The point to check
   * @returns `true` if {@link point} is inside this rectangle, otherwise `false`.
   */
  containsPoint([x2, y]) {
    const [left, top, width2, height] = this;
    return x2 >= left && x2 < left + width2 && y >= top && y < top + height;
  }
  /**
   * Checks if {@link other} is a smaller rectangle inside this rectangle.
   * One **must** be larger than the other; identical rectangles are not considered to contain each other.
   * @param other The rectangle to check
   * @returns `true` if {@link other} is inside this rectangle, otherwise `false`.
   */
  containsRect(other) {
    const { right, bottom } = this;
    const otherRight = other[0] + other[2];
    const otherBottom = other[1] + other[3];
    const identical = this.x === other[0] && this.y === other[1] && right === otherRight && bottom === otherBottom;
    return !identical && this.x <= other[0] && this.y <= other[1] && right >= otherRight && bottom >= otherBottom;
  }
  /**
   * Checks if {@link rect} overlaps with this rectangle.
   * @param rect The rectangle to check
   * @returns `true` if {@link rect} overlaps with this rectangle, otherwise `false`.
   */
  overlaps(rect) {
    return this.x < rect[0] + rect[2] && this.y < rect[1] + rect[3] && this.x + this.width > rect[0] && this.y + this.height > rect[1];
  }
  /**
   * Finds the corner (if any) of this rectangle that contains the point [{@link x}, {@link y}].
   * @param x The x-coordinate to check
   * @param y The y-coordinate to check
   * @param cornerSize Each corner is treated as an inset square with this width and height.
   * @returns The compass direction of the corner that contains the point, or `undefined` if the point is not in any corner.
   */
  findContainingCorner(x2, y, cornerSize) {
    if (this.isInTopLeftCorner(x2, y, cornerSize)) return "NW";
    if (this.isInTopRightCorner(x2, y, cornerSize)) return "NE";
    if (this.isInBottomLeftCorner(x2, y, cornerSize)) return "SW";
    if (this.isInBottomRightCorner(x2, y, cornerSize)) return "SE";
  }
  /** @returns `true` if the point [{@link x}, {@link y}] is in the top-left corner of this rectangle, otherwise `false`. */
  isInTopLeftCorner(x2, y, cornerSize) {
    return isInRectangle(x2, y, this.x, this.y, cornerSize, cornerSize);
  }
  /** @returns `true` if the point [{@link x}, {@link y}] is in the top-right corner of this rectangle, otherwise `false`. */
  isInTopRightCorner(x2, y, cornerSize) {
    return isInRectangle(
      x2,
      y,
      this.right - cornerSize,
      this.y,
      cornerSize,
      cornerSize
    );
  }
  /** @returns `true` if the point [{@link x}, {@link y}] is in the bottom-left corner of this rectangle, otherwise `false`. */
  isInBottomLeftCorner(x2, y, cornerSize) {
    return isInRectangle(
      x2,
      y,
      this.x,
      this.bottom - cornerSize,
      cornerSize,
      cornerSize
    );
  }
  /** @returns `true` if the point [{@link x}, {@link y}] is in the bottom-right corner of this rectangle, otherwise `false`. */
  isInBottomRightCorner(x2, y, cornerSize) {
    return isInRectangle(
      x2,
      y,
      this.right - cornerSize,
      this.bottom - cornerSize,
      cornerSize,
      cornerSize
    );
  }
  /** @returns `true` if the point [{@link x}, {@link y}] is in the top edge of this rectangle, otherwise `false`. */
  isInTopEdge(x2, y, edgeSize) {
    return isInRectangle(x2, y, this.x, this.y, this.width, edgeSize);
  }
  /** @returns `true` if the point [{@link x}, {@link y}] is in the bottom edge of this rectangle, otherwise `false`. */
  isInBottomEdge(x2, y, edgeSize) {
    return isInRectangle(
      x2,
      y,
      this.x,
      this.bottom - edgeSize,
      this.width,
      edgeSize
    );
  }
  /** @returns `true` if the point [{@link x}, {@link y}] is in the left edge of this rectangle, otherwise `false`. */
  isInLeftEdge(x2, y, edgeSize) {
    return isInRectangle(x2, y, this.x, this.y, edgeSize, this.height);
  }
  /** @returns `true` if the point [{@link x}, {@link y}] is in the right edge of this rectangle, otherwise `false`. */
  isInRightEdge(x2, y, edgeSize) {
    return isInRectangle(
      x2,
      y,
      this.right - edgeSize,
      this.y,
      edgeSize,
      this.height
    );
  }
  /** @returns The centre point of this rectangle, as a new {@link Point}. */
  getCentre() {
    return [this.centreX, this.centreY];
  }
  /** @returns The area of this rectangle. */
  getArea() {
    return this.width * this.height;
  }
  /** @returns The perimeter of this rectangle. */
  getPerimeter() {
    return 2 * (this.width + this.height);
  }
  /** @returns The top-left corner of this rectangle, as a new {@link Point}. */
  getTopLeft() {
    return [this[0], this[1]];
  }
  /** @returns The bottom-right corner of this rectangle, as a new {@link Point}. */
  getBottomRight() {
    return [this.right, this.bottom];
  }
  /** @returns The width and height of this rectangle, as a new {@link Size}. */
  getSize() {
    return [this[2], this[3]];
  }
  /** @returns The offset from the top-left of this rectangle to the point [{@link x}, {@link y}], as a new {@link Point}. */
  getOffsetTo([x2, y]) {
    return [x2 - this[0], y - this[1]];
  }
  /** @returns The offset from the point [{@link x}, {@link y}] to the top-left of this rectangle, as a new {@link Point}. */
  getOffsetFrom([x2, y]) {
    return [this[0] - x2, this[1] - y];
  }
  /** Resizes the rectangle without moving it, setting its top-left corner to [{@link x}, {@link y}]. */
  resizeTopLeft(x1, y1) {
    this[2] += this[0] - x1;
    this[3] += this[1] - y1;
    this[0] = x1;
    this[1] = y1;
  }
  /** Resizes the rectangle without moving it, setting its bottom-left corner to [{@link x}, {@link y}]. */
  resizeBottomLeft(x1, y2) {
    this[2] += this[0] - x1;
    this[3] = y2 - this[1];
    this[0] = x1;
  }
  /** Resizes the rectangle without moving it, setting its top-right corner to [{@link x}, {@link y}]. */
  resizeTopRight(x2, y1) {
    this[2] = x2 - this[0];
    this[3] += this[1] - y1;
    this[1] = y1;
  }
  /** Resizes the rectangle without moving it, setting its bottom-right corner to [{@link x}, {@link y}]. */
  resizeBottomRight(x2, y2) {
    this[2] = x2 - this[0];
    this[3] = y2 - this[1];
  }
  /** Sets the width without moving the right edge (changes position) */
  setWidthRightAnchored(width2) {
    const currentWidth = this[2];
    this[2] = width2;
    this[0] += currentWidth - width2;
  }
  /** Sets the height without moving the bottom edge (changes position) */
  setHeightBottomAnchored(height) {
    const currentHeight = this[3];
    this[3] = height;
    this[1] += currentHeight - height;
  }
  clone() {
    return new Rectangle(this[0], this[1], this[2], this[3]);
  }
  /** Alias of {@link export}. */
  toArray() {
    return this.export();
  }
  /** @returns A new, untyped array (serializable) containing the values of this rectangle. */
  export() {
    return [this[0], this[1], this[2], this[3]];
  }
  /**
   * Draws a debug outline of this rectangle.
   * @internal Convenience debug/development interface; not for production use.
   */
  _drawDebug(ctx, colour = "red") {
    const { strokeStyle, lineWidth } = ctx;
    try {
      ctx.strokeStyle = colour;
      ctx.lineWidth = 0.5;
      ctx.beginPath();
      ctx.strokeRect(this[0], this[1], this[2], this[3]);
    } finally {
      ctx.strokeStyle = strokeStyle;
      ctx.lineWidth = lineWidth;
    }
  }
}
class LGraphButton extends LGraphBadge {
  static {
    __name(this, "LGraphButton");
  }
  name;
  _last_area = new Rectangle();
  constructor(options) {
    super(options);
    this.name = options.name;
  }
  getWidth(ctx) {
    if (!this.visible) return 0;
    const { font } = ctx;
    ctx.font = `${this.fontSize}px 'PrimeIcons'`;
    const textWidth = this.text ? ctx.measureText(this.text).width : 0;
    ctx.font = font;
    return textWidth;
  }
  /**
   * @internal
   *
   * Draws the button and updates its last rendered area for hit detection.
   * @param ctx The canvas rendering context.
   * @param x The x-coordinate to draw the button at.
   * @param y The y-coordinate to draw the button at.
   */
  draw(ctx, x2, y) {
    if (!this.visible) {
      return;
    }
    const width2 = this.getWidth(ctx);
    this._last_area[0] = x2 + this.xOffset;
    this._last_area[1] = y + this.yOffset;
    this._last_area[2] = width2;
    this._last_area[3] = this.height;
    const adjustedX = x2 + this.xOffset;
    const adjustedY = y + this.yOffset;
    const { font, fillStyle, textBaseline, textAlign } = ctx;
    const titleTextColor = ctx.fillStyle || "white";
    ctx.font = `${this.fontSize}px 'PrimeIcons'`;
    ctx.fillStyle = titleTextColor;
    ctx.textBaseline = "middle";
    ctx.textAlign = "center";
    const centerX = adjustedX + width2 / 2;
    const centerY = adjustedY + this.height / 2;
    if (this.text) {
      ctx.fillText(this.text, centerX, centerY);
    }
    ctx.font = font;
    ctx.fillStyle = fillStyle;
    ctx.textBaseline = textBaseline;
    ctx.textAlign = textAlign;
  }
  /**
   * Checks if a point is inside the button's last rendered area.
   * @param x The x-coordinate of the point.
   * @param y The y-coordinate of the point.
   * @returns `true` if the point is inside the button, otherwise `false`.
   */
  isPointInside(x2, y) {
    return this._last_area.containsPoint([x2, y]);
  }
}
const layoutMutations$1 = useLayoutMutations();
class LLink {
  static {
    __name(this, "LLink");
  }
  static _drawDebug = false;
  /** Link ID */
  id;
  parentId;
  type;
  /** Output node ID */
  origin_id;
  /** Output slot index */
  origin_slot;
  /** Input node ID */
  target_id;
  /** Input slot index */
  target_slot;
  data;
  _data;
  /** Centre point of the link, calculated during render only - can be inaccurate */
  _pos;
  /** @todo Clean up - never implemented in comfy. */
  _last_time;
  /** The last canvas 2D path that was used to render this link */
  path;
  /** @inheritdoc */
  _centreAngle;
  /** @inheritdoc */
  _dragging;
  #color;
  /** Custom colour for this link only */
  get color() {
    return this.#color;
  }
  set color(value) {
    this.#color = value === "" ? null : value;
  }
  get isFloatingOutput() {
    return this.origin_id === -1 && this.origin_slot === -1;
  }
  get isFloatingInput() {
    return this.target_id === -1 && this.target_slot === -1;
  }
  get isFloating() {
    return this.isFloatingOutput || this.isFloatingInput;
  }
  /** `true` if this link is connected to a subgraph input node (the actual origin is in a different graph). */
  get originIsIoNode() {
    return this.origin_id === SUBGRAPH_INPUT_ID;
  }
  /** `true` if this link is connected to a subgraph output node (the actual target is in a different graph). */
  get targetIsIoNode() {
    return this.target_id === SUBGRAPH_OUTPUT_ID;
  }
  constructor(id2, type, origin_id, origin_slot, target_id, target_slot, parentId) {
    this.id = id2;
    this.type = type;
    this.origin_id = origin_id;
    this.origin_slot = origin_slot;
    this.target_id = target_id;
    this.target_slot = target_slot;
    this.parentId = parentId;
    this._data = null;
    this._pos = [0, 0];
  }
  /** @deprecated Use {@link LLink.create} */
  static createFromArray(data) {
    return new LLink(data[0], data[5], data[1], data[2], data[3], data[4]);
  }
  /**
   * LLink static factory: creates a new LLink from the provided data.
   * @param data Serialised LLink data to create the link from
   * @returns A new LLink
   */
  static create(data) {
    return new LLink(
      data.id,
      data.type,
      data.origin_id,
      data.origin_slot,
      data.target_id,
      data.target_slot,
      data.parentId
    );
  }
  /**
   * Gets all reroutes from the output slot to this segment.  If this segment is a reroute, it will not be included.
   * @returns An ordered array of all reroutes from the node output to
   * this reroute or the reroute before it.  Otherwise, an empty array.
   */
  static getReroutes(network, linkSegment) {
    if (linkSegment.parentId === void 0) return [];
    return network.reroutes.get(linkSegment.parentId)?.getReroutes() ?? [];
  }
  static getFirstReroute(network, linkSegment) {
    return LLink.getReroutes(network, linkSegment).at(0);
  }
  /**
   * Finds the reroute in the chain after the provided reroute ID.
   * @param network The network this link belongs to
   * @param linkSegment The starting point of the search (input side).
   * Typically the LLink object itself, but can be any link segment.
   * @param rerouteId The matching reroute will have this set as its {@link parentId}.
   * @returns The reroute that was found, `undefined` if no reroute was found, or `null` if an infinite loop was detected.
   */
  static findNextReroute(network, linkSegment, rerouteId) {
    if (linkSegment.parentId === void 0) return;
    return network.reroutes.get(linkSegment.parentId)?.findNextReroute(rerouteId);
  }
  /**
   * Gets the origin node of a link.
   * @param network The network to search
   * @param linkId The ID of the link to get the origin node of
   * @returns The origin node of the link, or `undefined` if the link is not found or the origin node is not found
   */
  static getOriginNode(network, linkId) {
    const id2 = network.links.get(linkId)?.origin_id;
    return network.getNodeById(id2) ?? void 0;
  }
  /**
   * Gets the target node of a link.
   * @param network The network to search
   * @param linkId The ID of the link to get the target node of
   * @returns The target node of the link, or `undefined` if the link is not found or the target node is not found
   */
  static getTargetNode(network, linkId) {
    const id2 = network.links.get(linkId)?.target_id;
    return network.getNodeById(id2) ?? void 0;
  }
  /**
   * Resolves a link ID to the link, node, and slot objects.
   * @param linkId The {@link id} of the link to resolve
   * @param network The link network to search
   * @returns An object containing the input and output nodes, as well as the input and output slots.
   * @remarks This method is heavier than others; it will always resolve all objects.
   * Whilst the performance difference should in most cases be negligible,
   * it is recommended to use simpler methods where appropriate.
   */
  static resolve(linkId, network) {
    return network.getLink(linkId)?.resolve(network);
  }
  /**
   * Resolves a list of link IDs to the link, node, and slot objects.
   * Discards invalid link IDs.
   * @param linkIds An iterable of link {@link id}s to resolve
   * @param network The link network to search
   * @returns An array of resolved connections.  If a link is not found, it is not included in the array.
   * @see {@link LLink.resolve}
   */
  static resolveMany(linkIds, network) {
    const resolved = [];
    for (const id2 of linkIds) {
      const r = network.getLink(id2)?.resolve(network);
      if (r) resolved.push(r);
    }
    return resolved;
  }
  /**
   * Resolves the primitive ID values stored in the link to the referenced objects.
   * @param network The link network to search
   * @returns An object containing the input and output nodes, as well as the input and output slots.
   * @remarks This method is heavier than others; it will always resolve all objects.
   * Whilst the performance difference should in most cases be negligible,
   * it is recommended to use simpler methods where appropriate.
   */
  resolve(network) {
    const inputNode = this.target_id === -1 ? void 0 : network.getNodeById(this.target_id) ?? void 0;
    const input = inputNode?.inputs[this.target_slot];
    const subgraphInput = this.originIsIoNode ? network.inputNode?.slots[this.origin_slot] : void 0;
    if (subgraphInput) {
      return { inputNode, input, subgraphInput, link: this };
    }
    const outputNode = this.origin_id === -1 ? void 0 : network.getNodeById(this.origin_id) ?? void 0;
    const output = outputNode?.outputs[this.origin_slot];
    const subgraphOutput = this.targetIsIoNode ? network.outputNode?.slots[this.target_slot] : void 0;
    if (subgraphOutput) {
      return {
        outputNode,
        output,
        subgraphInput: void 0,
        subgraphOutput,
        link: this
      };
    }
    return {
      inputNode,
      outputNode,
      input,
      output,
      subgraphInput,
      subgraphOutput,
      link: this
    };
  }
  configure(o) {
    if (Array.isArray(o)) {
      this.id = o[0];
      this.origin_id = o[1];
      this.origin_slot = o[2];
      this.target_id = o[3];
      this.target_slot = o[4];
      this.type = o[5];
    } else {
      this.id = o.id;
      this.type = o.type;
      this.origin_id = o.origin_id;
      this.origin_slot = o.origin_slot;
      this.target_id = o.target_id;
      this.target_slot = o.target_slot;
      this.parentId = o.parentId;
    }
  }
  /**
   * Checks if the specified node id and output index are this link's origin (output side).
   * @param nodeId ID of the node to check
   * @param outputIndex The array index of the node output
   * @returns `true` if the origin matches, otherwise `false`.
   */
  hasOrigin(nodeId, outputIndex) {
    return this.origin_id === nodeId && this.origin_slot === outputIndex;
  }
  /**
   * Checks if the specified node id and input index are this link's target (input side).
   * @param nodeId ID of the node to check
   * @param inputIndex The array index of the node input
   * @returns `true` if the target matches, otherwise `false`.
   */
  hasTarget(nodeId, inputIndex) {
    return this.target_id === nodeId && this.target_slot === inputIndex;
  }
  /**
   * Creates a floating link from this link.
   * @param slotType The side of the link that is still connected
   * @param parentId The parent reroute ID of the link
   * @returns A new LLink that is floating
   */
  toFloating(slotType, parentId) {
    const exported = this.asSerialisable();
    exported.id = -1;
    exported.parentId = parentId;
    if (slotType === "input") {
      exported.origin_id = -1;
      exported.origin_slot = -1;
    } else {
      exported.target_id = -1;
      exported.target_slot = -1;
    }
    return LLink.create(exported);
  }
  /**
   * Disconnects a link and removes it from the graph, cleaning up any reroutes that are no longer used
   * @param network The container (LGraph) where reroutes should be updated
   * @param keepReroutes If `undefined`, reroutes will be automatically removed if no links remain.
   * If `input` or `output`, reroutes will not be automatically removed, and retain a connection to the input or output, respectively.
   */
  disconnect(network, keepReroutes) {
    const reroutes = LLink.getReroutes(network, this);
    const lastReroute = reroutes.at(-1);
    const outputFloating = keepReroutes === "output" && lastReroute?.linkIds.size === 1 && lastReroute.floatingLinkIds.size === 0;
    if (outputFloating || keepReroutes === "input" && lastReroute) {
      const newLink = LLink.create(this);
      newLink.id = -1;
      if (keepReroutes === "input") {
        newLink.origin_id = -1;
        newLink.origin_slot = -1;
        lastReroute.floating = { slotType: "input" };
      } else {
        newLink.target_id = -1;
        newLink.target_slot = -1;
        lastReroute.floating = { slotType: "output" };
      }
      network.addFloatingLink(newLink);
    }
    for (const reroute of reroutes) {
      reroute.linkIds.delete(this.id);
      if (!keepReroutes && !reroute.totalLinks) {
        network.reroutes.delete(reroute.id);
        layoutMutations$1.setSource(LayoutSource.Canvas);
        layoutMutations$1.deleteReroute(reroute.id);
      }
    }
    network.links.delete(this.id);
    layoutMutations$1.setSource(LayoutSource.Canvas);
    layoutMutations$1.deleteLink(this.id);
  }
  /**
   * @deprecated Prefer {@link LLink.asSerialisable} (returns an object, not an array)
   * @returns An array representing this LLink
   */
  serialize() {
    return [
      this.id,
      this.origin_id,
      this.origin_slot,
      this.target_id,
      this.target_slot,
      this.type
    ];
  }
  asSerialisable() {
    const copy = {
      id: this.id,
      origin_id: this.origin_id,
      origin_slot: this.origin_slot,
      target_id: this.target_id,
      target_slot: this.target_slot,
      type: this.type
    };
    if (this.parentId !== void 0) copy.parentId = this.parentId;
    return copy;
  }
}
function getNodeInputOnPos(node2, x2, y) {
  const { inputs } = node2;
  if (!inputs) return;
  for (const [index2, input] of inputs.entries()) {
    const pos = node2.getInputPos(index2);
    const nameLength = input.label?.length ?? input.localized_name?.length ?? input.name?.length;
    const width2 = 20 + (nameLength || 3) * 7;
    if (isInRectangle(x2, y, pos[0] - 10, pos[1] - 10, width2, 20)) {
      return { index: index2, input, pos };
    }
  }
}
__name(getNodeInputOnPos, "getNodeInputOnPos");
function getNodeOutputOnPos(node2, x2, y) {
  const { outputs } = node2;
  if (!outputs) return;
  for (const [index2, output] of outputs.entries()) {
    const pos = node2.getOutputPos(index2);
    if (isInRectangle(x2, y, pos[0] - 10, pos[1] - 10, 40, 20)) {
      return { index: index2, output, pos };
    }
  }
}
__name(getNodeOutputOnPos, "getNodeOutputOnPos");
function isOverNodeInput(node2, canvasx, canvasy, slot_pos) {
  const result = getNodeInputOnPos(node2, canvasx, canvasy);
  if (!result) return -1;
  if (slot_pos) {
    slot_pos[0] = result.pos[0];
    slot_pos[1] = result.pos[1];
  }
  return result.index;
}
__name(isOverNodeInput, "isOverNodeInput");
function isOverNodeOutput(node2, canvasx, canvasy, slot_pos) {
  const result = getNodeOutputOnPos(node2, canvasx, canvasy);
  if (!result) return -1;
  if (slot_pos) {
    slot_pos[0] = result.pos[0];
    slot_pos[1] = result.pos[1];
  }
  return result.index;
}
__name(isOverNodeOutput, "isOverNodeOutput");
const ELLIPSIS = "…";
const TWO_DOT_LEADER = "‥";
const ONE_DOT_LEADER = "․";
var SlotType = /* @__PURE__ */ ((SlotType2) => {
  SlotType2["Array"] = "array";
  SlotType2[SlotType2["Event"] = -1] = "Event";
  return SlotType2;
})(SlotType || {});
var SlotShape = ((SlotShape2) => {
  SlotShape2[SlotShape2["Box"] = RenderShape.BOX] = "Box";
  SlotShape2[SlotShape2["Arrow"] = RenderShape.ARROW] = "Arrow";
  SlotShape2[SlotShape2["Grid"] = RenderShape.GRID] = "Grid";
  SlotShape2[SlotShape2["Circle"] = RenderShape.CIRCLE] = "Circle";
  SlotShape2[SlotShape2["HollowCircle"] = RenderShape.HollowCircle] = "HollowCircle";
  return SlotShape2;
})(SlotShape || {});
var SlotDirection = /* @__PURE__ */ ((SlotDirection2) => {
  return SlotDirection2;
})(SlotDirection || {});
var LabelPosition = /* @__PURE__ */ ((LabelPosition2) => {
  LabelPosition2["Left"] = "left";
  LabelPosition2["Right"] = "right";
  return LabelPosition2;
})(LabelPosition || {});
function strokeShape(ctx, area, {
  shape: shape2 = RenderShape.BOX,
  round_radius,
  title_height,
  title_mode = TitleMode.NORMAL_TITLE,
  color: color2,
  padding = 6,
  collapsed = false,
  lineWidth: thickness = 1
} = {}) {
  round_radius ??= LiteGraph.ROUND_RADIUS;
  color2 ??= LiteGraph.NODE_BOX_OUTLINE_COLOR;
  if (title_mode === TitleMode.TRANSPARENT_TITLE) {
    const height2 = title_height ?? LiteGraph.NODE_TITLE_HEIGHT;
    area[1] -= height2;
    area[3] += height2;
  }
  const { lineWidth, strokeStyle } = ctx;
  ctx.lineWidth = thickness;
  ctx.globalAlpha = 0.8;
  ctx.strokeStyle = color2;
  ctx.beginPath();
  const [x2, y, width2, height] = area;
  switch (shape2) {
    case RenderShape.BOX: {
      ctx.rect(
        x2 - padding,
        y - padding,
        width2 + 2 * padding,
        height + 2 * padding
      );
      break;
    }
    case RenderShape.ROUND:
    case RenderShape.CARD: {
      const radius = round_radius + padding;
      const isCollapsed = shape2 === RenderShape.CARD && collapsed;
      const cornerRadii = isCollapsed || shape2 === RenderShape.ROUND ? [radius] : [radius, 2, radius, 2];
      ctx.roundRect(
        x2 - padding,
        y - padding,
        width2 + 2 * padding,
        height + 2 * padding,
        cornerRadii
      );
      break;
    }
    case RenderShape.CIRCLE: {
      const centerX = x2 + width2 / 2;
      const centerY = y + height / 2;
      const radius = Math.max(width2, height) / 2 + padding;
      ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
      break;
    }
  }
  ctx.stroke();
  ctx.lineWidth = lineWidth;
  ctx.strokeStyle = strokeStyle;
  ctx.globalAlpha = 1;
}
__name(strokeShape, "strokeShape");
function truncateTextToWidth(ctx, text, maxWidth) {
  if (!(maxWidth > 0)) return "";
  const fullWidth = ctx.measureText(text).width;
  if (fullWidth <= maxWidth) return text;
  const ellipsisWidth = ctx.measureText(ELLIPSIS).width * 0.75;
  if (ellipsisWidth > maxWidth) {
    const twoDotsWidth = ctx.measureText(TWO_DOT_LEADER).width * 0.75;
    if (twoDotsWidth < maxWidth) return TWO_DOT_LEADER;
    const oneDotWidth = ctx.measureText(ONE_DOT_LEADER).width * 0.75;
    return oneDotWidth < maxWidth ? ONE_DOT_LEADER : "";
  }
  let min = 0;
  let max = text.length;
  let bestLen = 0;
  while (min <= max) {
    const mid = Math.floor((min + max) * 0.5);
    if (mid === 0) {
      min = mid + 1;
      continue;
    }
    const sub = text.substring(0, mid);
    const currentWidth = ctx.measureText(sub).width + ellipsisWidth;
    if (currentWidth <= maxWidth) {
      bestLen = mid;
      min = mid + 1;
    } else {
      max = mid - 1;
    }
  }
  return bestLen === 0 ? ELLIPSIS : text.substring(0, bestLen) + ELLIPSIS;
}
__name(truncateTextToWidth, "truncateTextToWidth");
function drawTextInArea({
  ctx,
  text,
  area,
  align = "left"
}) {
  const { left, right, bottom, width: width2, centreX } = area;
  const fullWidth = ctx.measureText(text).width;
  if (fullWidth <= width2) {
    ctx.textAlign = align;
    const x2 = align === "left" ? left : align === "right" ? right : centreX;
    ctx.fillText(text, x2, bottom);
    return;
  }
  const truncated = truncateTextToWidth(ctx, text, width2);
  if (truncated.length === 0) return;
  ctx.textAlign = "left";
  ctx.fillText(truncated.slice(0, -1), left, bottom);
  ctx.rect(left, bottom, width2, 1);
  ctx.textAlign = "right";
  const ellipsis = truncated.at(-1);
  ctx.fillText(ellipsis, right, bottom, ctx.measureText(ellipsis).width * 0.75);
}
__name(drawTextInArea, "drawTextInArea");
class SlotBase {
  static {
    __name(this, "SlotBase");
  }
  name;
  localized_name;
  label;
  type;
  dir;
  removable;
  shape;
  color_off;
  color_on;
  locked;
  nameLocked;
  widget;
  _floatingLinks;
  hasErrors;
  boundingRect;
  constructor(name2, type, boundingRect) {
    this.name = name2;
    this.type = type;
    this.boundingRect = boundingRect ?? new Rectangle();
  }
  renderingColor(colorContext) {
    return this.isConnected ? this.color_on || colorContext.getConnectedColor(this.type) : this.color_off || colorContext.getDisconnectedColor(this.type);
  }
}
const ROTATION_OFFSET = -Math.PI / 2;
class NodeSlot extends SlotBase {
  static {
    __name(this, "NodeSlot");
  }
  pos;
  /** The offset from the parent node to the centre point of this slot. */
  get #centreOffset() {
    const nodePos = this.node.pos;
    const { boundingRect } = this;
    const diameter = boundingRect[3];
    return getCentre([
      boundingRect[0] - nodePos[0],
      boundingRect[1] - nodePos[1],
      diameter,
      diameter
    ]);
  }
  #node;
  get node() {
    return this.#node;
  }
  get highlightColor() {
    return LiteGraph.NODE_TEXT_HIGHLIGHT_COLOR ?? LiteGraph.NODE_SELECTED_TITLE_COLOR ?? LiteGraph.NODE_TEXT_COLOR;
  }
  constructor(slot, node2) {
    const maybeSubgraphSlot = slot;
    const { boundingRect, name: name2, type, _listenerController, ...rest } = maybeSubgraphSlot;
    const rectangle = boundingRect ? Rectangle.ensureRect(boundingRect) : new Rectangle();
    super(name2, type, rectangle);
    Object.assign(this, rest);
    this.#node = node2;
  }
  /**
   * The label to display in the UI.
   */
  get renderingLabel() {
    return this.label || this.localized_name || this.name || "";
  }
  draw(ctx, {
    colorContext,
    labelPosition = LabelPosition.Right,
    lowQuality = false,
    highlight = false,
    doStroke = false
  }) {
    const originalFillStyle = ctx.fillStyle;
    const originalStrokeStyle = ctx.strokeStyle;
    const originalLineWidth = ctx.lineWidth;
    const labelColor = highlight ? this.highlightColor : LiteGraph.NODE_TEXT_COLOR;
    const pos = this.#centreOffset;
    const slot_type = this.type;
    const slot_shape = slot_type === SlotType.Array ? SlotShape.Grid : this.shape;
    ctx.save();
    ctx.beginPath();
    let doFill = true;
    ctx.fillStyle = this.renderingColor(colorContext);
    ctx.lineWidth = 1;
    if (slot_type === SlotType.Event || slot_shape === SlotShape.Box) {
      ctx.rect(pos[0] - 6 + 0.5, pos[1] - 5 + 0.5, 14, 10);
    } else if (slot_shape === SlotShape.Arrow) {
      ctx.moveTo(pos[0] + 8, pos[1] + 0.5);
      ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);
      ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);
      ctx.closePath();
    } else if (slot_shape === SlotShape.Grid) {
      const gridSize = 3;
      const cellSize = 2;
      const spacing = 3;
      for (let x2 = 0; x2 < gridSize; x2++) {
        for (let y = 0; y < gridSize; y++) {
          ctx.rect(
            pos[0] - 4 + x2 * spacing,
            pos[1] - 4 + y * spacing,
            cellSize,
            cellSize
          );
        }
      }
      doStroke = false;
    } else {
      if (lowQuality) {
        ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8);
      } else {
        if (slot_shape === SlotShape.HollowCircle) {
          const path = new Path2D();
          path.arc(pos[0], pos[1], 10, 0, Math.PI * 2);
          path.arc(pos[0], pos[1], highlight ? 2.5 : 1.5, 0, Math.PI * 2);
          ctx.clip(path, "evenodd");
        }
        const radius = highlight ? 5 : 4;
        const typesSet = new Set(
          `${this.type}`.split(",").map(
            this.isConnected ? (type) => colorContext.getConnectedColor(type) : (type) => colorContext.getDisconnectedColor(type)
          )
        );
        const types = [...typesSet].slice(0, 3);
        if (types.length > 1) {
          doFill = false;
          const arcLen = Math.PI * 2 / types.length;
          types.forEach((type, idx) => {
            ctx.moveTo(pos[0], pos[1]);
            ctx.fillStyle = type;
            ctx.arc(
              pos[0],
              pos[1],
              radius,
              arcLen * idx + ROTATION_OFFSET,
              Math.PI * 2 + ROTATION_OFFSET
            );
            ctx.fill();
            ctx.beginPath();
          });
          ctx.save();
          ctx.strokeStyle = "black";
          ctx.lineWidth = 0.5;
          types.forEach((_, idx) => {
            ctx.moveTo(pos[0], pos[1]);
            const xOffset = Math.cos(arcLen * idx + ROTATION_OFFSET) * radius;
            const yOffset = Math.sin(arcLen * idx + ROTATION_OFFSET) * radius;
            ctx.lineTo(pos[0] + xOffset, pos[1] + yOffset);
          });
          ctx.stroke();
          ctx.restore();
          ctx.beginPath();
        }
        ctx.arc(pos[0], pos[1], radius, 0, Math.PI * 2);
      }
    }
    if (doFill) ctx.fill();
    if (!lowQuality && doStroke) ctx.stroke();
    ctx.restore();
    const hideLabel = lowQuality || this.isWidgetInputSlot;
    if (!hideLabel) {
      const text = this.renderingLabel;
      if (text) {
        ctx.fillStyle = labelColor;
        if (labelPosition === LabelPosition.Right) {
          if (this.dir == LinkDirection.UP) {
            ctx.fillText(text, pos[0], pos[1] - 10);
          } else {
            ctx.fillText(text, pos[0] + 10, pos[1] + 5);
          }
        } else {
          if (this.dir == LinkDirection.DOWN) {
            ctx.fillText(text, pos[0], pos[1] - 8);
          } else {
            ctx.fillText(text, pos[0] - 10, pos[1] + 5);
          }
        }
      }
    }
    if (this.hasErrors) {
      ctx.lineWidth = 2;
      ctx.strokeStyle = "red";
      ctx.beginPath();
      ctx.arc(pos[0], pos[1], 12, 0, Math.PI * 2);
      ctx.stroke();
    }
    ctx.fillStyle = originalFillStyle;
    ctx.strokeStyle = originalStrokeStyle;
    ctx.lineWidth = originalLineWidth;
  }
  drawCollapsed(ctx) {
    const [x2, y] = this.collapsedPos;
    const { fillStyle } = ctx;
    ctx.fillStyle = "#686";
    ctx.beginPath();
    if (this.type === SlotType.Event || this.shape === RenderShape.BOX) {
      ctx.rect(x2 - 7 + 0.5, y - 4, 14, 8);
    } else if (this.shape === RenderShape.ARROW) {
      const isInput = this instanceof NodeInputSlot;
      if (isInput) {
        ctx.moveTo(x2 + 8, y);
        ctx.lineTo(x2 - 4, y - 4);
        ctx.lineTo(x2 - 4, y + 4);
      } else {
        ctx.moveTo(x2 + 6, y);
        ctx.lineTo(x2 - 6, y - 4);
        ctx.lineTo(x2 - 6, y + 4);
      }
      ctx.closePath();
    } else {
      ctx.arc(x2, y, 4, 0, Math.PI * 2);
    }
    ctx.fill();
    ctx.fillStyle = fillStyle;
  }
}
const layoutMutations = useLayoutMutations();
class Reroute {
  static {
    __name(this, "Reroute");
  }
  /**
   * Initialises a new link reroute object.
   * @param id Unique identifier for this reroute
   * @param network The network of links this reroute belongs to.  Internally converted to a WeakRef.
   * @param pos Position in graph coordinates
   * @param linkIds Link IDs ({@link LLink.id}) of all links that use this reroute
   */
  constructor(id2, network, pos, parentId, linkIds, floatingLinkIds) {
    this.id = id2;
    this.network = new WeakRef(network);
    this.parentId = parentId;
    if (pos) this.pos = pos;
    this.linkIds = new Set(linkIds);
    this.floatingLinkIds = new Set(floatingLinkIds);
  }
  static radius = 10;
  /** Maximum distance from reroutes to their bezier curve control points. */
  static maxSplineOffset = 80;
  static drawIdBadge = false;
  static slotRadius = 5;
  /** Distance from reroute centre to slot centre. */
  static get slotOffset() {
    const gap = Reroute.slotRadius * 0.33;
    return Reroute.radius + gap + Reroute.slotRadius;
  }
  /** The network this reroute belongs to.  Contains all valid links and reroutes. */
  network;
  parentIdInternal;
  get parentId() {
    return this.parentIdInternal;
  }
  /** Ignores attempts to create an infinite loop. @inheritdoc */
  set parentId(value) {
    if (value === this.id) return;
    if (this.getReroutes() === null) return;
    this.parentIdInternal = value;
  }
  get parent() {
    return this.network.deref()?.getReroute(this.parentIdInternal);
  }
  /** This property is only defined on the last reroute of a floating reroute chain (closest to input end). */
  floating;
  posInternal = [0, 0];
  /** @inheritdoc */
  get pos() {
    return this.posInternal;
  }
  set pos(value) {
    if (!(value?.length >= 2))
      throw new TypeError(
        "Reroute.pos is an x,y point, and expects an indexable with at least two values."
      );
    this.posInternal[0] = value[0];
    this.posInternal[1] = value[1];
  }
  /** @inheritdoc */
  get boundingRect() {
    const { radius } = Reroute;
    const [x2, y] = this.posInternal;
    return [x2 - radius, y - radius, 2 * radius, 2 * radius];
  }
  /**
   * Slightly over-sized rectangle, guaranteed to contain the entire surface area for hover detection.
   * Eliminates most hover positions using an extremely cheap check.
   */
  get hoverArea() {
    const xOffset = 2 * Reroute.slotOffset;
    const yOffset = 2 * Math.max(Reroute.radius, Reroute.slotRadius);
    const [x2, y] = this.posInternal;
    return [x2 - xOffset, y - yOffset, 2 * xOffset, 2 * yOffset];
  }
  /** The total number of links & floating links using this reroute */
  get totalLinks() {
    return this.linkIds.size + this.floatingLinkIds.size;
  }
  /** @inheritdoc */
  selected;
  /** The ID ({@link LLink.id}) of every link using this reroute */
  linkIds;
  /** The ID ({@link LLink.id}) of every floating link using this reroute */
  floatingLinkIds;
  /** Cached cos */
  cos = 0;
  sin = 0;
  /** Bezier curve control point for the "target" (input) side of the link */
  controlPoint = [0, 0];
  /** @inheritdoc */
  path;
  /** @inheritdoc */
  _centreAngle;
  /** @inheritdoc */
  _pos = [0, 0];
  /** @inheritdoc */
  _dragging;
  /** Colour of the first link that rendered this reroute */
  _colour;
  /** Colour of the first link that rendered this reroute */
  get colour() {
    return this._colour ?? "#18184d";
  }
  /**
   * Used to ensure reroute angles are only executed once per frame.
   * @todo Calculate on change instead.
   */
  lastRenderTime = -Infinity;
  inputSlot = new RerouteSlot(this, true);
  outputSlot = new RerouteSlot(this, false);
  get isSlotHovered() {
    return this.isInputHovered || this.isOutputHovered;
  }
  get isInputHovered() {
    return this.inputSlot.hovering;
  }
  get isOutputHovered() {
    return this.outputSlot.hovering;
  }
  get firstLink() {
    const linkId = this.linkIds.values().next().value;
    return linkId === void 0 ? void 0 : this.network.deref()?.links.get(linkId);
  }
  get firstFloatingLink() {
    const linkId = this.floatingLinkIds.values().next().value;
    return linkId === void 0 ? void 0 : this.network.deref()?.floatingLinks.get(linkId);
  }
  /** @inheritdoc */
  get origin_id() {
    return this.firstLink?.origin_id;
  }
  /** @inheritdoc */
  get origin_slot() {
    return this.firstLink?.origin_slot;
  }
  /**
   * Applies a new parentId to the reroute, and optinoally a new position and linkId.
   * Primarily used for deserialisation.
   * @param parentId The ID of the reroute prior to this reroute, or
   * `undefined` if it is the first reroute connected to a nodes output
   * @param pos The position of this reroute
   * @param linkIds All link IDs that pass through this reroute
   */
  update(parentId, pos, linkIds, floating) {
    this.parentId = parentId;
    if (pos) this.pos = pos;
    if (linkIds) this.linkIds = new Set(linkIds);
    this.floating = floating;
  }
  /**
   * Validates the linkIds this reroute has.  Removes broken links.
   * @param links Collection of valid links
   * @returns true if any links remain after validation
   */
  validateLinks(links, floatingLinks) {
    const { linkIds, floatingLinkIds } = this;
    for (const linkId of linkIds) {
      if (!links.has(linkId)) linkIds.delete(linkId);
    }
    for (const linkId of floatingLinkIds) {
      if (!floatingLinks.has(linkId)) floatingLinkIds.delete(linkId);
    }
    return linkIds.size > 0 || floatingLinkIds.size > 0;
  }
  /**
   * Retrieves an ordered array of all reroutes from the node output.
   * @param visited Internal.  A set of reroutes that this function
   * has already visited whilst recursing up the chain.
   * @returns An ordered array of all reroutes from the node output to this reroute, inclusive.
   * `null` if an infinite loop is detected.
   * `undefined` if the reroute chain or {@link LinkNetwork} are invalid.
   */
  getReroutes(visited = /* @__PURE__ */ new Set()) {
    if (this.parentIdInternal === void 0) return [this];
    if (visited.has(this)) return null;
    visited.add(this);
    const parent = this.network.deref()?.reroutes.get(this.parentIdInternal);
    if (!parent) {
      this.parentIdInternal = void 0;
      return [this];
    }
    const reroutes = parent.getReroutes(visited);
    reroutes?.push(this);
    return reroutes;
  }
  /**
   * Internal.  Called by {@link LLink.findNextReroute}.  Not intended for use by itself.
   * @param withParentId The rerouteId to look for
   * @param visited A set of reroutes that have already been visited
   * @returns The reroute that was found, `undefined` if no reroute was found, or `null` if an infinite loop was detected.
   */
  findNextReroute(withParentId, visited = /* @__PURE__ */ new Set()) {
    if (this.parentIdInternal === withParentId) return this;
    if (visited.has(this)) return null;
    visited.add(this);
    if (this.parentIdInternal === void 0) return;
    return this.network.deref()?.reroutes.get(this.parentIdInternal)?.findNextReroute(withParentId, visited);
  }
  /**
   * Finds the output node and output slot of the first link passing through this reroute.
   * @returns The output node and output slot of the first link passing through this reroute, or `undefined` if no link is found.
   */
  findSourceOutput() {
    const link = this.firstLink ?? this.firstFloatingLink;
    if (!link) return;
    const node2 = this.network.deref()?.getNodeById(link.origin_id);
    if (!node2) return;
    return {
      node: node2,
      output: node2.outputs[link.origin_slot]
    };
  }
  /**
   * Finds the inputs and nodes of (floating) links passing through this reroute.
   * @returns An array of objects containing the node and input slot of each link passing through this reroute.
   */
  findTargetInputs() {
    const network = this.network.deref();
    if (!network) return;
    const results = [];
    addAllResults(network, this.linkIds, network.links);
    addAllResults(network, this.floatingLinkIds, network.floatingLinks);
    return results;
    function addAllResults(network2, linkIds, links) {
      for (const linkId of linkIds) {
        const link = links.get(linkId);
        if (!link) continue;
        const node2 = network2.getNodeById(link.target_id);
        const input = node2?.inputs[link.target_slot];
        if (!input) continue;
        results.push({ node: node2, input, link });
      }
    }
    __name(addAllResults, "addAllResults");
  }
  /**
   * Retrieves all floating links passing through this reroute.
   * @param from Filters the links by the currently connected link side.
   * @returns An array of floating links
   */
  getFloatingLinks(from) {
    const floatingLinks = this.network.deref()?.floatingLinks;
    if (!floatingLinks) return;
    const idProp = from === "input" ? "origin_id" : "target_id";
    const out = [];
    for (const linkId of this.floatingLinkIds) {
      const link = floatingLinks.get(linkId);
      if (link?.[idProp] === -1) out.push(link);
    }
    return out;
  }
  /**
   * Changes the origin node/output of all floating links that pass through this reroute.
   * @param node The new origin node
   * @param output The new origin output slot
   * @param index The slot index of {@link output}
   */
  setFloatingLinkOrigin(node2, output, index2) {
    const network = this.network.deref();
    const floatingOutLinks = this.getFloatingLinks("output");
    if (!floatingOutLinks)
      throw new Error("[setFloatingLinkOrigin]: Invalid network.");
    if (!floatingOutLinks.length) return;
    output._floatingLinks ??= /* @__PURE__ */ new Set();
    for (const link of floatingOutLinks) {
      output._floatingLinks.add(link);
      network?.getNodeById(link.origin_id)?.outputs[link.origin_slot]?._floatingLinks?.delete(link);
      link.origin_id = node2.id;
      link.origin_slot = index2;
    }
  }
  /** @inheritdoc */
  move(deltaX, deltaY) {
    const previousPos = { x: this.posInternal[0], y: this.posInternal[1] };
    this.posInternal[0] += deltaX;
    this.posInternal[1] += deltaY;
    layoutMutations.setSource(LayoutSource.Canvas);
    layoutMutations.moveReroute(
      this.id,
      { x: this.posInternal[0], y: this.posInternal[1] },
      previousPos
    );
  }
  /** @inheritdoc */
  snapToGrid(snapTo) {
    if (!snapTo) return false;
    const { pos } = this;
    pos[0] = snapTo * Math.round(pos[0] / snapTo);
    pos[1] = snapTo * Math.round(pos[1] / snapTo);
    return true;
  }
  removeAllFloatingLinks() {
    for (const linkId of this.floatingLinkIds) {
      this.removeFloatingLink(linkId);
    }
  }
  removeFloatingLink(linkId) {
    const network = this.network.deref();
    if (!network) return;
    const floatingLink = network.floatingLinks.get(linkId);
    if (!floatingLink) {
      console.warn(
        `[Reroute.removeFloatingLink] Floating link not found: ${linkId}, ignoring and discarding ID.`
      );
      this.floatingLinkIds.delete(linkId);
      return;
    }
    network.removeFloatingLink(floatingLink);
  }
  /**
   * Removes a link or floating link from this reroute, by matching link object instance equality.
   * @param link The link to remove.
   * @remarks Does not remove the link from the network.
   */
  removeLink(link) {
    const network = this.network.deref();
    if (!network) return;
    const floatingLink = network.floatingLinks.get(link.id);
    if (link === floatingLink) {
      this.floatingLinkIds.delete(link.id);
    } else {
      this.linkIds.delete(link.id);
    }
  }
  remove() {
    const network = this.network.deref();
    if (!network) return;
    network.removeReroute(this.id);
  }
  calculateAngle(lastRenderTime, network, linkStart) {
    if (!(lastRenderTime > this.lastRenderTime)) return;
    this.lastRenderTime = lastRenderTime;
    const { id: id2, pos: thisPos } = this;
    const angles = [];
    let sum = 0;
    calculateAngles(this.linkIds, network.links);
    calculateAngles(this.floatingLinkIds, network.floatingLinks);
    if (!angles.length) {
      this.cos = 0;
      this.sin = 0;
      this.controlPoint[0] = 0;
      this.controlPoint[1] = 0;
      return;
    }
    sum /= angles.length;
    const originToReroute = Math.atan2(
      this.posInternal[1] - linkStart[1],
      this.posInternal[0] - linkStart[0]
    );
    let diff2 = (originToReroute - sum) * 0.5;
    if (Math.abs(diff2) > Math.PI * 0.5) diff2 += Math.PI;
    const dist = Math.min(
      Reroute.maxSplineOffset,
      distance(linkStart, this.posInternal) * 0.25
    );
    const originDiff = originToReroute - diff2;
    const cos = Math.cos(originDiff);
    const sin = Math.sin(originDiff);
    this.cos = cos;
    this.sin = sin;
    this.controlPoint[0] = dist * -cos;
    this.controlPoint[1] = dist * -sin;
    function calculateAngles(linkIds, links) {
      for (const linkId of linkIds) {
        const link = links.get(linkId);
        const pos = getNextPos(network, link, id2);
        if (!pos) continue;
        const angle = getDirection(thisPos, pos);
        angles.push(angle);
        sum += angle;
      }
    }
    __name(calculateAngles, "calculateAngles");
  }
  /**
   * Renders the reroute on the canvas.
   * @param ctx Canvas context to draw on
   * @param backgroundPattern The canvas background pattern; used to make floating reroutes appear washed out.
   * @remarks Leaves {@link ctx}.fillStyle, strokeStyle, and lineWidth dirty (perf.).
   */
  draw(ctx, backgroundPattern) {
    const { globalAlpha } = ctx;
    const { pos } = this;
    ctx.beginPath();
    ctx.arc(pos[0], pos[1], Reroute.radius, 0, 2 * Math.PI);
    if (this.linkIds.size === 0) {
      ctx.fillStyle = backgroundPattern ?? "#797979";
      ctx.fill();
      ctx.globalAlpha = globalAlpha * 0.33;
    }
    ctx.fillStyle = this.colour;
    ctx.lineWidth = Reroute.radius * 0.1;
    ctx.strokeStyle = "rgb(0,0,0,0.5)";
    ctx.fill();
    ctx.stroke();
    ctx.fillStyle = "#ffffff55";
    ctx.strokeStyle = "rgb(0,0,0,0.3)";
    ctx.beginPath();
    ctx.arc(pos[0], pos[1], Reroute.radius * 0.8, 0, 2 * Math.PI);
    ctx.fill();
    ctx.stroke();
    if (this.selected) {
      ctx.strokeStyle = "#fff";
      ctx.beginPath();
      ctx.arc(pos[0], pos[1], Reroute.radius * 1.2, 0, 2 * Math.PI);
      ctx.stroke();
    }
    if (Reroute.drawIdBadge) {
      const idBadge = new LGraphBadge({ text: this.id.toString() });
      const x2 = pos[0] - idBadge.getWidth(ctx) * 0.5;
      const y = pos[1] - idBadge.height - Reroute.radius - 2;
      idBadge.draw(ctx, x2, y);
    }
    ctx.globalAlpha = globalAlpha;
  }
  /**
   * Draws the input and output slots on the canvas, if the slots are visible.
   * @param ctx The canvas context to draw on.
   */
  drawSlots(ctx) {
    this.inputSlot.draw(ctx);
    this.outputSlot.draw(ctx);
  }
  drawHighlight(ctx, colour) {
    const { pos } = this;
    const { strokeStyle, lineWidth } = ctx;
    ctx.strokeStyle = colour;
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.arc(pos[0], pos[1], Reroute.radius * 1.5, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.strokeStyle = strokeStyle;
    ctx.lineWidth = lineWidth;
  }
  /**
   * Updates visibility of the input and output slots, based on the position of the pointer.
   * @param pos The position of the pointer.
   * @returns `true` if any changes require a redraw.
   */
  updateVisibility(pos) {
    const input = this.inputSlot;
    const output = this.outputSlot;
    input.dirty = false;
    output.dirty = false;
    const { firstFloatingLink } = this;
    const hasLink = !!this.firstLink;
    const showInput = hasLink || firstFloatingLink?.isFloatingOutput;
    const showOutput = hasLink || firstFloatingLink?.isFloatingInput;
    const showEither = showInput || showOutput;
    if (showEither && isPointInRect(pos, this.hoverArea)) {
      const outlineOnly = this.contains(pos);
      if (showInput) input.update(pos, outlineOnly);
      if (showOutput) output.update(pos, outlineOnly);
    } else {
      this.hideSlots();
    }
    return input.dirty || output.dirty;
  }
  /** Prevents rendering of the input and output slots. */
  hideSlots() {
    this.inputSlot.hide();
    this.outputSlot.hide();
  }
  /**
   * Precisely determines if {@link pos} is inside this reroute.
   * @param pos The position to check (canvas space)
   * @returns `true` if {@link pos} is within the reroute's radius.
   */
  containsPoint(pos) {
    return isPointInRect(pos, this.hoverArea) && this.contains(pos);
  }
  contains(pos) {
    return distance(this.pos, pos) <= Reroute.radius;
  }
  /** @inheritdoc */
  asSerialisable() {
    const { id: id2, parentId, pos, linkIds } = this;
    return {
      id: id2,
      parentId,
      pos: [pos[0], pos[1]],
      linkIds: [...linkIds],
      floating: this.floating ? { slotType: this.floating.slotType } : void 0
    };
  }
}
class RerouteSlot {
  static {
    __name(this, "RerouteSlot");
  }
  /** The reroute that the slot belongs to. */
  reroute;
  offsetMultiplier;
  /** Centre point of this slot. */
  get pos() {
    const [x2, y] = this.reroute.pos;
    return [x2 + Reroute.slotOffset * this.offsetMultiplier, y];
  }
  /** Whether any changes require a redraw. */
  dirty = false;
  hoveringInternal = false;
  /** Whether the pointer is hovering over the slot itself. */
  get hovering() {
    return this.hoveringInternal;
  }
  set hovering(value) {
    if (!Object.is(this.hoveringInternal, value)) {
      this.hoveringInternal = value;
      this.dirty = true;
    }
  }
  showOutlineInternal = false;
  /** Whether the slot outline / faint background is visible. */
  get showOutline() {
    return this.showOutlineInternal;
  }
  set showOutline(value) {
    if (!Object.is(this.showOutlineInternal, value)) {
      this.showOutlineInternal = value;
      this.dirty = true;
    }
  }
  constructor(reroute, isInput) {
    this.reroute = reroute;
    this.offsetMultiplier = isInput ? -1 : 1;
  }
  /**
   * Updates the slot's visibility based on the position of the pointer.
   * @param pos The position of the pointer.
   * @param outlineOnly If `true`, slot will display with the faded outline only ({@link showOutline}).
   */
  update(pos, outlineOnly) {
    if (outlineOnly) {
      this.hovering = false;
      this.showOutline = true;
    } else {
      const dist = distance(this.pos, pos);
      this.hovering = dist <= 2 * Reroute.slotRadius;
      this.showOutline = dist <= 5 * Reroute.slotRadius;
    }
  }
  /** Hides the slot. */
  hide() {
    this.hovering = false;
    this.showOutline = false;
  }
  /**
   * Draws the slot on the canvas.
   * @param ctx The canvas context to draw on.
   */
  draw(ctx) {
    const { fillStyle, strokeStyle, lineWidth } = ctx;
    const {
      showOutline,
      hovering,
      pos: [x2, y]
    } = this;
    if (!showOutline) return;
    try {
      ctx.fillStyle = hovering ? this.reroute.colour : "rgba(127,127,127,0.3)";
      ctx.strokeStyle = "rgb(0,0,0,0.5)";
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.arc(x2, y, Reroute.slotRadius, 0, 2 * Math.PI);
      ctx.fill();
      ctx.stroke();
    } finally {
      ctx.fillStyle = fillStyle;
      ctx.strokeStyle = strokeStyle;
      ctx.lineWidth = lineWidth;
    }
  }
}
function getNextPos(network, link, id2) {
  if (!link) return;
  const linkPos = LLink.findNextReroute(network, link, id2)?.pos;
  if (linkPos) return linkPos;
  if (link.target_id === -1 || link.target_slot === -1) return;
  return network.getNodeById(link.target_id)?.getInputPos(link.target_slot);
}
__name(getNextPos, "getNextPos");
function getDirection(fromPos, toPos) {
  return Math.atan2(toPos[1] - fromPos[1], toPos[0] - fromPos[0]);
}
__name(getDirection, "getDirection");
function parseSlotTypes(type) {
  return type == "" || type == "0" ? ["*"] : String(type).toLowerCase().split(",");
}
__name(parseSlotTypes, "parseSlotTypes");
function nextUniqueName(name2, existingNames = []) {
  let i = 1;
  const baseName = name2;
  while (existingNames.includes(name2)) {
    name2 = `${baseName}_${i++}`;
  }
  return name2;
}
__name(nextUniqueName, "nextUniqueName");
function getAllNestedItems(items) {
  const allItems = /* @__PURE__ */ new Set();
  if (items) {
    for (const item of items) addRecursively(item, allItems);
  }
  return allItems;
  function addRecursively(item, flatSet) {
    if (flatSet.has(item) || item.pinned) return;
    flatSet.add(item);
    if (item.children) {
      for (const child of item.children) addRecursively(child, flatSet);
    }
  }
  __name(addRecursively, "addRecursively");
}
__name(getAllNestedItems, "getAllNestedItems");
function findFirstNode(items) {
  for (const item of items) {
    if (item instanceof LGraphNode) return item;
  }
}
__name(findFirstNode, "findFirstNode");
function findFreeSlotOfType(slots, type, hasNoLinks) {
  if (!slots?.length) return;
  let occupiedSlot;
  let wildSlot;
  let occupiedWildSlot;
  const validTypes = parseSlotTypes(type);
  for (const [index2, slot] of slots.entries()) {
    const slotTypes = parseSlotTypes(slot.type);
    for (const validType of validTypes) {
      for (const slotType of slotTypes) {
        if (slotType === validType) {
          if (hasNoLinks(slot)) {
            return { index: index2, slot };
          }
          occupiedSlot ??= { index: index2, slot };
        } else if (!wildSlot && (validType === "*" || slotType === "*")) {
          if (hasNoLinks(slot)) {
            wildSlot = { index: index2, slot };
          } else {
            occupiedWildSlot ??= { index: index2, slot };
          }
        }
      }
    }
  }
  return wildSlot ?? occupiedSlot ?? occupiedWildSlot;
}
__name(findFreeSlotOfType, "findFreeSlotOfType");
class CustomEventTarget extends EventTarget {
  static {
    __name(this, "CustomEventTarget");
  }
  dispatch(type, detail) {
    const event = new CustomEvent(type, { detail, cancelable: true });
    return super.dispatchEvent(event);
  }
  addEventListener(type, listener, options) {
    super.addEventListener(type, listener, options);
  }
  removeEventListener(type, listener, options) {
    super.removeEventListener(
      type,
      listener,
      options
    );
  }
  /** @deprecated Use {@link dispatch}. */
  dispatchEvent(event) {
    return super.dispatchEvent(event);
  }
}
class ConstrainedSize {
  static {
    __name(this, "ConstrainedSize");
  }
  #width = 0;
  #height = 0;
  #desiredWidth = 0;
  #desiredHeight = 0;
  minWidth = 0;
  minHeight = 0;
  maxWidth = Infinity;
  maxHeight = Infinity;
  get width() {
    return this.#width;
  }
  get height() {
    return this.#height;
  }
  get desiredWidth() {
    return this.#desiredWidth;
  }
  set desiredWidth(value) {
    this.#desiredWidth = value;
    this.#width = clamp(value, this.minWidth, this.maxWidth);
  }
  get desiredHeight() {
    return this.#desiredHeight;
  }
  set desiredHeight(value) {
    this.#desiredHeight = value;
    this.#height = clamp(value, this.minHeight, this.maxHeight);
  }
  constructor(width2, height) {
    this.desiredWidth = width2;
    this.desiredHeight = height;
  }
  static fromSize(size) {
    return new ConstrainedSize(size[0], size[1]);
  }
  static fromRect(rect) {
    return new ConstrainedSize(rect[2], rect[3]);
  }
  setSize(size) {
    this.desiredWidth = size[0];
    this.desiredHeight = size[1];
  }
  setValues(width2, height) {
    this.desiredWidth = width2;
    this.desiredHeight = height;
  }
  toSize() {
    return [this.#width, this.#height];
  }
}
class SubgraphSlot extends SlotBase {
  static {
    __name(this, "SubgraphSlot");
  }
  static get defaultHeight() {
    return LiteGraph.NODE_SLOT_HEIGHT;
  }
  #pos = [0, 0];
  measurement = new ConstrainedSize(
    SubgraphSlot.defaultHeight,
    SubgraphSlot.defaultHeight
  );
  id;
  parent;
  type;
  linkIds = [];
  boundingRect = new Rectangle(
    0,
    0,
    0,
    SubgraphSlot.defaultHeight
  );
  get pos() {
    return this.#pos;
  }
  set pos(value) {
    if (!value || value.length < 2) return;
    this.#pos[0] = value[0];
    this.#pos[1] = value[1];
  }
  /** Whether this slot is connected to another slot. */
  get isConnected() {
    return this.linkIds.length > 0;
  }
  /** The display name of this slot. */
  get displayName() {
    return this.label ?? this.localized_name ?? this.name;
  }
  constructor(slot, parent) {
    super(slot.name, slot.type);
    Object.assign(this, slot);
    this.id = slot.id ?? createUuidv4();
    this.type = slot.type;
    this.parent = parent;
  }
  isPointerOver = false;
  containsPoint(point) {
    return this.boundingRect.containsPoint(point);
  }
  onPointerMove(e2) {
    this.isPointerOver = this.boundingRect.containsXy(e2.canvasX, e2.canvasY);
  }
  getLinks() {
    const links = [];
    const { subgraph } = this.parent;
    for (const id2 of this.linkIds) {
      const link = subgraph.getLink(id2);
      if (link) links.push(link);
    }
    return links;
  }
  decrementSlots(inputsOrOutputs) {
    const { links } = this.parent.subgraph;
    const linkProperty = inputsOrOutputs === "inputs" ? "origin_slot" : "target_slot";
    for (const linkId of this.linkIds) {
      const link = links.get(linkId);
      if (link) link[linkProperty]--;
      else console.warn("decrementSlots: link ID not found", linkId);
    }
  }
  measure() {
    const width2 = LGraphCanvas._measureText?.(this.displayName) ?? 0;
    const { defaultHeight } = SubgraphSlot;
    this.measurement.setValues(width2 + defaultHeight, defaultHeight);
    return this.measurement.toSize();
  }
  /**
   * Disconnects all links connected to this slot.
   */
  disconnect() {
    const { subgraph } = this.parent;
    for (const linkId of this.linkIds) {
      subgraph.removeLink(linkId);
    }
    this.linkIds.length = 0;
  }
  /** @remarks Leaves the context dirty. */
  draw({
    ctx,
    colorContext,
    lowQuality,
    fromSlot,
    editorAlpha = 1
  }) {
    const shape2 = this.shape;
    const {
      isPointerOver,
      pos: [x2, y]
    } = this;
    const isValidTarget = fromSlot ? this.isValidTarget(fromSlot) : true;
    const isValid = !fromSlot || isValidTarget;
    const highlight = isValid && isPointerOver;
    const previousAlpha = ctx.globalAlpha;
    ctx.globalAlpha = isValid ? editorAlpha : 0.4 * editorAlpha;
    ctx.beginPath();
    const color2 = this.renderingColor(colorContext);
    if (lowQuality) {
      ctx.fillStyle = color2;
      ctx.rect(x2 - 4, y - 4, 8, 8);
      ctx.fill();
    } else if (shape2 === SlotShape.HollowCircle) {
      ctx.lineWidth = 3;
      ctx.strokeStyle = color2;
      const radius = highlight ? 4 : 3;
      ctx.arc(x2, y, radius, 0, Math.PI * 2);
      ctx.stroke();
    } else {
      ctx.fillStyle = color2;
      const radius = highlight ? 5 : 4;
      ctx.arc(x2, y, radius, 0, Math.PI * 2);
      ctx.fill();
    }
    if (this.displayName) {
      const [labelX, labelY] = this.labelPos;
      ctx.fillStyle = highlight ? "white" : LiteGraph.NODE_TEXT_COLOR || "#AAA";
      ctx.fillText(this.displayName, labelX, labelY);
    }
    ctx.globalAlpha = previousAlpha;
  }
  asSerialisable() {
    const {
      id: id2,
      name: name2,
      type,
      linkIds,
      localized_name,
      label,
      dir,
      shape: shape2,
      color_off,
      color_on,
      pos
    } = this;
    return {
      id: id2,
      name: name2,
      type,
      linkIds,
      localized_name,
      label,
      dir,
      shape: shape2,
      color_off,
      color_on,
      pos
    };
  }
}
class SubgraphInput extends SubgraphSlot {
  static {
    __name(this, "SubgraphInput");
  }
  events = new CustomEventTarget();
  /** The linked widget that this slot is connected to. */
  #widgetRef;
  get _widget() {
    return this.#widgetRef?.deref();
  }
  set _widget(widget) {
    this.#widgetRef = widget ? new WeakRef(widget) : void 0;
  }
  connect(slot, node2, afterRerouteId) {
    const { subgraph } = this.parent;
    const inputIndex = node2.inputs.indexOf(slot);
    if (node2.onConnectInput?.(inputIndex, this.type, this, this.parent, -1) === false)
      return;
    if (slot.link != null) {
      subgraph.beforeChange();
      const link2 = subgraph.getLink(slot.link);
      this.parent._disconnectNodeInput(node2, slot, link2);
    }
    const inputWidget = node2.getWidgetFromSlot(slot);
    if (inputWidget) {
      if (!this.matchesWidget(inputWidget)) {
        console.warn("Target input has invalid widget.", slot, node2);
        return;
      }
      this._widget ??= inputWidget;
      this.events.dispatch("input-connected", {
        input: slot,
        widget: inputWidget
      });
    }
    const link = new LLink(
      ++subgraph.state.lastLinkId,
      slot.type,
      this.parent.id,
      this.parent.slots.indexOf(this),
      node2.id,
      inputIndex,
      afterRerouteId
    );
    subgraph._links.set(link.id, link);
    this.linkIds.push(link.id);
    slot.link = link.id;
    const reroutes = LLink.getReroutes(subgraph, link);
    for (const reroute of reroutes) {
      reroute.linkIds.add(link.id);
      if (reroute.floating) delete reroute.floating;
      reroute._dragging = void 0;
    }
    const lastReroute = reroutes.at(-1);
    if (lastReroute) {
      for (const linkId of lastReroute.floatingLinkIds) {
        const link2 = subgraph.floatingLinks.get(linkId);
        if (link2?.parentId === lastReroute.id) {
          subgraph.removeFloatingLink(link2);
        }
      }
    }
    subgraph._version++;
    node2.onConnectionsChange?.(NodeSlotType.INPUT, inputIndex, true, link, slot);
    subgraph.afterChange();
    return link;
  }
  get labelPos() {
    const [x2, y, , height] = this.boundingRect;
    return [x2, y + height * 0.5];
  }
  getConnectedWidgets() {
    const { subgraph } = this.parent;
    const widgets2 = [];
    for (const linkId of this.linkIds) {
      const link = subgraph.getLink(linkId);
      if (!link) {
        console.error("Link not found", linkId);
        continue;
      }
      const resolved = link.resolve(subgraph);
      if (resolved.input && resolved.inputNode?.widgets) {
        const widgetNamePojo = resolved.input.widget;
        if (!widgetNamePojo) continue;
        if (!widgetNamePojo.name) {
          console.warn("Invalid widget name", widgetNamePojo);
          continue;
        }
        const widget = resolved.inputNode.widgets.find(
          (w) => w.name === widgetNamePojo.name
        );
        if (!widget) {
          console.warn("Widget not found", widgetNamePojo);
          continue;
        }
        widgets2.push(widget);
      }
    }
    return widgets2;
  }
  /**
   * Validates that the connection between the new slot and the existing widget is valid.
   * Used to prevent connections between widgets that are not of the same type.
   * @param otherWidget The widget to compare to.
   * @returns `true` if the connection is valid, otherwise `false`.
   */
  matchesWidget(otherWidget) {
    const widget = this.#widgetRef?.deref();
    if (!widget) return true;
    if (otherWidget.type !== widget.type || otherWidget.options.min !== widget.options.min || otherWidget.options.max !== widget.options.max || otherWidget.options.step !== widget.options.step || otherWidget.options.step2 !== widget.options.step2 || otherWidget.options.precision !== widget.options.precision) {
      return false;
    }
    return true;
  }
  disconnect() {
    super.disconnect();
    this.events.dispatch("input-disconnected", { input: this });
  }
  /** For inputs, x is the right edge of the input node. */
  arrange(rect) {
    const [right, top, width2, height] = rect;
    const { boundingRect: b, pos } = this;
    b[0] = right - width2;
    b[1] = top;
    b[2] = width2;
    b[3] = height;
    pos[0] = right - height * 0.5;
    pos[1] = top + height * 0.5;
  }
  /**
   * Checks if this slot is a valid target for a connection from the given slot.
   * For SubgraphInput (which acts as an output inside the subgraph),
   * the fromSlot should be an input slot.
   */
  isValidTarget(fromSlot) {
    if (isNodeSlot(fromSlot)) {
      return "link" in fromSlot && LiteGraph.isValidConnection(this.type, fromSlot.type);
    }
    if (isSubgraphOutput(fromSlot)) {
      return LiteGraph.isValidConnection(this.type, fromSlot.type);
    }
    return false;
  }
}
class EmptySubgraphInput extends SubgraphInput {
  static {
    __name(this, "EmptySubgraphInput");
  }
  constructor(parent) {
    super(
      {
        id: zeroUuid,
        name: "",
        type: ""
      },
      parent
    );
  }
  connect(slot, node2, afterRerouteId) {
    const { subgraph } = this.parent;
    const existingNames = subgraph.inputs.map((x2) => x2.name);
    const name2 = nextUniqueName(slot.name, existingNames);
    const input = subgraph.addInput(name2, String(slot.type));
    return input.connect(slot, node2, afterRerouteId);
  }
  get labelPos() {
    const [x2, y, , height] = this.boundingRect;
    return [x2, y + height * 0.5];
  }
}
class SubgraphIONodeBase {
  static {
    __name(this, "SubgraphIONodeBase");
  }
  constructor(subgraph) {
    this.subgraph = subgraph;
  }
  static margin = 10;
  static minWidth = 100;
  static roundedRadius = 10;
  #boundingRect = new Rectangle();
  get boundingRect() {
    return this.#boundingRect;
  }
  selected = false;
  pinned = false;
  removable = false;
  isPointerOver = false;
  get pos() {
    return this.boundingRect.pos;
  }
  set pos(value) {
    this.boundingRect.pos = value;
  }
  get size() {
    return this.boundingRect.size;
  }
  set size(value) {
    this.boundingRect.size = value;
  }
  get sideLineWidth() {
    return this.isPointerOver ? 2.5 : 2;
  }
  get sideStrokeStyle() {
    return this.isPointerOver ? "white" : "#efefef";
  }
  move(deltaX, deltaY) {
    this.pos[0] += deltaX;
    this.pos[1] += deltaY;
  }
  /** @inheritdoc */
  snapToGrid(snapTo) {
    return this.pinned ? false : snapPoint(this.pos, snapTo);
  }
  // #region Hoverable
  containsPoint(point) {
    return this.boundingRect.containsPoint(point);
  }
  onPointerMove(e2) {
    const containsPoint = this.boundingRect.containsXy(e2.canvasX, e2.canvasY);
    let underPointer = containsPoint ? CanvasItem.SubgraphIoNode : CanvasItem.Nothing;
    if (containsPoint) {
      if (!this.isPointerOver) this.onPointerEnter();
      for (const slot of this.allSlots) {
        slot.onPointerMove(e2);
        if (slot.isPointerOver) underPointer |= CanvasItem.SubgraphIoSlot;
      }
    } else if (this.isPointerOver) {
      this.onPointerLeave();
    }
    return underPointer;
  }
  onPointerEnter() {
    this.isPointerOver = true;
  }
  onPointerLeave() {
    this.isPointerOver = false;
    for (const slot of this.slots) {
      slot.isPointerOver = false;
    }
  }
  /**
   * Gets the slot at a given position in canvas space.
   * @param x The x coordinate of the position.
   * @param y The y coordinate of the position.
   * @returns The slot at the given position, otherwise `undefined`.
   */
  getSlotInPosition(x2, y) {
    for (const slot of this.allSlots) {
      if (slot.boundingRect.containsXy(x2, y)) {
        return slot;
      }
    }
  }
  /**
   * Handles double-click on an IO slot to rename it.
   * @param slot The slot that was double-clicked.
   * @param event The event that triggered the double-click.
   */
  handleSlotDoubleClick(slot, event) {
    if (slot !== this.emptySlot) {
      this.#promptForSlotRename(slot, event);
    }
  }
  /**
   * Shows the context menu for an IO slot.
   * @param slot The slot to show the context menu for.
   * @param event The event that triggered the context menu.
   */
  showSlotContextMenu(slot, event) {
    const options = this.#getSlotMenuOptions(slot);
    if (!(options.length > 0)) return;
    new LiteGraph.ContextMenu(options, {
      event,
      title: slot.name || "Subgraph Output",
      callback: /* @__PURE__ */ __name((item) => {
        this.#onSlotMenuAction(item, slot, event);
      }, "callback")
    });
  }
  /**
   * Gets the context menu options for an IO slot.
   * @param slot The slot to get the context menu options for.
   * @returns The context menu options.
   */
  #getSlotMenuOptions(slot) {
    const options = [];
    if (slot !== this.emptySlot && slot.linkIds.length > 0) {
      options.push({ content: "Disconnect Links", value: "disconnect" });
    }
    if (slot !== this.emptySlot) {
      options.push({ content: "Rename Slot", value: "rename" });
    }
    if (slot !== this.emptySlot) {
      options.push(null);
      options.push({
        content: "Remove Slot",
        value: "remove",
        className: "danger"
      });
    }
    return options;
  }
  /**
   * Handles the action for an IO slot context menu.
   * @param selectedItem The item that was selected from the context menu.
   * @param slot The slot
   * @param event The event that triggered the context menu.
   */
  #onSlotMenuAction(selectedItem, slot, event) {
    switch (selectedItem.value) {
      // Disconnect all links from this output
      case "disconnect":
        slot.disconnect();
        break;
      // Remove the slot
      case "remove":
        if (slot !== this.emptySlot) {
          this.removeSlot(slot);
        }
        break;
      // Rename the slot
      case "rename":
        if (slot !== this.emptySlot) {
          this.#promptForSlotRename(slot, event);
        }
        break;
    }
    this.subgraph.setDirtyCanvas(true);
  }
  /**
   * Prompts the user to rename a slot.
   * @param slot The slot to rename.
   * @param event The event that triggered the rename.
   */
  #promptForSlotRename(slot, event) {
    this.subgraph.canvasAction(
      (c2) => c2.prompt(
        "Slot name",
        slot.displayName,
        (newName) => {
          if (newName) this.renameSlot(slot, newName);
        },
        event
      )
    );
  }
  /** Arrange the slots in this node. */
  arrange() {
    const { minWidth, roundedRadius } = SubgraphIONodeBase;
    const [, y] = this.boundingRect;
    const x2 = this.slotAnchorX;
    const { size } = this;
    let maxWidth = minWidth;
    let currentY = y + roundedRadius;
    for (const slot of this.allSlots) {
      const [slotWidth, slotHeight] = slot.measure();
      slot.arrange([x2, currentY, slotWidth, slotHeight]);
      currentY += slotHeight;
      if (slotWidth > maxWidth) maxWidth = slotWidth;
    }
    size[0] = maxWidth + 2 * roundedRadius;
    size[1] = currentY - y + roundedRadius;
  }
  draw(ctx, colorContext, fromSlot, editorAlpha) {
    const { lineWidth, strokeStyle, fillStyle, font, textBaseline } = ctx;
    this.drawProtected(ctx, colorContext, fromSlot, editorAlpha);
    Object.assign(ctx, {
      lineWidth,
      strokeStyle,
      fillStyle,
      font,
      textBaseline
    });
  }
  /** @internal Leaves {@link ctx} dirty. */
  drawSlots(ctx, colorContext, fromSlot, editorAlpha) {
    ctx.fillStyle = "#AAA";
    ctx.font = "12px Inter, sans-serif";
    ctx.textBaseline = "middle";
    for (const slot of this.allSlots) {
      slot.draw({ ctx, colorContext, fromSlot, editorAlpha });
    }
  }
  configure(data) {
    this.#boundingRect.set(data.bounding);
    this.pinned = data.pinned ?? false;
  }
  asSerialisable() {
    return {
      id: this.id,
      bounding: this.boundingRect.export(),
      pinned: this.pinned ? true : void 0
    };
  }
}
class SubgraphInputNode extends SubgraphIONodeBase {
  static {
    __name(this, "SubgraphInputNode");
  }
  id = SUBGRAPH_INPUT_ID;
  emptySlot = new EmptySubgraphInput(this);
  get slots() {
    return this.subgraph.inputs;
  }
  get allSlots() {
    return [...this.slots, this.emptySlot];
  }
  get slotAnchorX() {
    const [x2, , width2] = this.boundingRect;
    return x2 + width2 - SubgraphIONodeBase.roundedRadius;
  }
  onPointerDown(e2, pointer, linkConnector) {
    if (e2.button === 0) {
      for (const slot of this.allSlots) {
        if (slot.boundingRect.containsXy(e2.canvasX, e2.canvasY)) {
          pointer.onDragStart = () => {
            linkConnector.dragNewFromSubgraphInput(this.subgraph, this, slot);
          };
          pointer.onDragEnd = (eUp) => {
            linkConnector.dropLinks(this.subgraph, eUp);
          };
          pointer.onDoubleClick = () => {
            this.handleSlotDoubleClick(slot, e2);
          };
          pointer.finally = () => {
            linkConnector.reset(true);
          };
        }
      }
    } else if (e2.button === 2) {
      const slot = this.getSlotInPosition(e2.canvasX, e2.canvasY);
      if (slot) this.showSlotContextMenu(slot, e2);
    }
  }
  /** @inheritdoc */
  renameSlot(slot, name2) {
    this.subgraph.renameInput(slot, name2);
  }
  /** @inheritdoc */
  removeSlot(slot) {
    this.subgraph.removeInput(slot);
  }
  canConnectTo(inputNode, input, fromSlot) {
    return inputNode.canConnectTo(this, input, fromSlot);
  }
  connectSlots(fromSlot, inputNode, input, afterRerouteId) {
    const { subgraph } = this;
    const outputIndex = this.slots.indexOf(fromSlot);
    const inputIndex = inputNode.inputs.indexOf(input);
    if (outputIndex === -1 || inputIndex === -1)
      throw new Error("Invalid slot indices.");
    return new LLink(
      ++subgraph.state.lastLinkId,
      input.type || fromSlot.type,
      this.id,
      outputIndex,
      inputNode.id,
      inputIndex,
      afterRerouteId
    );
  }
  // #region Legacy LGraphNode compatibility
  connectByType(slot, target_node, target_slotType, optsIn) {
    const inputSlot = target_node.findInputByType(target_slotType);
    if (!inputSlot) return;
    if (slot === -1) {
      const newSubgraphInput = this.subgraph.addInput(
        inputSlot.slot.name,
        String(inputSlot.slot.type ?? "")
      );
      const newSlotIndex = this.slots.indexOf(newSubgraphInput);
      if (newSlotIndex === -1) {
        console.error("Could not find newly created subgraph input slot.");
        return;
      }
      slot = newSlotIndex;
    }
    return this.slots[slot].connect(
      inputSlot.slot,
      target_node,
      optsIn?.afterRerouteId
    );
  }
  findOutputSlot(name2) {
    return this.slots.find((output) => output.name === name2);
  }
  findOutputByType(type) {
    return findFreeSlotOfType(
      this.slots,
      type,
      (slot) => slot.linkIds.length > 0
    )?.slot;
  }
  // #endregion Legacy LGraphNode compatibility
  _disconnectNodeInput(node2, input, link) {
    const { subgraph } = this;
    if (input._floatingLinks?.size) {
      for (const link2 of input._floatingLinks) {
        subgraph.removeFloatingLink(link2);
      }
    }
    input.link = null;
    subgraph.setDirtyCanvas(false, true);
    if (!link) return;
    const subgraphInputIndex = link.origin_slot;
    link.disconnect(subgraph, "output");
    subgraph._version++;
    const subgraphInput = this.slots.at(subgraphInputIndex);
    if (!subgraphInput) {
      console.warn(
        "disconnectNodeInput: subgraphInput not found",
        this,
        subgraphInputIndex
      );
      return;
    }
    const index2 = subgraphInput.linkIds.indexOf(link.id);
    if (index2 !== -1) {
      subgraphInput.linkIds.splice(index2, 1);
    } else {
      console.warn(
        "disconnectNodeInput: link ID not found in subgraphInput linkIds",
        link.id
      );
    }
    const slotIndex = node2.inputs.findIndex((inp) => inp === input);
    if (slotIndex !== -1) {
      node2.onConnectionsChange?.(
        NodeSlotType.INPUT,
        slotIndex,
        false,
        link,
        subgraphInput
      );
    }
  }
  drawProtected(ctx, colorContext, fromSlot, editorAlpha) {
    const { roundedRadius } = SubgraphIONodeBase;
    const transform = ctx.getTransform();
    const [x2, y, width2, height] = this.boundingRect;
    ctx.translate(x2, y);
    ctx.strokeStyle = this.sideStrokeStyle;
    ctx.lineWidth = this.sideLineWidth;
    ctx.beginPath();
    ctx.arc(
      width2 - roundedRadius,
      roundedRadius,
      roundedRadius,
      Math.PI * 1.5,
      0
    );
    ctx.moveTo(width2, roundedRadius);
    ctx.lineTo(width2, height - roundedRadius);
    ctx.arc(
      width2 - roundedRadius,
      height - roundedRadius,
      roundedRadius,
      0,
      Math.PI * 0.5
    );
    ctx.stroke();
    ctx.setTransform(transform);
    this.drawSlots(ctx, colorContext, fromSlot, editorAlpha);
  }
}
class SubgraphOutput extends SubgraphSlot {
  static {
    __name(this, "SubgraphOutput");
  }
  connect(slot, node2, afterRerouteId) {
    const { subgraph } = this.parent;
    if (!LiteGraph.isValidConnection(slot.type, this.type)) return;
    const outputIndex = node2.outputs.indexOf(slot);
    if (outputIndex === -1)
      throw new Error("Slot is not an output of the given node");
    if (node2.onConnectOutput?.(outputIndex, this.type, this, this.parent, -1) === false)
      return;
    const existingLink = this.getLinks().at(0);
    if (existingLink != null) {
      subgraph.beforeChange();
      existingLink.disconnect(subgraph, "input");
      const resolved = existingLink.resolve(subgraph);
      const links = resolved.output?.links;
      if (links) pull(links, existingLink.id);
    }
    const link = new LLink(
      ++subgraph.state.lastLinkId,
      slot.type,
      node2.id,
      outputIndex,
      this.parent.id,
      this.parent.slots.indexOf(this),
      afterRerouteId
    );
    subgraph._links.set(link.id, link);
    this.linkIds[0] = link.id;
    slot.links ??= [];
    slot.links.push(link.id);
    const reroutes = LLink.getReroutes(subgraph, link);
    for (const reroute of reroutes) {
      reroute.linkIds.add(link.id);
      if (reroute.floating) delete reroute.floating;
      reroute._dragging = void 0;
    }
    const lastReroute = reroutes.at(-1);
    if (lastReroute) {
      for (const linkId of lastReroute.floatingLinkIds) {
        const link2 = subgraph.floatingLinks.get(linkId);
        if (link2?.parentId === lastReroute.id) {
          subgraph.removeFloatingLink(link2);
        }
      }
    }
    subgraph._version++;
    node2.onConnectionsChange?.(
      NodeSlotType.OUTPUT,
      outputIndex,
      true,
      link,
      slot
    );
    subgraph.afterChange();
    return link;
  }
  get labelPos() {
    const [x2, y, , height] = this.boundingRect;
    return [x2 + height, y + height * 0.5];
  }
  arrange(rect) {
    const [left, top, width2, height] = rect;
    const { boundingRect: b, pos } = this;
    b[0] = left;
    b[1] = top;
    b[2] = width2;
    b[3] = height;
    pos[0] = left + height * 0.5;
    pos[1] = top + height * 0.5;
  }
  /**
   * Checks if this slot is a valid target for a connection from the given slot.
   * For SubgraphOutput (which acts as an input inside the subgraph),
   * the fromSlot should be an output slot.
   */
  isValidTarget(fromSlot) {
    if (isNodeSlot(fromSlot)) {
      return "links" in fromSlot && LiteGraph.isValidConnection(fromSlot.type, this.type);
    }
    if (isSubgraphInput(fromSlot)) {
      return LiteGraph.isValidConnection(fromSlot.type, this.type);
    }
    return false;
  }
  disconnect() {
    const { subgraph } = this.parent;
    for (const linkId of this.linkIds) {
      const link = subgraph.links[linkId];
      if (!link) continue;
      subgraph.removeLink(linkId);
      const { output, outputNode } = link.resolve(subgraph);
      if (output)
        output.links = output.links?.filter((id2) => id2 !== linkId) ?? null;
      outputNode?.onConnectionsChange?.(
        NodeSlotType.OUTPUT,
        link.origin_slot,
        false,
        link,
        this
      );
    }
    this.linkIds.length = 0;
  }
}
class EmptySubgraphOutput extends SubgraphOutput {
  static {
    __name(this, "EmptySubgraphOutput");
  }
  constructor(parent) {
    super(
      {
        id: zeroUuid,
        name: "",
        type: ""
      },
      parent
    );
  }
  connect(slot, node2, afterRerouteId) {
    const { subgraph } = this.parent;
    const existingNames = subgraph.outputs.map((x2) => x2.name);
    const name2 = nextUniqueName(slot.name, existingNames);
    const output = subgraph.addOutput(name2, String(slot.type));
    return output.connect(slot, node2, afterRerouteId);
  }
  get labelPos() {
    const [x2, y, , height] = this.boundingRect;
    return [x2, y + height * 0.5];
  }
}
class SubgraphOutputNode extends SubgraphIONodeBase {
  static {
    __name(this, "SubgraphOutputNode");
  }
  id = SUBGRAPH_OUTPUT_ID;
  emptySlot = new EmptySubgraphOutput(this);
  get slots() {
    return this.subgraph.outputs;
  }
  get allSlots() {
    return [...this.slots, this.emptySlot];
  }
  get slotAnchorX() {
    const [x2] = this.boundingRect;
    return x2 + SubgraphIONodeBase.roundedRadius;
  }
  onPointerDown(e2, pointer, linkConnector) {
    if (e2.button === 0) {
      for (const slot of this.allSlots) {
        if (slot.boundingRect.containsXy(e2.canvasX, e2.canvasY)) {
          pointer.onDragStart = () => {
            linkConnector.dragNewFromSubgraphOutput(this.subgraph, this, slot);
          };
          pointer.onDragEnd = (eUp) => {
            linkConnector.dropLinks(this.subgraph, eUp);
          };
          pointer.onDoubleClick = () => {
            this.handleSlotDoubleClick(slot, e2);
          };
          pointer.finally = () => {
            linkConnector.reset(true);
          };
        }
      }
    } else if (e2.button === 2) {
      const slot = this.getSlotInPosition(e2.canvasX, e2.canvasY);
      if (slot) this.showSlotContextMenu(slot, e2);
    }
  }
  /** @inheritdoc */
  renameSlot(slot, name2) {
    this.subgraph.renameOutput(slot, name2);
  }
  /** @inheritdoc */
  removeSlot(slot) {
    this.subgraph.removeOutput(slot);
  }
  canConnectTo(outputNode, fromSlot, output) {
    return outputNode.canConnectTo(this, fromSlot, output);
  }
  connectByTypeOutput(slot, target_node, target_slotType, optsIn) {
    const outputSlot = target_node.findOutputByType(target_slotType);
    if (!outputSlot) return;
    return this.slots[slot].connect(
      outputSlot.slot,
      target_node,
      optsIn?.afterRerouteId
    );
  }
  findInputByType(type) {
    return findFreeSlotOfType(
      this.slots,
      type,
      (slot) => slot.linkIds.length > 0
    )?.slot;
  }
  drawProtected(ctx, colorContext, fromSlot, editorAlpha) {
    const { roundedRadius } = SubgraphIONodeBase;
    const transform = ctx.getTransform();
    const [x2, y, , height] = this.boundingRect;
    ctx.translate(x2, y);
    ctx.strokeStyle = this.sideStrokeStyle;
    ctx.lineWidth = this.sideLineWidth;
    ctx.beginPath();
    ctx.arc(roundedRadius, roundedRadius, roundedRadius, Math.PI, Math.PI * 1.5);
    ctx.moveTo(0, roundedRadius);
    ctx.lineTo(0, height - roundedRadius);
    ctx.arc(
      roundedRadius,
      height - roundedRadius,
      roundedRadius,
      Math.PI,
      Math.PI * 0.5,
      true
    );
    ctx.stroke();
    ctx.setTransform(transform);
    this.drawSlots(ctx, colorContext, fromSlot, editorAlpha);
  }
}
function splitPositionables(items) {
  const nodes = /* @__PURE__ */ new Set();
  const reroutes = /* @__PURE__ */ new Set();
  const groups = /* @__PURE__ */ new Set();
  const subgraphInputNodes = /* @__PURE__ */ new Set();
  const subgraphOutputNodes = /* @__PURE__ */ new Set();
  const unknown = /* @__PURE__ */ new Set();
  for (const item of items) {
    switch (true) {
      case item instanceof LGraphNode:
        nodes.add(item);
        break;
      case item instanceof LGraphGroup:
        groups.add(item);
        break;
      case item instanceof Reroute:
        reroutes.add(item);
        break;
      case item instanceof SubgraphInputNode:
        subgraphInputNodes.add(item);
        break;
      case item instanceof SubgraphOutputNode:
        subgraphOutputNodes.add(item);
        break;
      default:
        unknown.add(item);
        break;
    }
  }
  return {
    nodes,
    reroutes,
    groups,
    subgraphInputNodes,
    subgraphOutputNodes,
    unknown
  };
}
__name(splitPositionables, "splitPositionables");
function getBoundaryLinks(graph, items) {
  const internalLinks = [];
  const boundaryLinks = [];
  const boundaryInputLinks = [];
  const boundaryOutputLinks = [];
  const boundaryFloatingLinks = [];
  const visited = /* @__PURE__ */ new WeakSet();
  for (const item of items) {
    if (visited.has(item)) continue;
    visited.add(item);
    if (item instanceof LGraphNode) {
      const node2 = item;
      if (node2.inputs) {
        for (const input of node2.inputs) {
          addFloatingLinks(input._floatingLinks);
          if (input.link == null) continue;
          const resolved = LLink.resolve(input.link, graph);
          if (!resolved) {
            console.warn(`Failed to resolve link ID [${input.link}]`);
            continue;
          }
          const { link, outputNode } = resolved;
          if (outputNode) {
            if (!items.has(outputNode)) {
              boundaryInputLinks.push(link);
            } else {
              internalLinks.push(link);
            }
          } else if (link.origin_id === SUBGRAPH_INPUT_ID) {
            boundaryInputLinks.push(link);
          }
        }
      }
      if (node2.outputs) {
        for (const output of node2.outputs) {
          addFloatingLinks(output._floatingLinks);
          if (!output.links) continue;
          const many = LLink.resolveMany(output.links, graph);
          for (const { link, inputNode } of many) {
            if (
              // Subgraph output node
              link.target_id === SUBGRAPH_OUTPUT_ID || // Input end of this link is outside the items set
              inputNode && !items.has(inputNode)
            ) {
              boundaryOutputLinks.push(link);
            }
          }
        }
      }
    } else if (item instanceof Reroute) {
      const reroute = item;
      const results = LLink.resolveMany(reroute.linkIds, graph);
      for (const { link } of results) {
        const reroutes = LLink.getReroutes(graph, link);
        const reroutesOutside = reroutes.filter(
          (reroute2) => !items.has(reroute2)
        );
        const { inputNode, outputNode } = link.resolve(graph);
        if (reroutesOutside.length || inputNode && !items.has(inputNode) || outputNode && !items.has(outputNode)) {
          boundaryLinks.push(link);
        }
      }
    }
  }
  return {
    boundaryLinks,
    boundaryFloatingLinks,
    internalLinks,
    boundaryInputLinks,
    boundaryOutputLinks
  };
  function addFloatingLinks(floatingLinks) {
    if (!floatingLinks) return;
    for (const link of floatingLinks) {
      const crossesBoundary = LLink.getReroutes(graph, link).some(
        (reroute) => !items.has(reroute)
      );
      if (crossesBoundary) boundaryFloatingLinks.push(link);
    }
  }
  __name(addFloatingLinks, "addFloatingLinks");
}
__name(getBoundaryLinks, "getBoundaryLinks");
function multiClone(nodes) {
  const clonedNodes = [];
  for (const node2 of nodes) {
    const newNode = LiteGraph.createNode(node2.type);
    if (!newNode) {
      console.warn("Failed to create node", node2.type);
      const serializedData = structuredClone(node2.serialize());
      clonedNodes.push(serializedData);
      continue;
    }
    const data = structuredClone(node2.serialize());
    newNode.configure(data);
    clonedNodes.push(newNode.serialize());
  }
  return clonedNodes;
}
__name(multiClone, "multiClone");
function groupResolvedByOutput(resolvedConnections) {
  const groupedByOutput = /* @__PURE__ */ new Map();
  for (const resolved of resolvedConnections) {
    const groupBy2 = resolved.subgraphInput ?? resolved.output ?? {};
    const group = groupedByOutput.get(groupBy2);
    if (group) {
      group.push(resolved);
    } else {
      groupedByOutput.set(groupBy2, [resolved]);
    }
  }
  return groupedByOutput;
}
__name(groupResolvedByOutput, "groupResolvedByOutput");
function mapSubgraphInputsAndLinks(resolvedInputLinks, links) {
  const groupedByOutput = groupResolvedByOutput(resolvedInputLinks);
  const inputs = [];
  for (const [, connections] of groupedByOutput) {
    const inputLinks = [];
    for (const resolved of connections) {
      const { link, input: input2 } = resolved;
      if (!input2) continue;
      const linkData = link.asSerialisable();
      linkData.origin_id = SUBGRAPH_INPUT_ID;
      linkData.origin_slot = inputs.length;
      links.push(linkData);
      inputLinks.push(linkData);
    }
    const { input } = connections[0];
    if (!input) continue;
    const {
      color_off,
      color_on,
      dir,
      hasErrors,
      label,
      localized_name,
      name: name2,
      shape: shape2,
      type
    } = input;
    const uniqueName = nextUniqueName(
      name2,
      inputs.map((input2) => input2.name)
    );
    const uniqueLocalizedName = localized_name ? nextUniqueName(
      localized_name,
      inputs.map((input2) => input2.localized_name ?? "")
    ) : void 0;
    const inputData = {
      id: createUuidv4(),
      type: String(type),
      linkIds: inputLinks.map((link) => link.id),
      name: uniqueName,
      color_off,
      color_on,
      dir,
      label,
      localized_name: uniqueLocalizedName,
      hasErrors,
      shape: shape2
    };
    inputs.push(inputData);
  }
  return inputs;
}
__name(mapSubgraphInputsAndLinks, "mapSubgraphInputsAndLinks");
function mapSubgraphOutputsAndLinks(resolvedOutputLinks, links) {
  const groupedByOutput = groupResolvedByOutput(resolvedOutputLinks);
  const outputs = [];
  for (const [, connections] of groupedByOutput) {
    const outputLinks = [];
    for (const resolved of connections) {
      const { link, output: output2 } = resolved;
      if (!output2) continue;
      const linkData = link.asSerialisable();
      linkData.target_id = SUBGRAPH_OUTPUT_ID;
      linkData.target_slot = outputs.length;
      links.push(linkData);
      outputLinks.push(linkData);
    }
    const { output } = connections[0];
    if (!output) continue;
    const {
      color_off,
      color_on,
      dir,
      hasErrors,
      label,
      localized_name,
      name: name2,
      shape: shape2,
      type
    } = output;
    const uniqueName = nextUniqueName(
      name2,
      outputs.map((output2) => output2.name)
    );
    const uniqueLocalizedName = localized_name ? nextUniqueName(
      localized_name,
      outputs.map((output2) => output2.localized_name ?? "")
    ) : void 0;
    const outputData = {
      id: createUuidv4(),
      type: String(type),
      linkIds: outputLinks.map((link) => link.id),
      name: uniqueName,
      color_off,
      color_on,
      dir,
      label,
      localized_name: uniqueLocalizedName,
      hasErrors,
      shape: shape2
    };
    outputs.push(structuredClone(outputData));
  }
  return outputs;
}
__name(mapSubgraphOutputsAndLinks, "mapSubgraphOutputsAndLinks");
function getDirectSubgraphIds(graph) {
  const subgraphIds = /* @__PURE__ */ new Set();
  for (const node2 of graph._nodes) {
    if (node2.isSubgraphNode()) {
      subgraphIds.add(node2.type);
    }
  }
  return subgraphIds;
}
__name(getDirectSubgraphIds, "getDirectSubgraphIds");
function findUsedSubgraphIds(rootGraph, subgraphRegistry) {
  const usedSubgraphIds = /* @__PURE__ */ new Set();
  const toVisit = [rootGraph];
  while (toVisit.length > 0) {
    const graph = toVisit.shift();
    const directIds = getDirectSubgraphIds(graph);
    for (const id2 of directIds) {
      if (!usedSubgraphIds.has(id2)) {
        usedSubgraphIds.add(id2);
        const subgraph = subgraphRegistry.get(id2);
        if (subgraph) {
          toVisit.push(subgraph);
        }
      }
    }
  }
  return usedSubgraphIds;
}
__name(findUsedSubgraphIds, "findUsedSubgraphIds");
function isSubgraphInput(slot) {
  return slot != null && typeof slot === "object" && "parent" in slot && slot.parent instanceof SubgraphInputNode;
}
__name(isSubgraphInput, "isSubgraphInput");
function isSubgraphOutput(slot) {
  return slot != null && typeof slot === "object" && "parent" in slot && slot.parent instanceof SubgraphOutputNode;
}
__name(isSubgraphOutput, "isSubgraphOutput");
function isNodeSlot(slot) {
  return slot != null && typeof slot === "object" && ("link" in slot || "links" in slot);
}
__name(isNodeSlot, "isNodeSlot");
class NodeInputSlot extends NodeSlot {
  static {
    __name(this, "NodeInputSlot");
  }
  link;
  alwaysVisible;
  get isWidgetInputSlot() {
    return !!this.widget;
  }
  #widget;
  /** Internal use only; API is not finalised and may change at any time. */
  get _widget() {
    return this.#widget?.deref();
  }
  set _widget(widget) {
    this.#widget = widget ? new WeakRef(widget) : void 0;
  }
  get collapsedPos() {
    return [0, LiteGraph.NODE_TITLE_HEIGHT * -0.5];
  }
  constructor(slot, node2) {
    super(slot, node2);
    this.link = slot.link;
  }
  get isConnected() {
    return this.link != null;
  }
  isValidTarget(fromSlot) {
    if ("links" in fromSlot) {
      return LiteGraph.isValidConnection(fromSlot.type, this.type);
    }
    if (isSubgraphInput(fromSlot)) {
      return LiteGraph.isValidConnection(fromSlot.type, this.type);
    }
    return false;
  }
  draw(ctx, options) {
    const { textAlign } = ctx;
    ctx.textAlign = "left";
    super.draw(ctx, {
      ...options,
      labelPosition: LabelPosition.Right,
      doStroke: false
    });
    ctx.textAlign = textAlign;
  }
}
class NodeOutputSlot extends NodeSlot {
  static {
    __name(this, "NodeOutputSlot");
  }
  #node;
  links;
  _data;
  slot_index;
  get isWidgetInputSlot() {
    return false;
  }
  get collapsedPos() {
    return [
      this.#node._collapsed_width ?? LiteGraph.NODE_COLLAPSED_WIDTH,
      LiteGraph.NODE_TITLE_HEIGHT * -0.5
    ];
  }
  constructor(slot, node2) {
    super(slot, node2);
    this.links = slot.links;
    this._data = slot._data;
    this.slot_index = slot.slot_index;
    this.#node = node2;
  }
  isValidTarget(fromSlot) {
    if ("link" in fromSlot) {
      return LiteGraph.isValidConnection(this.type, fromSlot.type);
    }
    if (isSubgraphOutput(fromSlot)) {
      return LiteGraph.isValidConnection(this.type, fromSlot.type);
    }
    return false;
  }
  get isConnected() {
    return this.links != null && this.links.length > 0;
  }
  draw(ctx, options) {
    const { textAlign, strokeStyle } = ctx;
    ctx.textAlign = "right";
    ctx.strokeStyle = "black";
    super.draw(ctx, {
      ...options,
      labelPosition: LabelPosition.Left,
      doStroke: true
    });
    ctx.textAlign = textAlign;
    ctx.strokeStyle = strokeStyle;
  }
}
const UNIQUE_MESSAGE_LIMIT = 1e4;
const sentWarnings = /* @__PURE__ */ new Set();
function warnDeprecated(message, source) {
  if (!LiteGraph.alwaysRepeatWarnings) {
    if (sentWarnings.has(message)) return;
    if (sentWarnings.size > UNIQUE_MESSAGE_LIMIT) return;
    sentWarnings.add(message);
  }
  for (const callback of LiteGraph.onDeprecationWarning) {
    callback(message, source);
  }
}
__name(warnDeprecated, "warnDeprecated");
function distributeSpace(totalSpace, requests) {
  if (requests.length === 0) return [];
  const totalMinSize = requests.reduce((sum, req) => sum + req.minSize, 0);
  if (totalSpace < totalMinSize) {
    return requests.map((req) => req.minSize);
  }
  let allocations = requests.map((req) => ({
    computedSize: req.minSize,
    maxSize: req.maxSize ?? Infinity,
    remaining: (req.maxSize ?? Infinity) - req.minSize
  }));
  let remainingSpace = totalSpace - totalMinSize;
  while (remainingSpace > 0 && allocations.some((alloc) => alloc.remaining > 0)) {
    const growableItems = allocations.filter(
      (alloc) => alloc.remaining > 0
    ).length;
    if (growableItems === 0) break;
    const sharePerItem = remainingSpace / growableItems;
    let spaceUsedThisRound = 0;
    allocations = allocations.map((alloc) => {
      if (alloc.remaining <= 0) return alloc;
      const growth = Math.min(sharePerItem, alloc.remaining);
      spaceUsedThisRound += growth;
      return {
        ...alloc,
        computedSize: alloc.computedSize + growth,
        remaining: alloc.remaining - growth
      };
    });
    remainingSpace -= spaceUsedThisRound;
    if (spaceUsedThisRound === 0) break;
  }
  return allocations.map(({ computedSize }) => computedSize);
}
__name(distributeSpace, "distributeSpace");
function truncateText(ctx, text, maxWidth, ellipsis = "...") {
  const textWidth = ctx.measureText(text).width;
  if (textWidth <= maxWidth || maxWidth <= 0) {
    return text;
  }
  const ellipsisWidth = ctx.measureText(ellipsis).width;
  const availableWidth = maxWidth - ellipsisWidth;
  if (availableWidth <= 0) {
    return ellipsis;
  }
  let low = 0;
  let high = text.length;
  let bestFit = 0;
  while (low <= high) {
    const mid = Math.floor((low + high) / 2);
    const testText = text.substring(0, mid);
    const testWidth = ctx.measureText(testText).width;
    if (testWidth <= availableWidth) {
      bestFit = mid;
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }
  return text.substring(0, bestFit) + ellipsis;
}
__name(truncateText, "truncateText");
class BaseWidget {
  static {
    __name(this, "BaseWidget");
  }
  /** From node edge to widget edge */
  static margin = 15;
  /** From widget edge to tip of arrow button */
  static arrowMargin = 6;
  /** Arrow button width */
  static arrowWidth = 10;
  /** Absolute minimum display width of widget values */
  static minValueWidth = 42;
  /** Minimum gap between label and value */
  static labelValueGap = 5;
  #node;
  /** The node that this widget belongs to. */
  get node() {
    return this.#node;
  }
  linkedWidgets;
  name;
  options;
  label;
  type;
  y = 0;
  last_y;
  width;
  disabled;
  computedDisabled;
  hidden;
  advanced;
  promoted;
  tooltip;
  element;
  #value;
  get value() {
    return this.#value;
  }
  set value(value) {
    this.#value = value;
  }
  constructor(widget, node2) {
    this.#node = node2 ?? widget.node;
    this.name = widget.name;
    this.options = widget.options;
    this.type = widget.type;
    const {
      node: _,
      // @ts-expect-error Prevent naming conflicts with custom nodes.
      outline_color,
      // @ts-expect-error Prevent naming conflicts with custom nodes.
      background_color,
      // @ts-expect-error Prevent naming conflicts with custom nodes.
      height,
      // @ts-expect-error Prevent naming conflicts with custom nodes.
      text_color,
      // @ts-expect-error Prevent naming conflicts with custom nodes.
      secondary_text_color,
      // @ts-expect-error Prevent naming conflicts with custom nodes.
      disabledTextColor,
      // @ts-expect-error Prevent naming conflicts with custom nodes.
      displayName,
      // @ts-expect-error Prevent naming conflicts with custom nodes.
      displayValue,
      // @ts-expect-error Prevent naming conflicts with custom nodes.
      labelBaseline,
      promoted,
      ...safeValues
    } = widget;
    Object.assign(this, safeValues);
  }
  get outline_color() {
    if (this.promoted) return LiteGraph.WIDGET_PROMOTED_OUTLINE_COLOR;
    return this.advanced ? LiteGraph.WIDGET_ADVANCED_OUTLINE_COLOR : LiteGraph.WIDGET_OUTLINE_COLOR;
  }
  get background_color() {
    return LiteGraph.WIDGET_BGCOLOR;
  }
  get height() {
    return LiteGraph.NODE_WIDGET_HEIGHT;
  }
  get text_color() {
    return LiteGraph.WIDGET_TEXT_COLOR;
  }
  get secondary_text_color() {
    return LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
  }
  get disabledTextColor() {
    return LiteGraph.WIDGET_DISABLED_TEXT_COLOR;
  }
  get displayName() {
    return this.label || this.name;
  }
  // TODO: Resolve this workaround. Ref: https://github.com/Comfy-Org/litegraph.js/issues/1022
  get _displayValue() {
    return this.computedDisabled ? "" : String(this.value);
  }
  get labelBaseline() {
    return this.y + this.height * 0.7;
  }
  /**
   * Draws the standard widget shape - elongated capsule. The path of the widget shape is not
   * cleared, and may be used for further drawing.
   * @param ctx The canvas context
   * @param options The options for drawing the widget
   * @remarks Leaves {@link ctx} dirty.
   */
  drawWidgetShape(ctx, { width: width2, showText }) {
    const { height, y } = this;
    const { margin } = BaseWidget;
    ctx.textAlign = "left";
    ctx.strokeStyle = this.outline_color;
    ctx.fillStyle = this.background_color;
    ctx.beginPath();
    if (showText) {
      ctx.roundRect(margin, y, width2 - margin * 2, height, [height * 0.5]);
    } else {
      ctx.rect(margin, y, width2 - margin * 2, height);
    }
    ctx.fill();
    if (showText && !this.computedDisabled) ctx.stroke();
  }
  /**
   * A shared routine for drawing a label and value as text, truncated
   * if they exceed the available width.
   */
  drawTruncatingText({
    ctx,
    width: width2,
    leftPadding = 5,
    rightPadding = 20
  }) {
    const { height, y } = this;
    const { margin } = BaseWidget;
    const { displayName, _displayValue } = this;
    const labelWidth = ctx.measureText(displayName).width;
    const valueWidth = ctx.measureText(_displayValue).width;
    const gap = BaseWidget.labelValueGap;
    const x2 = margin * 2 + leftPadding;
    const totalWidth = width2 - x2 - 2 * margin - rightPadding;
    const requiredWidth = labelWidth + gap + valueWidth;
    const area = new Rectangle(x2, y, totalWidth, height * 0.7);
    ctx.fillStyle = this.secondary_text_color;
    if (requiredWidth <= totalWidth) {
      drawTextInArea({ ctx, text: displayName, area, align: "left" });
    } else if (LiteGraph.truncateWidgetTextEvenly) {
      const scale = (totalWidth - gap) / (requiredWidth - gap);
      area.width = labelWidth * scale;
      drawTextInArea({ ctx, text: displayName, area, align: "left" });
      area.right = x2 + totalWidth;
      area.setWidthRightAnchored(valueWidth * scale);
    } else if (LiteGraph.truncateWidgetValuesFirst) {
      const cappedLabelWidth = Math.min(labelWidth, totalWidth);
      area.width = cappedLabelWidth;
      drawTextInArea({ ctx, text: displayName, area, align: "left" });
      area.right = x2 + totalWidth;
      area.setWidthRightAnchored(
        Math.max(totalWidth - gap - cappedLabelWidth, 0)
      );
    } else {
      const cappedValueWidth = Math.min(valueWidth, totalWidth);
      area.width = Math.max(totalWidth - gap - cappedValueWidth, 0);
      drawTextInArea({ ctx, text: displayName, area, align: "left" });
      area.right = x2 + totalWidth;
      area.setWidthRightAnchored(cappedValueWidth);
    }
    ctx.fillStyle = this.text_color;
    drawTextInArea({ ctx, text: _displayValue, area, align: "right" });
  }
  /**
   * Sets the value of the widget
   * @param value The value to set
   * @param options The options for setting the value
   */
  setValue(value, { e: e2, node: node2, canvas: canvas2 }) {
    const oldValue = this.value;
    if (value === this.value) return;
    const v2 = this.type === "number" ? Number(value) : value;
    this.value = v2;
    if (this.options?.property && node2.properties[this.options.property] !== void 0) {
      node2.setProperty(this.options.property, v2);
    }
    const pos = canvas2.graph_mouse;
    this.callback?.(this.value, canvas2, node2, pos, e2);
    node2.onWidgetChanged?.(this.name ?? "", v2, oldValue, this);
    if (node2.graph) node2.graph._version++;
  }
  /**
   * Clones the widget.
   * @param node The node that will own the cloned widget.
   * @returns A new widget with the same properties as the original
   * @remarks Subclasses with custom constructors must override this method.
   *
   * Correctly and safely typing this is currently not possible (practical?) in TypeScript 5.8.
   */
  createCopyForNode(node2) {
    const cloned = new this.constructor(this, node2);
    cloned.value = this.value;
    return cloned;
  }
}
class AssetWidget extends BaseWidget {
  static {
    __name(this, "AssetWidget");
  }
  constructor(widget, node2) {
    super(widget, node2);
    this.type ??= "asset";
    this.value = widget.value?.toString() ?? "";
  }
  set value(value) {
    const oldValue = this.value;
    super.value = value;
    if (oldValue !== value && this.node.graph?.list_of_graphcanvas) {
      for (const canvas2 of this.node.graph.list_of_graphcanvas) {
        canvas2.setDirty(true);
      }
    }
  }
  get value() {
    return super.value;
  }
  get _displayValue() {
    return String(this.value);
  }
  drawWidget(ctx, { width: width2, showText = true }) {
    const { fillStyle, strokeStyle, textAlign } = ctx;
    this.drawWidgetShape(ctx, { width: width2, showText });
    if (showText) {
      this.drawTruncatingText({ ctx, width: width2, leftPadding: 0, rightPadding: 0 });
    }
    Object.assign(ctx, { textAlign, strokeStyle, fillStyle });
  }
  onClick() {
    this.callback?.(this.value);
  }
}
let BooleanWidget$1 = class BooleanWidget2 extends BaseWidget {
  static {
    __name(this, "BooleanWidget");
  }
  type = "toggle";
  drawWidget(ctx, { width: width2, showText = true }) {
    const { height, y } = this;
    const { margin } = BaseWidget;
    this.drawWidgetShape(ctx, { width: width2, showText });
    ctx.fillStyle = this.value ? "#89A" : "#333";
    ctx.beginPath();
    ctx.arc(width2 - margin * 2, y + height * 0.5, height * 0.36, 0, Math.PI * 2);
    ctx.fill();
    if (showText) {
      this.drawLabel(ctx, margin * 2);
      this.drawValue(ctx, width2 - 40);
    }
  }
  drawLabel(ctx, x2) {
    ctx.fillStyle = this.secondary_text_color;
    const { displayName } = this;
    if (displayName) ctx.fillText(displayName, x2, this.labelBaseline);
  }
  drawValue(ctx, x2) {
    ctx.fillStyle = this.value ? this.text_color : this.secondary_text_color;
    ctx.textAlign = "right";
    const value = this.value ? this.options.on || "true" : this.options.off || "false";
    ctx.fillText(value, x2, this.labelBaseline);
  }
  onClick(options) {
    this.setValue(!this.value, options);
  }
};
class ButtonWidget extends BaseWidget {
  static {
    __name(this, "ButtonWidget");
  }
  type = "button";
  clicked;
  constructor(widget, node2) {
    super(widget, node2);
    this.clicked ??= false;
  }
  /**
   * Draws the widget
   * @param ctx The canvas context
   * @param options The options for drawing the widget
   */
  drawWidget(ctx, { width: width2, showText = true }) {
    const { fillStyle, strokeStyle, textAlign } = ctx;
    const { height, y } = this;
    const { margin } = BaseWidget;
    ctx.fillStyle = this.background_color;
    if (this.clicked) {
      ctx.fillStyle = "#AAA";
      this.clicked = false;
    }
    ctx.fillRect(margin, y, width2 - margin * 2, height);
    if (showText && !this.computedDisabled) {
      ctx.strokeStyle = this.outline_color;
      ctx.strokeRect(margin, y, width2 - margin * 2, height);
    }
    if (showText) this.drawLabel(ctx, width2 * 0.5);
    Object.assign(ctx, { textAlign, strokeStyle, fillStyle });
  }
  drawLabel(ctx, x2) {
    ctx.textAlign = "center";
    ctx.fillStyle = this.text_color;
    ctx.fillText(this.displayName, x2, this.y + this.height * 0.7);
  }
  onClick({ e: e2, node: node2, canvas: canvas2 }) {
    const pos = canvas2.graph_mouse;
    this.clicked = true;
    canvas2.setDirty(true);
    this.callback?.(this, canvas2, node2, pos, e2);
  }
}
class ChartWidget extends BaseWidget {
  static {
    __name(this, "ChartWidget");
  }
  type = "chart";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "Chart: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
class ColorWidget extends BaseWidget {
  static {
    __name(this, "ColorWidget");
  }
  type = "color";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "Color: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
class BaseSteppedWidget extends BaseWidget {
  static {
    __name(this, "BaseSteppedWidget");
  }
  /**
   * Draw the arrow buttons for the widget
   * @param ctx The canvas rendering context
   * @param width The width of the widget
   */
  drawArrowButtons(ctx, width2) {
    const { height, text_color, disabledTextColor, y } = this;
    const { arrowMargin, arrowWidth, margin } = BaseWidget;
    const arrowTipX = margin + arrowMargin;
    const arrowInnerX = arrowTipX + arrowWidth;
    ctx.fillStyle = this.canDecrement() ? text_color : disabledTextColor;
    ctx.beginPath();
    ctx.moveTo(arrowInnerX, y + 5);
    ctx.lineTo(arrowTipX, y + height * 0.5);
    ctx.lineTo(arrowInnerX, y + height - 5);
    ctx.fill();
    ctx.fillStyle = this.canIncrement() ? text_color : disabledTextColor;
    ctx.beginPath();
    ctx.moveTo(width2 - arrowInnerX, y + 5);
    ctx.lineTo(width2 - arrowTipX, y + height * 0.5);
    ctx.lineTo(width2 - arrowInnerX, y + height - 5);
    ctx.fill();
  }
  drawWidget(ctx, options) {
    const { fillStyle, strokeStyle, textAlign } = ctx;
    this.drawWidgetShape(ctx, options);
    if (options.showText) {
      if (!this.computedDisabled) this.drawArrowButtons(ctx, options.width);
      this.drawTruncatingText({ ctx, width: options.width });
    }
    Object.assign(ctx, { textAlign, strokeStyle, fillStyle });
  }
}
function toArray(values) {
  return Array.isArray(values) ? values : Object.keys(values);
}
__name(toArray, "toArray");
class ComboWidget extends BaseSteppedWidget {
  static {
    __name(this, "ComboWidget");
  }
  type = "combo";
  get _displayValue() {
    if (this.computedDisabled) return "";
    if (this.options.getOptionLabel) {
      try {
        return this.options.getOptionLabel(
          this.value ? String(this.value) : null
        );
      } catch (e2) {
        console.error("Failed to map value:", e2);
        return this.value ? String(this.value) : "";
      }
    }
    const { values: rawValues } = this.options;
    if (rawValues) {
      const values = typeof rawValues === "function" ? rawValues() : rawValues;
      if (values && !Array.isArray(values)) {
        return values[this.value];
      }
    }
    return typeof this.value === "number" ? String(this.value) : this.value;
  }
  getValues(node2) {
    const { values } = this.options;
    if (values == null) throw new Error("[ComboWidget]: values is required");
    return typeof values === "function" ? values(this, node2) : values;
  }
  /**
   * Checks if the value is {@link Array.at at} the given index in the combo list.
   * @param increment `true` if checking the use of the increment button, `false` for decrement
   * @returns `true` if the value is at the given index, otherwise `false`.
   */
  canUseButton(increment) {
    const { values } = this.options;
    if (typeof values === "function") return false;
    const valuesArray = toArray(values);
    if (!(valuesArray.length > 1)) return false;
    const firstValue = valuesArray.at(0);
    const lastValue = valuesArray.at(-1);
    if (firstValue === lastValue) return true;
    return this.value !== (increment ? lastValue : firstValue);
  }
  /**
   * Returns `true` if the current value is not the last value in the list.
   * Handles edge case where the value is both the first and last item in the list.
   */
  canIncrement() {
    return this.canUseButton(true);
  }
  canDecrement() {
    return this.canUseButton(false);
  }
  incrementValue(options) {
    this.tryChangeValue(1, options);
  }
  decrementValue(options) {
    this.tryChangeValue(-1, options);
  }
  tryChangeValue(delta2, options) {
    const values = this.getValues(options.node);
    const indexedValues = toArray(values);
    options.canvas.last_mouseclick = 0;
    const foundIndex = typeof values === "object" ? indexedValues.indexOf(String(this.value)) + delta2 : (
      // @ts-expect-error handle non-string values
      indexedValues.indexOf(this.value) + delta2
    );
    const index2 = clamp(foundIndex, 0, indexedValues.length - 1);
    const value = Array.isArray(values) ? values[index2] : index2;
    this.setValue(value, options);
  }
  onClick({ e: e2, node: node2, canvas: canvas2 }) {
    const x2 = e2.canvasX - node2.pos[0];
    const width2 = this.width || node2.size[0];
    if (typeof this.options.values === "function") {
      warnDeprecated(
        "Using a function for values is deprecated. Use an array of unique values instead."
      );
    }
    if (x2 < 40) return this.decrementValue({ e: e2, node: node2, canvas: canvas2 });
    if (x2 > width2 - 40) return this.incrementValue({ e: e2, node: node2, canvas: canvas2 });
    const values = this.getValues(node2);
    const values_list = toArray(values);
    if (this.options.getOptionLabel) {
      const menuOptions = {
        scale: Math.max(1, canvas2.ds.scale),
        event: e2,
        className: "dark",
        callback: /* @__PURE__ */ __name((value) => {
          this.setValue(value, { e: e2, node: node2, canvas: canvas2 });
        }, "callback")
      };
      const menu2 = new LiteGraph.ContextMenu([], menuOptions);
      for (const value of values_list) {
        try {
          const label = this.options.getOptionLabel(String(value));
          menu2.addItem(label, value, menuOptions);
        } catch (err) {
          console.error("Failed to map value:", err);
          menu2.addItem(String(value), value, menuOptions);
        }
      }
      return;
    }
    const text_values = values != values_list ? Object.values(values) : values;
    new LiteGraph.ContextMenu(text_values, {
      scale: Math.max(1, canvas2.ds.scale),
      event: e2,
      className: "dark",
      callback: /* @__PURE__ */ __name((value) => {
        this.setValue(
          values != values_list ? text_values.indexOf(value) : value,
          { e: e2, node: node2, canvas: canvas2 }
        );
      }, "callback")
    });
  }
}
class FileUploadWidget extends BaseWidget {
  static {
    __name(this, "FileUploadWidget");
  }
  type = "fileupload";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "Fileupload: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
class GalleriaWidget extends BaseWidget {
  static {
    __name(this, "GalleriaWidget");
  }
  type = "galleria";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "Galleria: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
class ImageCompareWidget extends BaseWidget {
  static {
    __name(this, "ImageCompareWidget");
  }
  type = "imagecompare";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "ImageCompare: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
function getWidgetStep(options) {
  return options.step2 || (options.step || 10) * 0.1;
}
__name(getWidgetStep, "getWidgetStep");
class KnobWidget extends BaseWidget {
  static {
    __name(this, "KnobWidget");
  }
  type = "knob";
  /**
   * Compute the layout size of the widget.
   * @returns The layout size of the widget.
   */
  computeLayoutSize() {
    return {
      minHeight: 60,
      minWidth: 20,
      maxHeight: 1e6,
      maxWidth: 1e6
    };
  }
  get height() {
    return this.computedHeight || super.height;
  }
  drawWidget(ctx, { width: width2, showText = true }) {
    const { fillStyle, strokeStyle, textAlign } = ctx;
    const { y } = this;
    const { margin } = BaseWidget;
    const { gradient_stops = "rgb(14, 182, 201); rgb(0, 216, 72)" } = this.options;
    const effective_height = this.computedHeight || this.height;
    const size_modifier = Math.min(this.computedHeight || this.height, this.width || 20) / 20;
    const arc_center = { x: width2 / 2, y: effective_height / 2 + y };
    ctx.lineWidth = (Math.min(width2, effective_height) - margin * size_modifier) / 6;
    const arc_size = (Math.min(width2, effective_height) - margin * size_modifier - ctx.lineWidth) / 2;
    {
      const gradient2 = ctx.createRadialGradient(
        arc_center.x,
        arc_center.y,
        arc_size + ctx.lineWidth,
        0,
        0,
        arc_size + ctx.lineWidth
      );
      gradient2.addColorStop(0, "rgb(29, 29, 29)");
      gradient2.addColorStop(1, "rgb(116, 116, 116)");
      ctx.fillStyle = gradient2;
    }
    ctx.beginPath();
    {
      ctx.arc(
        arc_center.x,
        arc_center.y,
        arc_size + ctx.lineWidth / 2,
        0,
        Math.PI * 2,
        false
      );
      ctx.fill();
      ctx.closePath();
    }
    const arc2 = {
      start_angle: Math.PI * 0.6,
      end_angle: Math.PI * 2.4
    };
    ctx.beginPath();
    {
      const gradient2 = ctx.createRadialGradient(
        arc_center.x,
        arc_center.y,
        arc_size + ctx.lineWidth,
        0,
        0,
        arc_size + ctx.lineWidth
      );
      gradient2.addColorStop(0, "rgb(99, 99, 99)");
      gradient2.addColorStop(1, "rgb(36, 36, 36)");
      ctx.strokeStyle = gradient2;
    }
    ctx.arc(
      arc_center.x,
      arc_center.y,
      arc_size,
      arc2.start_angle,
      arc2.end_angle,
      false
    );
    ctx.stroke();
    ctx.closePath();
    const range = this.options.max - this.options.min;
    let nvalue = (this.value - this.options.min) / range;
    nvalue = clamp(nvalue, 0, 1);
    ctx.beginPath();
    const gradient = ctx.createConicGradient(
      arc2.start_angle,
      arc_center.x,
      arc_center.y
    );
    const gs = gradient_stops.split(";");
    for (const [index2, stop] of gs.entries()) {
      gradient.addColorStop(index2, stop.trim());
    }
    ctx.strokeStyle = gradient;
    const value_end_angle = (arc2.end_angle - arc2.start_angle) * nvalue + arc2.start_angle;
    ctx.arc(
      arc_center.x,
      arc_center.y,
      arc_size,
      arc2.start_angle,
      value_end_angle,
      false
    );
    ctx.stroke();
    ctx.closePath();
    if (showText && !this.computedDisabled) {
      ctx.strokeStyle = this.outline_color;
      ctx.beginPath();
      ctx.strokeStyle = this.outline_color;
      ctx.arc(
        arc_center.x,
        arc_center.y,
        arc_size + ctx.lineWidth / 2,
        0,
        Math.PI * 2,
        false
      );
      ctx.lineWidth = 1;
      ctx.stroke();
      ctx.closePath();
    }
    if (showText) {
      ctx.textAlign = "center";
      ctx.fillStyle = this.text_color;
      const fixedValue = Number(this.value).toFixed(this.options.precision ?? 3);
      ctx.fillText(
        `${this.label || this.name}
${fixedValue}`,
        width2 * 0.5,
        y + effective_height * 0.5
      );
    }
    Object.assign(ctx, { textAlign, strokeStyle, fillStyle });
  }
  onClick() {
    this.current_drag_offset = 0;
  }
  current_drag_offset = 0;
  onDrag(options) {
    if (this.options.read_only) return;
    const { e: e2 } = options;
    const step = getWidgetStep(this.options);
    const range = this.options.max - this.options.min;
    const range_10_percent = range / 10;
    const range_1_percent = range / 100;
    const step_for = {
      shift: range_10_percent > step ? range_10_percent - range_10_percent % step : step,
      delta_y: range_1_percent > step ? range_1_percent - range_1_percent % step : step
      // 1% increments
    };
    const use_y = Math.abs(e2.movementY) > Math.abs(e2.movementX);
    const delta2 = use_y ? -e2.movementY : e2.movementX;
    const drag_threshold = 15;
    this.current_drag_offset += delta2;
    let adjustment = 0;
    if (this.current_drag_offset > drag_threshold) {
      adjustment += 1;
      this.current_drag_offset -= drag_threshold;
    } else if (this.current_drag_offset < -drag_threshold) {
      adjustment -= 1;
      this.current_drag_offset += drag_threshold;
    }
    const step_with_shift_modifier = e2.shiftKey ? step_for.shift : use_y ? step_for.delta_y : step;
    const deltaValue = adjustment * step_with_shift_modifier;
    const newValue2 = clamp(
      this.value + deltaValue,
      this.options.min,
      this.options.max
    );
    if (newValue2 !== this.value) {
      this.setValue(newValue2, options);
    }
  }
}
class LegacyWidget extends BaseWidget {
  static {
    __name(this, "LegacyWidget");
  }
  drawWidget(ctx, options) {
    const H = LiteGraph.NODE_WIDGET_HEIGHT;
    this.draw?.(ctx, this.node, options.width, this.y, H, !!options.showText);
  }
  onClick() {
    console.warn(
      "Custom widget wrapper onClick was just called. Handling for third party widgets is done via LGraphCanvas - the mouse callback."
    );
  }
}
class MarkdownWidget extends BaseWidget {
  static {
    __name(this, "MarkdownWidget");
  }
  type = "markdown";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "Markdown: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
class MultiSelectWidget extends BaseWidget {
  static {
    __name(this, "MultiSelectWidget");
  }
  type = "multiselect";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "MultiSelect: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
class NumberWidget extends BaseSteppedWidget {
  static {
    __name(this, "NumberWidget");
  }
  type = "number";
  get _displayValue() {
    if (this.computedDisabled) return "";
    return Number(this.value).toFixed(
      this.options.precision !== void 0 ? this.options.precision : 3
    );
  }
  canIncrement() {
    const { max } = this.options;
    return max == null || this.value < max;
  }
  canDecrement() {
    const { min } = this.options;
    return min == null || this.value > min;
  }
  incrementValue(options) {
    this.setValue(this.value + getWidgetStep(this.options), options);
  }
  decrementValue(options) {
    this.setValue(this.value - getWidgetStep(this.options), options);
  }
  setValue(value, options) {
    let newValue2 = value;
    if (this.options.min != null && newValue2 < this.options.min) {
      newValue2 = this.options.min;
    }
    if (this.options.max != null && newValue2 > this.options.max) {
      newValue2 = this.options.max;
    }
    super.setValue(newValue2, options);
  }
  onClick({ e, node, canvas }) {
    const x = e.canvasX - node.pos[0];
    const width = this.width || node.size[0];
    const delta = x < 40 ? -1 : x > width - 40 ? 1 : 0;
    if (delta) {
      this.setValue(this.value + delta * getWidgetStep(this.options), {
        e,
        node,
        canvas
      });
      return;
    }
    canvas.prompt(
      "Value",
      this.value,
      (v) => {
        if (/^[\d\s()*+/-]+|\d+\.\d+$/.test(v)) {
          try {
            v = eval(v);
          } catch {
          }
        }
        const newValue = Number(v);
        if (!isNaN(newValue)) {
          this.setValue(newValue, { e, node, canvas });
        }
      },
      e
    );
  }
  /**
   * Handles drag events for the number widget
   * @param options The options for handling the drag event
   */
  onDrag({ e: e2, node: node2, canvas: canvas2 }) {
    const width2 = this.width || node2.width;
    const x2 = e2.canvasX - node2.pos[0];
    const delta2 = x2 < 40 ? -1 : x2 > width2 - 40 ? 1 : 0;
    if (delta2 && x2 > -3 && x2 < width2 + 3) return;
    this.setValue(this.value + (e2.deltaX ?? 0) * getWidgetStep(this.options), {
      e: e2,
      node: node2,
      canvas: canvas2
    });
  }
}
class SelectButtonWidget extends BaseWidget {
  static {
    __name(this, "SelectButtonWidget");
  }
  type = "selectbutton";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "SelectButton: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
class SliderWidget extends BaseWidget {
  static {
    __name(this, "SliderWidget");
  }
  type = "slider";
  marker;
  /**
   * Draws the widget
   * @param ctx The canvas context
   * @param options The options for drawing the widget
   */
  drawWidget(ctx, { width: width2, showText = true }) {
    const { fillStyle, strokeStyle, textAlign } = ctx;
    const { height, y } = this;
    const { margin } = BaseWidget;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(margin, y, width2 - margin * 2, height);
    const range = this.options.max - this.options.min;
    let nvalue = (this.value - this.options.min) / range;
    nvalue = clamp(nvalue, 0, 1);
    ctx.fillStyle = this.options.slider_color ?? "#678";
    ctx.fillRect(margin, y, nvalue * (width2 - margin * 2), height);
    if (showText && !this.computedDisabled) {
      ctx.strokeStyle = this.outline_color;
      ctx.strokeRect(margin, y, width2 - margin * 2, height);
    }
    if (this.marker != null) {
      let marker_nvalue = (this.marker - this.options.min) / range;
      marker_nvalue = clamp(marker_nvalue, 0, 1);
      ctx.fillStyle = this.options.marker_color ?? "#AA9";
      ctx.fillRect(margin + marker_nvalue * (width2 - margin * 2), y, 2, height);
    }
    if (showText) {
      ctx.textAlign = "center";
      ctx.fillStyle = this.text_color;
      const fixedValue = Number(this.value).toFixed(this.options.precision ?? 3);
      ctx.fillText(
        `${this.label || this.name}  ${fixedValue}`,
        width2 * 0.5,
        y + height * 0.7
      );
    }
    Object.assign(ctx, { textAlign, strokeStyle, fillStyle });
  }
  /**
   * Handles click events for the slider widget
   */
  onClick(options) {
    if (this.options.read_only) return;
    const { e: e2, node: node2 } = options;
    const width2 = this.width || node2.size[0];
    const x2 = e2.canvasX - node2.pos[0];
    const slideFactor = clamp((x2 - 15) / (width2 - 30), 0, 1);
    const newValue2 = this.options.min + (this.options.max - this.options.min) * slideFactor;
    if (newValue2 !== this.value) {
      this.setValue(newValue2, options);
    }
  }
  /**
   * Handles drag events for the slider widget
   */
  onDrag(options) {
    if (this.options.read_only) return false;
    const { e: e2, node: node2 } = options;
    const width2 = this.width || node2.size[0];
    const x2 = e2.canvasX - node2.pos[0];
    const slideFactor = clamp((x2 - 15) / (width2 - 30), 0, 1);
    const newValue2 = this.options.min + (this.options.max - this.options.min) * slideFactor;
    if (newValue2 !== this.value) {
      this.setValue(newValue2, options);
    }
  }
}
class TextWidget extends BaseWidget {
  static {
    __name(this, "TextWidget");
  }
  constructor(widget, node2) {
    super(widget, node2);
    this.type ??= "string";
    this.value = widget.value?.toString() ?? "";
  }
  /**
   * Draws the widget
   * @param ctx The canvas context
   * @param options The options for drawing the widget
   */
  drawWidget(ctx, { width: width2, showText = true }) {
    const { fillStyle, strokeStyle, textAlign } = ctx;
    this.drawWidgetShape(ctx, { width: width2, showText });
    if (showText) {
      this.drawTruncatingText({ ctx, width: width2, leftPadding: 0, rightPadding: 0 });
    }
    Object.assign(ctx, { textAlign, strokeStyle, fillStyle });
  }
  onClick({ e: e2, node: node2, canvas: canvas2 }) {
    canvas2.prompt(
      "Value",
      this.value,
      (v2) => {
        if (v2 !== null) {
          this.setValue(v2, { e: e2, node: node2, canvas: canvas2 });
        }
      },
      e2,
      this.options?.multiline ?? false
    );
  }
}
class TextareaWidget extends BaseWidget {
  static {
    __name(this, "TextareaWidget");
  }
  type = "textarea";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "Textarea: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
class TreeSelectWidget extends BaseWidget {
  static {
    __name(this, "TreeSelectWidget");
  }
  type = "treeselect";
  drawWidget(ctx, options) {
    const { width: width2 } = options;
    const { y, height } = this;
    const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx;
    ctx.fillStyle = this.background_color;
    ctx.fillRect(15, y, width2 - 30, height);
    ctx.strokeStyle = this.outline_color;
    ctx.strokeRect(15, y, width2 - 30, height);
    ctx.fillStyle = this.text_color;
    ctx.font = "11px monospace";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    const text = "TreeSelect: Vue-only";
    ctx.fillText(text, width2 / 2, y + height / 2);
    Object.assign(ctx, {
      fillStyle,
      strokeStyle,
      textAlign,
      textBaseline,
      font
    });
  }
  onClick(_options) {
  }
}
function toConcreteWidget(widget, node2, wrapLegacyWidgets = true) {
  if (widget instanceof BaseWidget) return widget;
  const narrowedWidget = widget;
  switch (narrowedWidget.type) {
    case "button":
      return toClass(ButtonWidget, narrowedWidget, node2);
    case "toggle":
      return toClass(BooleanWidget$1, narrowedWidget, node2);
    case "slider":
      return toClass(SliderWidget, narrowedWidget, node2);
    case "knob":
      return toClass(KnobWidget, narrowedWidget, node2);
    case "combo":
      return toClass(ComboWidget, narrowedWidget, node2);
    case "number":
      return toClass(NumberWidget, narrowedWidget, node2);
    case "string":
      return toClass(TextWidget, narrowedWidget, node2);
    case "text":
      return toClass(TextWidget, narrowedWidget, node2);
    case "fileupload":
      return toClass(FileUploadWidget, narrowedWidget, node2);
    case "color":
      return toClass(ColorWidget, narrowedWidget, node2);
    case "markdown":
      return toClass(MarkdownWidget, narrowedWidget, node2);
    case "treeselect":
      return toClass(TreeSelectWidget, narrowedWidget, node2);
    case "multiselect":
      return toClass(MultiSelectWidget, narrowedWidget, node2);
    case "chart":
      return toClass(ChartWidget, narrowedWidget, node2);
    case "galleria":
      return toClass(GalleriaWidget, narrowedWidget, node2);
    case "imagecompare":
      return toClass(ImageCompareWidget, narrowedWidget, node2);
    case "selectbutton":
      return toClass(SelectButtonWidget, narrowedWidget, node2);
    case "textarea":
      return toClass(TextareaWidget, narrowedWidget, node2);
    case "asset":
      return toClass(AssetWidget, narrowedWidget, node2);
    default: {
      if (wrapLegacyWidgets) return toClass(LegacyWidget, widget, node2);
    }
  }
}
__name(toConcreteWidget, "toConcreteWidget");
function isComboWidget(widget) {
  return widget.type === "combo";
}
__name(isComboWidget, "isComboWidget");
class LGraphNode {
  static {
    __name(this, "LGraphNode");
  }
  // Static properties used by dynamic child classes
  static title;
  static MAX_CONSOLE;
  static type;
  static category;
  static description;
  static filter;
  static skip_list;
  static resizeHandleSize = 15;
  static resizeEdgeSize = 5;
  /** Default setting for {@link LGraphNode.connectInputToOutput}. @see {@link INodeFlags.keepAllLinksOnBypass} */
  static keepAllLinksOnBypass = false;
  /** The title text of the node. */
  title;
  /**
   * The font style used to render the node's title text.
   */
  get titleFontStyle() {
    return `${LiteGraph.NODE_TEXT_SIZE}px ${LiteGraph.NODE_FONT}`;
  }
  get innerFontStyle() {
    return `normal ${LiteGraph.NODE_SUBTEXT_SIZE}px ${LiteGraph.NODE_FONT}`;
  }
  get displayType() {
    return this.type;
  }
  graph = null;
  id;
  type = "";
  inputs = [];
  outputs = [];
  #concreteInputs = [];
  #concreteOutputs = [];
  properties = {};
  properties_info = [];
  flags = {};
  widgets;
  /** Property manager for this node */
  changeTracker;
  /**
   * The amount of space available for widgets to grow into.
   * @see {@link layoutWidgets}
   */
  freeWidgetSpace;
  locked;
  /** Execution order, automatically computed during run @see {@link LGraph.computeExecutionOrder} */
  order = 0;
  mode = LGraphEventMode.ALWAYS;
  last_serialization;
  serialize_widgets;
  /**
   * The overridden fg color used to render the node.
   * @see {@link renderingColor}
   */
  color;
  /**
   * The overridden bg color used to render the node.
   * @see {@link renderingBgColor}
   */
  bgcolor;
  /**
   * The overridden box color used to render the node.
   * @see {@link renderingBoxColor}
   */
  boxcolor;
  /** The fg color used to render the node. */
  get renderingColor() {
    const baseColor = this.color || this.constructor.color || LiteGraph.NODE_DEFAULT_COLOR;
    return adjustColor(baseColor, { lightness: LiteGraph.nodeLightness });
  }
  /** The bg color used to render the node. */
  get renderingBgColor() {
    const baseBgColor = this.bgcolor || this.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;
    const adjustments = {
      opacity: LiteGraph.nodeOpacity,
      lightness: LiteGraph.nodeLightness
    };
    return adjustColor(
      this.mode === LGraphEventMode.BYPASS ? LiteGraph.NODE_DEFAULT_BYPASS_COLOR : baseBgColor,
      adjustments
    );
  }
  /** The box color used to render the node. */
  get renderingBoxColor() {
    if (this.boxcolor) return this.boxcolor;
    if (LiteGraph.node_box_coloured_when_on) {
      if (this.action_triggered) return "#FFF";
      if (this.execute_triggered) return "#AAA";
    }
    if (LiteGraph.node_box_coloured_by_mode) {
      const modeColour = LiteGraph.NODE_MODES_COLORS[this.mode ?? LGraphEventMode.ALWAYS];
      if (modeColour) return modeColour;
    }
    return LiteGraph.NODE_DEFAULT_BOXCOLOR;
  }
  /** @inheritdoc {@link IColorable.setColorOption} */
  setColorOption(colorOption) {
    if (colorOption == null) {
      this.color = void 0;
      this.bgcolor = void 0;
    } else {
      this.color = colorOption.color;
      this.bgcolor = colorOption.bgcolor;
    }
  }
  /** @inheritdoc {@link IColorable.getColorOption} */
  getColorOption() {
    return Object.values(LGraphCanvas.node_colors).find(
      (colorOption) => colorOption.color === this.color && colorOption.bgcolor === this.bgcolor
    ) ?? null;
  }
  /**
   * The stroke styles that should be applied to the node.
   */
  strokeStyles;
  /**
   * The progress of node execution. Used to render a progress bar. Value between 0 and 1.
   */
  progress;
  exec_version;
  action_call;
  execute_triggered;
  action_triggered;
  widgets_up;
  widgets_start_y;
  lostFocusAt;
  gotFocusAt;
  badges = [];
  title_buttons = [];
  badgePosition = BadgePosition.TopLeft;
  /**
   * The width of the node when collapsed.
   * Updated by {@link LGraphCanvas.drawNode}
   */
  _collapsed_width;
  console;
  _level;
  _shape;
  mouseOver;
  redraw_on_mouse;
  resizable;
  clonable;
  _relative_id;
  clip_area;
  ignore_remove;
  has_errors;
  removable;
  block_delete;
  selected;
  showAdvanced;
  isSubgraphNode() {
    return false;
  }
  /** @inheritdoc {@link renderArea} */
  #renderArea = new Rectangle();
  /**
   * Rect describing the node area, including shadows and any protrusions.
   * Determines if the node is visible.  Calculated once at the start of every frame.
   */
  get renderArea() {
    return this.#renderArea;
  }
  /** @inheritdoc {@link boundingRect} */
  #boundingRect = new Rectangle();
  /**
   * Cached node position & area as `x, y, width, height`.  Includes changes made by {@link onBounding}, if present.
   *
   * Determines the node hitbox and other rendering effects.  Calculated once at the start of every frame.
   */
  get boundingRect() {
    return this.#boundingRect;
  }
  /** The offset from {@link pos} to the top-left of {@link boundingRect}. */
  get boundingOffset() {
    const {
      pos: [posX, posY],
      boundingRect: [bX, bY]
    } = this;
    return [posX - bX, posY - bY];
  }
  /** {@link pos} and {@link size} values are backed by this {@link Rectangle}. */
  _posSize = new Rectangle();
  _pos = this._posSize.pos;
  _size = this._posSize.size;
  get pos() {
    return this._pos;
  }
  /** Node position does not necessarily correlate to the top-left corner. */
  set pos(value) {
    if (!value || value.length < 2) return;
    this._pos[0] = value[0];
    this._pos[1] = value[1];
  }
  get size() {
    return this._size;
  }
  set size(value) {
    if (!value || value.length < 2) return;
    this._size[0] = value[0];
    this._size[1] = value[1];
  }
  /**
   * The size of the node used for rendering.
   */
  get renderingSize() {
    return this.flags.collapsed ? [this._collapsed_width ?? 0, 0] : this._size;
  }
  get shape() {
    return this._shape;
  }
  set shape(v2) {
    const oldValue = this._shape;
    switch (v2) {
      case "default":
        this._shape = void 0;
        break;
      case "box":
        this._shape = RenderShape.BOX;
        break;
      case "round":
        this._shape = RenderShape.ROUND;
        break;
      case "circle":
        this._shape = RenderShape.CIRCLE;
        break;
      case "card":
        this._shape = RenderShape.CARD;
        break;
      default:
        this._shape = v2;
    }
    if (oldValue !== this._shape) {
      this.graph?.trigger("node:property:changed", {
        nodeId: this.id,
        property: "shape",
        oldValue,
        newValue: this._shape
      });
    }
  }
  /**
   * The shape of the node used for rendering. @see {@link RenderShape}
   */
  get renderingShape() {
    return this._shape || this.constructor.shape || LiteGraph.NODE_DEFAULT_SHAPE;
  }
  get is_selected() {
    return this.selected;
  }
  set is_selected(value) {
    this.selected = value;
  }
  get title_mode() {
    return this.constructor.title_mode ?? TitleMode.NORMAL_TITLE;
  }
  #getErrorStrokeStyle() {
    if (this.has_errors) {
      return {
        padding: 12,
        lineWidth: 10,
        color: LiteGraph.NODE_ERROR_COLOUR
      };
    }
  }
  #getSelectedStrokeStyle() {
    if (this.selected) {
      return {
        padding: this.has_errors ? 20 : void 0
      };
    }
  }
  constructor(title, type) {
    this.id = LiteGraph.use_uuids ? LiteGraph.uuidv4() : -1;
    this.title = title || "Unnamed";
    this.type = type ?? "";
    this.size = [LiteGraph.NODE_WIDTH, 60];
    this.pos = [10, 10];
    this.strokeStyles = {
      error: this.#getErrorStrokeStyle,
      selected: this.#getSelectedStrokeStyle
    };
    this.changeTracker = new LGraphNodeProperties(this);
  }
  /**
   * configure a node from an object containing the serialized info
   */
  configure(info) {
    if (this.graph) {
      this.graph._version++;
    }
    for (const j in info) {
      if (j == "properties") {
        for (const k in info.properties) {
          this.properties[k] = info.properties[k];
          this.onPropertyChanged?.(k, info.properties[k]);
        }
        continue;
      }
      if (info[j] == null) {
        continue;
      } else if (typeof info[j] == "object") {
        if (this[j]?.configure) {
          this[j]?.configure(info[j]);
        } else {
          this[j] = LiteGraph.cloneObject(info[j], this[j]);
        }
      } else {
        this[j] = info[j];
      }
    }
    if (!info.title) {
      this.title = this.constructor.title;
    }
    this.inputs ??= [];
    this.inputs = this.inputs.map(
      (input) => toClass(NodeInputSlot, input, this)
    );
    for (const [i, input] of this.inputs.entries()) {
      const link = this.graph && input.link != null ? this.graph._links.get(input.link) : null;
      this.onConnectionsChange?.(NodeSlotType.INPUT, i, true, link, input);
      this.onInputAdded?.(input);
    }
    this.outputs ??= [];
    this.outputs = this.outputs.map(
      (output) => toClass(NodeOutputSlot, output, this)
    );
    for (const [i, output] of this.outputs.entries()) {
      if (!output.links) continue;
      for (const linkId of output.links) {
        const link = this.graph ? this.graph._links.get(linkId) : null;
        this.onConnectionsChange?.(NodeSlotType.OUTPUT, i, true, link, output);
      }
      this.onOutputAdded?.(output);
    }
    this._internalConfigureAfterSlots?.();
    if (this.widgets) {
      for (const w of this.widgets) {
        if (!w) continue;
        const input = this.inputs.find((i) => i.widget?.name === w.name);
        if (input?.label) w.label = input.label;
        if (w.options?.property && this.properties[w.options.property] != void 0)
          w.value = JSON.parse(
            JSON.stringify(this.properties[w.options.property])
          );
      }
      if (info.widgets_values) {
        let i = 0;
        for (const widget of this.widgets ?? []) {
          if (widget.serialize === false) continue;
          if (i >= info.widgets_values.length) break;
          widget.value = info.widgets_values[i++];
        }
      }
    }
    if (this.pinned) this.resizable = false;
    this.onConfigure?.(info);
  }
  /**
   * serialize the content
   */
  serialize() {
    const o = {
      id: this.id,
      type: this.type,
      pos: [this.pos[0], this.pos[1]],
      size: [this.size[0], this.size[1]],
      flags: LiteGraph.cloneObject(this.flags),
      order: this.order,
      mode: this.mode,
      showAdvanced: this.showAdvanced
    };
    if (this.constructor === LGraphNode && this.last_serialization)
      return { ...this.last_serialization, mode: o.mode, pos: o.pos };
    if (this.inputs)
      o.inputs = this.inputs.map((input) => inputAsSerialisable(input));
    if (this.outputs)
      o.outputs = this.outputs.map((output) => outputAsSerialisable(output));
    if (this.title && this.title != this.constructor.title) o.title = this.title;
    if (this.properties) o.properties = LiteGraph.cloneObject(this.properties);
    const { widgets: widgets2 } = this;
    if (widgets2 && this.serialize_widgets) {
      o.widgets_values = [];
      for (const [i, widget] of widgets2.entries()) {
        if (widget.serialize === false) continue;
        o.widgets_values[i] = widget ? widget.value : null;
      }
    }
    if (!o.type && this.constructor.type) o.type = this.constructor.type;
    if (this.color) o.color = this.color;
    if (this.bgcolor) o.bgcolor = this.bgcolor;
    if (this.boxcolor) o.boxcolor = this.boxcolor;
    if (this.shape) o.shape = this.shape;
    if (this.onSerialize?.(o))
      console.warn(
        "node onSerialize shouldn't return anything, data should be stored in the object pass in the first parameter"
      );
    return o;
  }
  /* Creates a clone of this node */
  clone() {
    if (this.type == null) return null;
    const node2 = LiteGraph.createNode(this.type);
    if (!node2) return null;
    const data = LiteGraph.cloneObject(this.serialize());
    const { inputs, outputs } = data;
    if (inputs) {
      for (const input of inputs) {
        input.link = null;
      }
    }
    if (outputs) {
      for (const { links } of outputs) {
        if (links) links.length = 0;
      }
    }
    data.id = void 0;
    if (LiteGraph.use_uuids) data.id = LiteGraph.uuidv4();
    node2.configure(data);
    return node2;
  }
  /**
   * serialize and stringify
   */
  toString() {
    return JSON.stringify(this.serialize());
  }
  /**
   * get the title string
   */
  getTitle() {
    return this.title || this.constructor.title;
  }
  /**
   * sets the value of a property
   * @param name
   * @param value
   */
  setProperty(name2, value) {
    this.properties ||= {};
    if (value === this.properties[name2]) return;
    const prev_value = this.properties[name2];
    this.properties[name2] = value;
    if (this.onPropertyChanged?.(name2, value, prev_value) === false)
      this.properties[name2] = prev_value;
    if (this.widgets) {
      for (const w of this.widgets) {
        if (!w) continue;
        if (w.options.property == name2) {
          w.value = value;
          break;
        }
      }
    }
  }
  /**
   * sets the output data
   * @param slot
   * @param data
   */
  setOutputData(slot, data) {
    const { outputs } = this;
    if (!outputs) return;
    if (slot == -1 || slot >= outputs.length) return;
    const output_info = outputs[slot];
    if (!output_info) return;
    output_info._data = data;
    if (!this.graph) throw new NullGraphError();
    const { links } = outputs[slot];
    if (links) {
      for (const id2 of links) {
        const link = this.graph._links.get(id2);
        if (link) link.data = data;
      }
    }
  }
  /**
   * sets the output data type, useful when you want to be able to overwrite the data type
   */
  setOutputDataType(slot, type) {
    const { outputs } = this;
    if (!outputs || slot == -1 || slot >= outputs.length) return;
    const output_info = outputs[slot];
    if (!output_info) return;
    output_info.type = type;
    if (!this.graph) throw new NullGraphError();
    const { links } = outputs[slot];
    if (links) {
      for (const id2 of links) {
        const link = this.graph._links.get(id2);
        if (link) link.type = type;
      }
    }
  }
  /**
   * Retrieves the input data (data traveling through the connection) from one slot
   * @param slot
   * @param force_update if set to true it will force the connected node of this slot to output data into this link
   * @returns data or if it is not connected returns undefined
   */
  getInputData(slot, force_update) {
    if (!this.inputs) return;
    if (slot >= this.inputs.length || this.inputs[slot].link == null) return;
    if (!this.graph) throw new NullGraphError();
    const link_id = this.inputs[slot].link;
    const link = this.graph._links.get(link_id);
    if (!link) return null;
    if (!force_update) return link.data;
    const node2 = this.graph.getNodeById(link.origin_id);
    if (!node2) return link.data;
    if (node2.updateOutputData) {
      node2.updateOutputData(link.origin_slot);
    } else {
      node2.onExecute?.();
    }
    return link.data;
  }
  /**
   * Retrieves the input data type (in case this supports multiple input types)
   * @param slot
   * @returns datatype in string format
   */
  getInputDataType(slot) {
    if (!this.inputs) return null;
    if (slot >= this.inputs.length || this.inputs[slot].link == null)
      return null;
    if (!this.graph) throw new NullGraphError();
    const link_id = this.inputs[slot].link;
    const link = this.graph._links.get(link_id);
    if (!link) return null;
    const node2 = this.graph.getNodeById(link.origin_id);
    if (!node2) return link.type;
    const output_info = node2.outputs[link.origin_slot];
    return output_info ? output_info.type : null;
  }
  /**
   * Retrieves the input data from one slot using its name instead of slot number
   * @param slot_name
   * @param force_update if set to true it will force the connected node of this slot to output data into this link
   * @returns data or if it is not connected returns null
   */
  getInputDataByName(slot_name, force_update) {
    const slot = this.findInputSlot(slot_name);
    return slot == -1 ? null : this.getInputData(slot, force_update);
  }
  /**
   * tells you if there is a connection in one input slot
   * @param slot The 0-based index of the input to check
   * @returns `true` if the input slot has a link ID (does not perform validation)
   */
  isInputConnected(slot) {
    if (!this.inputs) return false;
    return slot < this.inputs.length && this.inputs[slot].link != null;
  }
  /**
   * tells you info about an input connection (which node, type, etc)
   * @returns object or null { link: id, name: string, type: string or 0 }
   */
  getInputInfo(slot) {
    return !this.inputs || !(slot < this.inputs.length) ? null : this.inputs[slot];
  }
  /**
   * Returns the link info in the connection of an input slot
   * @returns object or null
   */
  getInputLink(slot) {
    if (!this.inputs) return null;
    if (slot < this.inputs.length) {
      if (!this.graph) throw new NullGraphError();
      const input = this.inputs[slot];
      if (input.link != null) {
        return this.graph._links.get(input.link) ?? null;
      }
    }
    return null;
  }
  /**
   * returns the node connected in the input slot
   * @returns node or null
   */
  getInputNode(slot) {
    if (!this.inputs) return null;
    if (slot >= this.inputs.length) return null;
    const input = this.inputs[slot];
    if (!input || input.link === null) return null;
    if (!this.graph) throw new NullGraphError();
    const link_info = this.graph._links.get(input.link);
    if (!link_info) return null;
    return this.graph.getNodeById(link_info.origin_id);
  }
  /**
   * returns the value of an input with this name, otherwise checks if there is a property with that name
   * @returns value
   */
  getInputOrProperty(name2) {
    const { inputs } = this;
    if (!inputs?.length) {
      return this.properties ? this.properties[name2] : null;
    }
    if (!this.graph) throw new NullGraphError();
    for (const input of inputs) {
      if (name2 == input.name && input.link != null) {
        const link = this.graph._links.get(input.link);
        if (link) return link.data;
      }
    }
    return this.properties[name2];
  }
  /**
   * tells you the last output data that went in that slot
   * @returns object or null
   */
  getOutputData(slot) {
    if (!this.outputs) return null;
    if (slot >= this.outputs.length) return null;
    const info = this.outputs[slot];
    return info._data;
  }
  /**
   * tells you info about an output connection (which node, type, etc)
   * @returns object or null { name: string, type: string, links: [ ids of links in number ] }
   */
  getOutputInfo(slot) {
    return !this.outputs || !(slot < this.outputs.length) ? null : this.outputs[slot];
  }
  /**
   * tells you if there is a connection in one output slot
   */
  isOutputConnected(slot) {
    if (!this.outputs) return false;
    return slot < this.outputs.length && Number(this.outputs[slot].links?.length) > 0;
  }
  /**
   * tells you if there is any connection in the output slots
   */
  isAnyOutputConnected() {
    const { outputs } = this;
    if (!outputs) return false;
    for (const output of outputs) {
      if (output.links?.length) return true;
    }
    return false;
  }
  /**
   * retrieves all the nodes connected to this output slot
   */
  getOutputNodes(slot) {
    const { outputs } = this;
    if (!outputs || outputs.length == 0) return null;
    if (slot >= outputs.length) return null;
    const { links } = outputs[slot];
    if (!links || links.length == 0) return null;
    if (!this.graph) throw new NullGraphError();
    const r = [];
    for (const id2 of links) {
      const link = this.graph._links.get(id2);
      if (link) {
        const target_node = this.graph.getNodeById(link.target_id);
        if (target_node) {
          r.push(target_node);
        }
      }
    }
    return r;
  }
  addOnTriggerInput() {
    const trigS = this.findInputSlot("onTrigger");
    if (trigS == -1) {
      this.addInput("onTrigger", LiteGraph.EVENT, {
        nameLocked: true
      });
      return this.findInputSlot("onTrigger");
    }
    return trigS;
  }
  addOnExecutedOutput() {
    const trigS = this.findOutputSlot("onExecuted");
    if (trigS == -1) {
      this.addOutput("onExecuted", LiteGraph.ACTION, {
        nameLocked: true
      });
      return this.findOutputSlot("onExecuted");
    }
    return trigS;
  }
  onAfterExecuteNode(param, options) {
    const trigS = this.findOutputSlot("onExecuted");
    if (trigS != -1) {
      this.triggerSlot(trigS, param, null, options);
    }
  }
  changeMode(modeTo) {
    switch (modeTo) {
      case LGraphEventMode.ON_EVENT:
        break;
      case LGraphEventMode.ON_TRIGGER:
        this.addOnTriggerInput();
        this.addOnExecutedOutput();
        break;
      case LGraphEventMode.NEVER:
        break;
      case LGraphEventMode.ALWAYS:
        break;
      // @ts-expect-error Not impl.
      case LiteGraph.ON_REQUEST:
        break;
      default:
        return false;
    }
    this.mode = modeTo;
    return true;
  }
  /**
   * Triggers the node code execution, place a boolean/counter to mark the node as being executed
   */
  doExecute(param, options) {
    options = options || {};
    if (this.onExecute) {
      options.action_call ||= `${this.id}_exec_${Math.floor(Math.random() * 9999)}`;
      if (!this.graph) throw new NullGraphError();
      this.graph.nodes_executing[this.id] = true;
      this.onExecute(param, options);
      this.graph.nodes_executing[this.id] = false;
      this.exec_version = this.graph.iteration;
      if (options?.action_call) {
        this.action_call = options.action_call;
        this.graph.nodes_executedAction[this.id] = options.action_call;
      }
    }
    this.execute_triggered = 2;
    this.onAfterExecuteNode?.(param, options);
  }
  /**
   * Triggers an action, wrapped by logics to control execution flow
   * @param action name
   */
  actionDo(action, param, options) {
    options = options || {};
    if (this.onAction) {
      options.action_call ||= `${this.id}_${action || "action"}_${Math.floor(Math.random() * 9999)}`;
      if (!this.graph) throw new NullGraphError();
      this.graph.nodes_actioning[this.id] = action || "actioning";
      this.onAction(action, param, options);
      this.graph.nodes_actioning[this.id] = false;
      if (options?.action_call) {
        this.action_call = options.action_call;
        this.graph.nodes_executedAction[this.id] = options.action_call;
      }
    }
    this.action_triggered = 2;
    this.onAfterExecuteNode?.(param, options);
  }
  /**
   * Triggers an event in this node, this will trigger any output with the same name
   * @param action name ( "on_play", ... ) if action is equivalent to false then the event is send to all
   */
  trigger(action, param, options) {
    const { outputs } = this;
    if (!outputs || !outputs.length) {
      return;
    }
    if (this.graph) this.graph._last_trigger_time = LiteGraph.getTime();
    for (const [i, output] of outputs.entries()) {
      if (!output || output.type !== LiteGraph.EVENT || action && output.name != action) {
        continue;
      }
      this.triggerSlot(i, param, null, options);
    }
  }
  /**
   * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes
   * @param slot the index of the output slot
   * @param link_id [optional] in case you want to trigger and specific output link in a slot
   */
  triggerSlot(slot, param, link_id, options) {
    options = options || {};
    if (!this.outputs) return;
    if (slot == null) {
      console.error("slot must be a number");
      return;
    }
    if (typeof slot !== "number")
      console.warn(
        "slot must be a number, use node.trigger('name') if you want to use a string"
      );
    const output = this.outputs[slot];
    if (!output) return;
    const links = output.links;
    if (!links || !links.length) return;
    if (!this.graph) throw new NullGraphError();
    this.graph._last_trigger_time = LiteGraph.getTime();
    for (const id2 of links) {
      if (link_id != null && link_id != id2) continue;
      const link_info = this.graph._links.get(id2);
      if (!link_info) continue;
      link_info._last_time = LiteGraph.getTime();
      const node2 = this.graph.getNodeById(link_info.target_id);
      if (!node2) continue;
      if (node2.mode === LGraphEventMode.ON_TRIGGER) {
        if (!options.action_call)
          options.action_call = `${this.id}_trigg_${Math.floor(Math.random() * 9999)}`;
        node2.doExecute?.(param, options);
      } else if (node2.onAction) {
        if (!options.action_call)
          options.action_call = `${this.id}_act_${Math.floor(Math.random() * 9999)}`;
        const target_connection = node2.inputs[link_info.target_slot];
        node2.actionDo(target_connection.name, param, options);
      }
    }
  }
  /**
   * clears the trigger slot animation
   * @param slot the index of the output slot
   * @param link_id [optional] in case you want to trigger and specific output link in a slot
   */
  clearTriggeredSlot(slot, link_id) {
    if (!this.outputs) return;
    const output = this.outputs[slot];
    if (!output) return;
    const links = output.links;
    if (!links || !links.length) return;
    if (!this.graph) throw new NullGraphError();
    for (const id2 of links) {
      if (link_id != null && link_id != id2) continue;
      const link_info = this.graph._links.get(id2);
      if (!link_info) continue;
      link_info._last_time = 0;
    }
  }
  /**
   * changes node size and triggers callback
   */
  setSize(size) {
    this.size = size;
    this.onResize?.(this.size);
  }
  /**
   * Expands the node size to fit its content.
   */
  expandToFitContent() {
    const newSize = this.computeSize();
    this.setSize([
      Math.max(this.size[0], newSize[0]),
      Math.max(this.size[1], newSize[1])
    ]);
  }
  /**
   * add a new property to this node
   * @param type string defining the output type ("vec3","number",...)
   * @param extra_info this can be used to have special properties of the property (like values, etc)
   */
  addProperty(name2, default_value, type, extra_info) {
    const o = { name: name2, type, default_value };
    if (extra_info) Object.assign(o, extra_info);
    this.properties_info ||= [];
    this.properties_info.push(o);
    this.properties ||= {};
    this.properties[name2] = default_value;
    return o;
  }
  /**
   * add a new output slot to use in this node
   * @param type string defining the output type ("vec3","number",...)
   * @param extra_info this can be used to have special properties of an output (label, special color, position, etc)
   */
  addOutput(name2, type, extra_info) {
    const output = Object.assign(
      new NodeOutputSlot({ name: name2, type, links: null }, this),
      extra_info
    );
    this.outputs ||= [];
    this.outputs.push(output);
    this.onOutputAdded?.(output);
    if (LiteGraph.auto_load_slot_types)
      LiteGraph.registerNodeAndSlotType(this, type, true);
    this.expandToFitContent();
    this.setDirtyCanvas(true, true);
    return output;
  }
  /**
   * remove an existing output slot
   */
  removeOutput(slot) {
    if (this.graph) {
      this.disconnectOutput(slot);
    }
    const { outputs } = this;
    outputs.splice(slot, 1);
    for (let i = slot; i < outputs.length; ++i) {
      const output = outputs[i];
      if (!output || !output.links) continue;
      if (this.graph) {
        for (const linkId of output.links) {
          const link = this.graph._links.get(linkId);
          if (link) link.origin_slot--;
        }
      }
    }
    this.onOutputRemoved?.(slot);
    this.setDirtyCanvas(true, true);
  }
  /**
   * add a new input slot to use in this node
   * @param type string defining the input type ("vec3","number",...), it its a generic one use 0
   * @param extra_info this can be used to have special properties of an input (label, color, position, etc)
   */
  addInput(name2, type, extra_info) {
    type ||= 0;
    const input = Object.assign(
      new NodeInputSlot({ name: name2, type, link: null }, this),
      extra_info
    );
    this.inputs ||= [];
    this.inputs.push(input);
    this.expandToFitContent();
    this.onInputAdded?.(input);
    LiteGraph.registerNodeAndSlotType(this, type);
    this.setDirtyCanvas(true, true);
    return input;
  }
  /**
   * remove an existing input slot
   */
  removeInput(slot) {
    if (this.graph) {
      this.disconnectInput(slot, true);
    }
    const { inputs } = this;
    const slot_info = inputs.splice(slot, 1);
    for (let i = slot; i < inputs.length; ++i) {
      const input = inputs[i];
      if (!input?.link) continue;
      if (this.graph) {
        const link = this.graph._links.get(input.link);
        if (link) link.target_slot--;
      }
    }
    this.onInputRemoved?.(slot, slot_info[0]);
    this.setDirtyCanvas(true, true);
  }
  /**
   * computes the minimum size of a node according to its inputs and output slots
   * @returns the total size
   */
  computeSize(out) {
    const ctorSize = this.constructor.size;
    if (ctorSize) return [ctorSize[0], ctorSize[1]];
    const { inputs, outputs, widgets: widgets2 } = this;
    let rows = Math.max(
      inputs ? inputs.filter((input) => !isWidgetInputSlot(input)).length : 1,
      outputs ? outputs.length : 1
    );
    const size = out ?? [0, 0];
    rows = Math.max(rows, 1);
    const font_size = LiteGraph.NODE_TEXT_SIZE;
    const padLeft = LiteGraph.NODE_TITLE_HEIGHT;
    const padRight = padLeft * 0.33;
    const title_width = padLeft + compute_text_size(this.title, this.titleFontStyle) + padRight;
    let input_width = 0;
    let widgetWidth = 0;
    let output_width = 0;
    if (inputs) {
      for (const input of inputs) {
        const text = input.label || input.localized_name || input.name || "";
        const text_width = compute_text_size(text, this.innerFontStyle);
        if (isWidgetInputSlot(input)) {
          const widget = this.getWidgetFromSlot(input);
          if (widget && !this.isWidgetVisible(widget)) continue;
          if (text_width > widgetWidth) widgetWidth = text_width;
        } else {
          if (text_width > input_width) input_width = text_width;
        }
      }
    }
    if (outputs) {
      for (const output of outputs) {
        const text = output.label || output.localized_name || output.name || "";
        const text_width = compute_text_size(text, this.innerFontStyle);
        if (output_width < text_width) output_width = text_width;
      }
    }
    const minWidth = LiteGraph.NODE_WIDTH * (widgets2?.length ? 1.5 : 1);
    const centrePadding = input_width && output_width ? 5 : 0;
    const slotsWidth = input_width + output_width + 2 * LiteGraph.NODE_SLOT_HEIGHT + centrePadding;
    const widgetMargin = BaseWidget.margin + BaseWidget.arrowMargin + BaseWidget.arrowWidth;
    const widgetPadding = BaseWidget.minValueWidth + 2 * widgetMargin;
    if (widgetWidth) widgetWidth += widgetPadding;
    size[0] = Math.max(slotsWidth, widgetWidth, title_width, minWidth);
    size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT;
    let widgets_height = 0;
    if (widgets2?.length) {
      for (const widget of widgets2) {
        if (!this.isWidgetVisible(widget)) continue;
        let widget_height = 0;
        if (widget.computeSize) {
          widget_height += widget.computeSize(size[0])[1];
        } else if (widget.computeLayoutSize) {
          const { minHeight, minWidth: minWidth2 } = widget.computeLayoutSize(this);
          const widgetWidth2 = minWidth2 + widgetPadding;
          if (widgetWidth2 > size[0]) size[0] = widgetWidth2;
          widget_height += minHeight;
        } else {
          widget_height += LiteGraph.NODE_WIDGET_HEIGHT;
        }
        widgets_height += widget_height + 4;
      }
      widgets_height += 8;
    }
    if (this.widgets_up) size[1] = Math.max(size[1], widgets_height);
    else if (this.widgets_start_y != null)
      size[1] = Math.max(size[1], widgets_height + this.widgets_start_y);
    else size[1] += widgets_height;
    function compute_text_size(text, fontStyle) {
      return LGraphCanvas._measureText?.(text, fontStyle) ?? font_size * (text?.length ?? 0) * 0.6;
    }
    __name(compute_text_size, "compute_text_size");
    if (this.constructor.min_height && size[1] < this.constructor.min_height) {
      size[1] = this.constructor.min_height;
    }
    size[1] += 6;
    return size;
  }
  inResizeCorner(canvasX, canvasY) {
    const rows = this.outputs ? this.outputs.length : 1;
    const outputs_offset = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT;
    return isInRectangle(
      canvasX,
      canvasY,
      this.pos[0] + this.size[0] - 15,
      this.pos[1] + Math.max(this.size[1] - 15, outputs_offset),
      20,
      20
    );
  }
  /**
   * Returns which resize corner the point is over, if any.
   * @param canvasX X position in canvas coordinates
   * @param canvasY Y position in canvas coordinates
   * @returns The compass corner the point is in, otherwise `undefined`.
   */
  findResizeDirection(canvasX, canvasY) {
    if (this.resizable === false) return;
    const { boundingRect } = this;
    if (!boundingRect.containsXy(canvasX, canvasY)) return;
    return boundingRect.findContainingCorner(
      canvasX,
      canvasY,
      LGraphNode.resizeHandleSize
    );
  }
  /**
   * returns all the info available about a property of this node.
   * @param property name of the property
   * @returns the object with all the available info
   */
  getPropertyInfo(property) {
    let info = null;
    const { properties_info } = this;
    if (properties_info) {
      for (const propInfo of properties_info) {
        if (propInfo.name == property) {
          info = propInfo;
          break;
        }
      }
    }
    if (this.constructor[`@${property}`])
      info = this.constructor[`@${property}`];
    if (this.constructor.widgets_info?.[property])
      info = this.constructor.widgets_info[property];
    if (!info && this.onGetPropertyInfo) {
      info = this.onGetPropertyInfo(property);
    }
    info ||= {};
    info.type ||= typeof this.properties[property];
    if (info.widget == "combo") info.type = "enum";
    return info;
  }
  /**
   * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties
   * @param type the widget type
   * @param name the text to show on the widget
   * @param value the default value
   * @param callback function to call when it changes (optionally, it can be the name of the property to modify)
   * @param options the object that contains special properties of this widget
   * @returns the created widget object
   */
  addWidget(type, name2, value, callback, options) {
    this.widgets ||= [];
    if (!options && callback && typeof callback === "object") {
      options = callback;
      callback = null;
    }
    options ||= {};
    if (typeof options === "string") options = { property: options };
    if (callback && typeof callback === "string") {
      options.property = callback;
      callback = null;
    }
    const w = {
      // @ts-expect-error - Type casting for widget type property
      type: type.toLowerCase(),
      name: name2,
      value,
      callback: typeof callback !== "function" ? void 0 : callback,
      options,
      y: 0
    };
    if (w.options.y !== void 0) {
      w.y = w.options.y;
    }
    if (!callback && !w.options.callback && !w.options.property) {
      console.warn(
        "LiteGraph addWidget(...) without a callback or property assigned"
      );
    }
    if (type == "combo" && !w.options.values) {
      throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }";
    }
    const widget = this.addCustomWidget(w);
    this.expandToFitContent();
    return widget;
  }
  addCustomWidget(custom_widget) {
    this.widgets ||= [];
    const widget = toConcreteWidget(custom_widget, this, false) ?? custom_widget;
    this.widgets.push(widget);
    return widget;
  }
  addTitleButton(options) {
    this.title_buttons ||= [];
    const button = new LGraphButton(options);
    this.title_buttons.push(button);
    return button;
  }
  onTitleButtonClick(button, canvas2) {
    canvas2.dispatch("litegraph:node-title-button-clicked", {
      node: this,
      button
    });
  }
  removeWidgetByName(name2) {
    const widget = this.widgets?.find((x2) => x2.name === name2);
    if (widget) this.removeWidget(widget);
  }
  removeWidget(widget) {
    if (!this.widgets)
      throw new Error("removeWidget called on node without widgets");
    const widgetIndex = this.widgets.indexOf(widget);
    if (widgetIndex === -1) throw new Error("Widget not found on this node");
    if (this.inputs) {
      for (const input of this.inputs) {
        if (input._widget === widget) {
          input._widget = void 0;
          input.widget = void 0;
        }
      }
    }
    widget.onRemove?.();
    this.widgets.splice(widgetIndex, 1);
  }
  ensureWidgetRemoved(widget) {
    try {
      this.removeWidget(widget);
    } catch (error) {
      console.error("Failed to remove widget", error);
    }
  }
  move(deltaX, deltaY) {
    if (this.pinned) return;
    if (LiteGraph.vueNodesMode) {
      return;
    }
    this.pos[0] += deltaX;
    this.pos[1] += deltaY;
  }
  /**
   * Internal method to measure the node for rendering.  Prefer {@link boundingRect} where possible.
   *
   * Populates {@link out} with the results in graph space.
   * Populates {@link _collapsed_width} with the collapsed width if the node is collapsed.
   * Adjusts for title and collapsed status, but does not call {@link onBounding}.
   * @param out `x, y, width, height` are written to this array.
   * @param ctx The canvas context to use for measuring text.
   */
  measure(out, ctx) {
    const titleMode = this.title_mode;
    const renderTitle = titleMode != TitleMode.TRANSPARENT_TITLE && titleMode != TitleMode.NO_TITLE;
    const titleHeight = renderTitle ? LiteGraph.NODE_TITLE_HEIGHT : 0;
    out[0] = this.pos[0];
    out[1] = this.pos[1] + -titleHeight;
    if (!this.flags?.collapsed) {
      out[2] = this.size[0];
      out[3] = this.size[1] + titleHeight;
    } else {
      if (ctx) ctx.font = this.innerFontStyle;
      this._collapsed_width = Math.min(
        this.size[0],
        ctx ? ctx.measureText(this.getTitle() ?? "").width + LiteGraph.NODE_TITLE_HEIGHT * 2 : 0
      );
      out[2] = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;
      out[3] = LiteGraph.NODE_TITLE_HEIGHT;
    }
  }
  /**
   * returns the bounding of the object, used for rendering purposes
   * @param out {Rect?} [optional] a place to store the output, to free garbage
   * @param includeExternal {boolean?} [optional] set to true to
   * include the shadow and connection points in the bounding calculation
   * @returns the bounding box in format of [topleft_cornerx, topleft_cornery, width, height]
   */
  getBounding(out, includeExternal) {
    out ||= [0, 0, 0, 0];
    const rect = includeExternal ? this.renderArea : this.boundingRect;
    out[0] = rect[0];
    out[1] = rect[1];
    out[2] = rect[2];
    out[3] = rect[3];
    return out;
  }
  /**
   * Calculates the render area of this node, populating both {@link boundingRect} and {@link renderArea}.
   * Called automatically at the start of every frame.
   */
  updateArea(ctx) {
    const bounds = this.#boundingRect;
    this.measure(bounds, ctx);
    this.onBounding?.(bounds);
    const renderArea = this.#renderArea;
    renderArea.set(bounds);
    renderArea[0] -= 4;
    renderArea[1] -= 4;
    renderArea[2] += 6 + 4;
    renderArea[3] += 5 + 4;
  }
  /**
   * checks if a point is inside the shape of a node
   */
  isPointInside(x2, y) {
    return isInRect(x2, y, this.boundingRect);
  }
  /**
   * Checks if the provided point is inside this node's collapse button area.
   * @param x X co-ordinate to check
   * @param y Y co-ordinate to check
   * @returns true if the x,y point is in the collapse button area, otherwise false
   */
  isPointInCollapse(x2, y) {
    const squareLength = LiteGraph.NODE_TITLE_HEIGHT;
    return isInRectangle(
      x2,
      y,
      this.pos[0],
      this.pos[1] - squareLength,
      squareLength,
      squareLength
    );
  }
  /**
   * Returns the input slot at the given position. Uses full 20 height, and approximates the label length.
   * @param pos The graph co-ordinates to check
   * @returns The input slot at the given position if found, otherwise `undefined`.
   */
  getInputOnPos(pos) {
    return getNodeInputOnPos(this, pos[0], pos[1])?.input;
  }
  /**
   * Returns the output slot at the given position. Uses full 20x20 box for the slot.
   * @param pos The graph co-ordinates to check
   * @returns The output slot at the given position if found, otherwise `undefined`.
   */
  getOutputOnPos(pos) {
    return getNodeOutputOnPos(this, pos[0], pos[1])?.output;
  }
  /**
   * Returns the input or output slot at the given position.
   *
   * Tries {@link getNodeInputOnPos} first, then {@link getNodeOutputOnPos}.
   * @param pos The graph co-ordinates to check
   * @returns The input or output slot at the given position if found, otherwise `undefined`.
   */
  getSlotOnPos(pos) {
    if (!isPointInRect(pos, this.boundingRect)) return;
    return this.getInputOnPos(pos) ?? this.getOutputOnPos(pos);
  }
  /**
   * @deprecated Use {@link getSlotOnPos} instead.
   * checks if a point is inside a node slot, and returns info about which slot
   * @param x
   * @param y
   * @returns if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }
   */
  getSlotInPosition(x2, y) {
    const { inputs, outputs } = this;
    if (inputs) {
      for (const [i, input] of inputs.entries()) {
        const pos = this.getInputPos(i);
        if (isInRectangle(x2, y, pos[0] - 10, pos[1] - 10, 20, 20)) {
          return { input, slot: i, link_pos: pos };
        }
      }
    }
    if (outputs) {
      for (const [i, output] of outputs.entries()) {
        const pos = this.getOutputPos(i);
        if (isInRectangle(x2, y, pos[0] - 10, pos[1] - 10, 20, 20)) {
          return { output, slot: i, link_pos: pos };
        }
      }
    }
    return null;
  }
  /**
   * Gets the widget on this node at the given co-ordinates.
   * @param canvasX X co-ordinate in graph space
   * @param canvasY Y co-ordinate in graph space
   * @returns The widget found, otherwise `null`
   */
  getWidgetOnPos(canvasX, canvasY, includeDisabled = false) {
    const { widgets: widgets2, pos, size } = this;
    if (!widgets2?.length) return;
    const x2 = canvasX - pos[0];
    const y = canvasY - pos[1];
    const nodeWidth = size[0];
    for (const widget of widgets2) {
      if (widget.computedDisabled && !includeDisabled || !this.isWidgetVisible(widget)) {
        continue;
      }
      const h2 = widget.computedHeight ?? widget.computeSize?.(nodeWidth)[1] ?? LiteGraph.NODE_WIDGET_HEIGHT;
      const maybeDOMWidget = widget;
      const mtop = maybeDOMWidget.margin ?? -2;
      const mbot = maybeDOMWidget.margin ?? 2;
      const mx = maybeDOMWidget.margin ?? 6;
      const w = widget.width || nodeWidth;
      if (widget.last_y !== void 0 && isInRectangle(
        x2,
        y,
        mx,
        widget.last_y + mtop,
        w - 2 * mx,
        h2 - mtop - mbot
      )) {
        return widget;
      }
    }
  }
  findInputSlot(name2, returnObj = false) {
    const { inputs } = this;
    if (!inputs) return -1;
    for (const [i, input] of inputs.entries()) {
      if (name2 == input.name) {
        return !returnObj ? i : input;
      }
    }
    return -1;
  }
  findOutputSlot(name2, returnObj = false) {
    const { outputs } = this;
    if (!outputs) return -1;
    for (const [i, output] of outputs.entries()) {
      if (name2 == output.name) {
        return !returnObj ? i : output;
      }
    }
    return -1;
  }
  findInputSlotFree(optsIn) {
    return this.#findFreeSlot(this.inputs, optsIn);
  }
  findOutputSlotFree(optsIn) {
    return this.#findFreeSlot(this.outputs, optsIn);
  }
  /**
   * Finds the next free slot
   * @param slots The slots to search, i.e. this.inputs or this.outputs
   */
  #findFreeSlot(slots, options) {
    const defaults = {
      returnObj: false,
      typesNotAccepted: []
    };
    const opts = Object.assign(defaults, options || {});
    const length = slots?.length;
    if (!(length > 0)) return -1;
    for (let i = 0; i < length; ++i) {
      const slot = slots[i];
      if (!slot || slot.link || slot.links?.length) continue;
      if (opts.typesNotAccepted?.includes?.(slot.type)) continue;
      return !opts.returnObj ? i : slot;
    }
    return -1;
  }
  findInputSlotByType(type, returnObj, preferFreeSlot, doNotUseOccupied) {
    return this.#findSlotByType(
      this.inputs,
      type,
      returnObj,
      preferFreeSlot,
      doNotUseOccupied
    );
  }
  findOutputSlotByType(type, returnObj, preferFreeSlot, doNotUseOccupied) {
    return this.#findSlotByType(
      this.outputs,
      type,
      returnObj,
      preferFreeSlot,
      doNotUseOccupied
    );
  }
  findSlotByType(input, type, returnObj, preferFreeSlot, doNotUseOccupied) {
    return input ? this.#findSlotByType(
      this.inputs,
      type,
      returnObj,
      preferFreeSlot,
      doNotUseOccupied
    ) : this.#findSlotByType(
      this.outputs,
      type,
      returnObj,
      preferFreeSlot,
      doNotUseOccupied
    );
  }
  /**
   * Finds a matching slot from those provided, returning the slot itself or its index in {@link slots}.
   * @param slots Slots to search (this.inputs or this.outputs)
   * @param type Type of slot to look for
   * @param returnObj If true, returns the slot itself.  Otherwise, the index.
   * @param preferFreeSlot Prefer a free slot, but if none are found, fall back to an occupied slot.
   * @param doNotUseOccupied Do not fall back to occupied slots.
   * @see {findSlotByType}
   * @see {findOutputSlotByType}
   * @see {findInputSlotByType}
   * @returns If a match is found, the slot if returnObj is true, otherwise the index.  If no matches are found, -1
   */
  #findSlotByType(slots, type, returnObj, preferFreeSlot, doNotUseOccupied) {
    const length = slots?.length;
    if (!length) return -1;
    if (type == "" || type == "*") type = 0;
    const sourceTypes = String(type).toLowerCase().split(",");
    let occupiedSlot = null;
    for (let i = 0; i < length; ++i) {
      const slot = slots[i];
      const destTypes = slot.type == "0" || slot.type == "*" ? ["0"] : String(slot.type).toLowerCase().split(",");
      for (const sourceType of sourceTypes) {
        const source = sourceType == "_event_" ? LiteGraph.EVENT : sourceType;
        for (const destType of destTypes) {
          const dest = destType == "_event_" ? LiteGraph.EVENT : destType;
          if (source == dest || source === "*" || dest === "*") {
            if (preferFreeSlot && (slot.links?.length || slot.link != null)) {
              occupiedSlot ??= returnObj ? slot : i;
              continue;
            }
            return returnObj ? slot : i;
          }
        }
      }
    }
    return doNotUseOccupied ? -1 : occupiedSlot ?? -1;
  }
  /**
   * Determines the slot index to connect to when attempting to connect by type.
   * @param findInputs If true, searches for an input.  Otherwise, an output.
   * @param node The node at the other end of the connection.
   * @param slotType The type of slot at the other end of the connection.
   * @param options Search restrictions to adhere to.
   * @see {connectByType}
   * @see {connectByTypeOutput}
   */
  findConnectByTypeSlot(findInputs, node2, slotType, options) {
    if (options && typeof options === "object") {
      if ("firstFreeIfInputGeneralInCase" in options)
        options.wildcardToTyped = !!options.firstFreeIfInputGeneralInCase;
      if ("firstFreeIfOutputGeneralInCase" in options)
        options.wildcardToTyped = !!options.firstFreeIfOutputGeneralInCase;
      if ("generalTypeInCase" in options)
        options.typedToWildcard = !!options.generalTypeInCase;
    }
    const optsDef = {
      createEventInCase: true,
      wildcardToTyped: true,
      typedToWildcard: true
    };
    const opts = Object.assign(optsDef, options);
    if (!this.graph) throw new NullGraphError();
    if (node2 && typeof node2 === "number") {
      const nodeById = this.graph.getNodeById(node2);
      if (!nodeById) return;
      node2 = nodeById;
    }
    const slot = node2.findSlotByType(findInputs, slotType, false, true);
    if (slot >= 0 && slot !== null) return slot;
    if (opts.createEventInCase && slotType == LiteGraph.EVENT) {
      if (findInputs) return -1;
      if (LiteGraph.do_add_triggers_slots) return node2.addOnExecutedOutput();
    }
    if (opts.typedToWildcard) {
      const generalSlot = node2.findSlotByType(findInputs, 0, false, true, true);
      if (generalSlot >= 0) return generalSlot;
    }
    if (opts.wildcardToTyped && (slotType == 0 || slotType == "*" || slotType == "")) {
      const opt = { typesNotAccepted: [LiteGraph.EVENT] };
      const nonEventSlot = findInputs ? node2.findInputSlotFree(opt) : node2.findOutputSlotFree(opt);
      if (nonEventSlot >= 0) return nonEventSlot;
    }
  }
  /**
   * Finds the first free output slot with any of the comma-delimited types in {@link type}.
   *
   * If no slots are free, falls back in order to:
   * - The first free wildcard slot
   * - The first occupied slot
   * - The first occupied wildcard slot
   * @param type The {@link ISlotType type} of slot to find
   * @returns The index and slot if found, otherwise `undefined`.
   */
  findOutputByType(type) {
    return findFreeSlotOfType(
      this.outputs,
      type,
      (output) => !output.links?.length
    );
  }
  /**
   * Finds the first free input slot with any of the comma-delimited types in {@link type}.
   *
   * If no slots are free, falls back in order to:
   * - The first free wildcard slot
   * - The first occupied slot
   * - The first occupied wildcard slot
   * @param type The {@link ISlotType type} of slot to find
   * @returns The index and slot if found, otherwise `undefined`.
   */
  findInputByType(type) {
    return findFreeSlotOfType(
      this.inputs,
      type,
      (input) => input.link == null || !!this.graph?.getLink(input.link)?._dragging
    );
  }
  /**
   * connect this node output to the input of another node BY TYPE
   * @param slot (could be the number of the slot or the string with the name of the slot)
   * @param target_node the target node
   * @param target_slotType the input slot type of the target node
   * @returns the link_info is created, otherwise null
   */
  connectByType(slot, target_node, target_slotType, optsIn) {
    const slotIndex = this.findConnectByTypeSlot(
      true,
      target_node,
      target_slotType,
      optsIn
    );
    if (slotIndex !== void 0)
      return this.connect(slot, target_node, slotIndex, optsIn?.afterRerouteId);
    return null;
  }
  /**
   * connect this node input to the output of another node BY TYPE
   * @param slot (could be the number of the slot or the string with the name of the slot)
   * @param source_node the target node
   * @param source_slotType the output slot type of the target node
   * @returns the link_info is created, otherwise null
   */
  connectByTypeOutput(slot, source_node, source_slotType, optsIn) {
    if (typeof optsIn === "object") {
      if ("firstFreeIfInputGeneralInCase" in optsIn)
        optsIn.wildcardToTyped = !!optsIn.firstFreeIfInputGeneralInCase;
      if ("generalTypeInCase" in optsIn)
        optsIn.typedToWildcard = !!optsIn.generalTypeInCase;
    }
    const slotIndex = this.findConnectByTypeSlot(
      false,
      source_node,
      source_slotType,
      optsIn
    );
    if (slotIndex !== void 0)
      return source_node.connect(slotIndex, this, slot, optsIn?.afterRerouteId);
    console.error(
      "[connectByType]: no way to connect type:",
      source_slotType,
      "to node:",
      source_node
    );
    return null;
  }
  canConnectTo(node2, toSlot, fromSlot) {
    return this.id !== node2.id && LiteGraph.isValidConnection(fromSlot.type, toSlot.type);
  }
  /**
   * Connect an output of this node to an input of another node
   * @param slot (could be the number of the slot or the string with the name of the slot)
   * @param target_node the target node
   * @param target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)
   * @returns the link_info is created, otherwise null
   */
  connect(slot, target_node, target_slot, afterRerouteId) {
    let targetIndex;
    const { graph, outputs } = this;
    if (!graph) {
      console.error(
        "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them."
      );
      return null;
    }
    if (typeof slot === "string") {
      slot = this.findOutputSlot(slot);
      if (slot == -1) {
        if (LiteGraph.debug)
          console.error(`Connect: Error, no slot of name ${slot}`);
        return null;
      }
    } else if (!outputs || slot >= outputs.length) {
      if (LiteGraph.debug)
        console.error("Connect: Error, slot number not found");
      return null;
    }
    if (target_node && typeof target_node === "number") {
      const nodeById = graph.getNodeById(target_node);
      if (!nodeById) throw "target node is null";
      target_node = nodeById;
    }
    if (!target_node) throw "target node is null";
    if (target_node == this) return null;
    if (typeof target_slot === "string") {
      targetIndex = target_node.findInputSlot(target_slot);
      if (targetIndex == -1) {
        if (LiteGraph.debug)
          console.error(`Connect: Error, no slot of name ${targetIndex}`);
        return null;
      }
    } else if (target_slot === LiteGraph.EVENT) {
      if (LiteGraph.do_add_triggers_slots) {
        target_node.changeMode(LGraphEventMode.ON_TRIGGER);
        targetIndex = target_node.findInputSlot("onTrigger");
      } else {
        return null;
      }
    } else if (typeof target_slot === "number") {
      targetIndex = target_slot;
    } else {
      targetIndex = 0;
    }
    if (target_node.onBeforeConnectInput) {
      const requestedIndex = target_node.onBeforeConnectInput(
        targetIndex,
        target_slot
      );
      targetIndex = typeof requestedIndex === "number" ? requestedIndex : null;
    }
    if (targetIndex === null || !target_node.inputs || targetIndex >= target_node.inputs.length) {
      if (LiteGraph.debug)
        console.error("Connect: Error, slot number not found");
      return null;
    }
    const input = target_node.inputs[targetIndex];
    const output = outputs[slot];
    if (!output) return null;
    if (output.links?.length) {
      if (output.type === LiteGraph.EVENT && !LiteGraph.allow_multi_output_for_events) {
        graph.beforeChange();
        this.disconnectOutput(slot, false, { doProcessChange: false });
      }
    }
    const link = this.connectSlots(output, target_node, input, afterRerouteId);
    return link ?? null;
  }
  /**
   * Connect two slots between two nodes
   * @param output The output slot to connect
   * @param inputNode The node that the input slot is on
   * @param input The input slot to connect
   * @param afterRerouteId The reroute ID to use for the link
   * @returns The link that was created, or null if the connection was blocked
   */
  connectSlots(output, inputNode, input, afterRerouteId) {
    const { graph } = this;
    if (!graph) throw new NullGraphError();
    const layoutMutations2 = useLayoutMutations();
    const outputIndex = this.outputs.indexOf(output);
    if (outputIndex === -1) {
      console.warn("connectSlots: output not found");
      return;
    }
    const inputIndex = inputNode.inputs.indexOf(input);
    if (inputIndex === -1) {
      console.warn("connectSlots: input not found");
      return;
    }
    if (!LiteGraph.isValidConnection(output.type, input.type)) {
      this.setDirtyCanvas(false, true);
      return null;
    }
    if (inputNode.onConnectInput?.(
      inputIndex,
      output.type,
      output,
      this,
      outputIndex
    ) === false)
      return null;
    if (this.onConnectOutput?.(
      outputIndex,
      input.type,
      input,
      inputNode,
      inputIndex
    ) === false)
      return null;
    if (inputNode.inputs[inputIndex]?.link != null) {
      graph.beforeChange();
      inputNode.disconnectInput(inputIndex, true);
    }
    const maybeCommonType = input.type && output.type && commonType(input.type, output.type);
    const link = new LLink(
      ++graph.state.lastLinkId,
      maybeCommonType || input.type || output.type,
      this.id,
      outputIndex,
      inputNode.id,
      inputIndex,
      afterRerouteId
    );
    graph._links.set(link.id, link);
    layoutMutations2.setSource(LayoutSource.Canvas);
    layoutMutations2.createLink(
      link.id,
      this.id,
      outputIndex,
      inputNode.id,
      inputIndex
    );
    output.links ??= [];
    output.links.push(link.id);
    const targetInput = inputNode.inputs[inputIndex];
    targetInput.link = link.id;
    if (targetInput.widget) {
      graph.trigger("node:slot-links:changed", {
        nodeId: inputNode.id,
        slotType: NodeSlotType.INPUT,
        slotIndex: inputIndex,
        connected: true,
        linkId: link.id
      });
    }
    const reroutes = LLink.getReroutes(graph, link);
    for (const reroute of reroutes) {
      reroute.linkIds.add(link.id);
      if (reroute.floating) reroute.floating = void 0;
      reroute._dragging = void 0;
    }
    const lastReroute = reroutes.at(-1);
    if (lastReroute) {
      for (const linkId of lastReroute.floatingLinkIds) {
        const link2 = graph.floatingLinks.get(linkId);
        if (link2?.parentId === lastReroute.id) {
          graph.removeFloatingLink(link2);
        }
      }
    }
    graph._version++;
    this.onConnectionsChange?.(
      NodeSlotType.OUTPUT,
      outputIndex,
      true,
      link,
      output
    );
    inputNode.onConnectionsChange?.(
      NodeSlotType.INPUT,
      inputIndex,
      true,
      link,
      input
    );
    this.setDirtyCanvas(false, true);
    graph.afterChange();
    return link;
  }
  connectFloatingReroute(pos, slot, afterRerouteId) {
    const { graph, id: id2 } = this;
    if (!graph) throw new NullGraphError();
    const inputIndex = this.inputs.indexOf(slot);
    const outputIndex = this.outputs.indexOf(slot);
    if (inputIndex === -1 && outputIndex === -1) throw new Error("Invalid slot");
    const slotType = outputIndex === -1 ? "input" : "output";
    const reroute = graph.setReroute({
      pos,
      parentId: afterRerouteId,
      linkIds: [],
      floating: { slotType }
    });
    const parentReroute = graph.getReroute(afterRerouteId);
    const fromLastFloatingReroute = parentReroute?.floating?.slotType === "output";
    if (afterRerouteId == null || !fromLastFloatingReroute) {
      const link2 = new LLink(
        -1,
        slot.type,
        outputIndex === -1 ? -1 : id2,
        outputIndex,
        inputIndex === -1 ? -1 : id2,
        inputIndex
      );
      link2.parentId = reroute.id;
      graph.addFloatingLink(link2);
      return reroute;
    }
    if (!parentReroute)
      throw new Error("[connectFloatingReroute] Parent reroute not found");
    const link = parentReroute.getFloatingLinks("output")?.[0];
    if (!link)
      throw new Error("[connectFloatingReroute] Floating link not found");
    reroute.floatingLinkIds.add(link.id);
    link.parentId = reroute.id;
    parentReroute.floating = void 0;
    return reroute;
  }
  /**
   * disconnect one output to an specific node
   * @param slot (could be the number of the slot or the string with the name of the slot)
   * @param target_node the target node to which this slot is connected [Optional,
   * if not target_node is specified all nodes will be disconnected]
   * @returns if it was disconnected successfully
   */
  disconnectOutput(slot, target_node) {
    if (typeof slot === "string") {
      slot = this.findOutputSlot(slot);
      if (slot == -1) {
        if (LiteGraph.debug)
          console.error(`Connect: Error, no slot of name ${slot}`);
        return false;
      }
    } else if (!this.outputs || slot >= this.outputs.length) {
      if (LiteGraph.debug)
        console.error("Connect: Error, slot number not found");
      return false;
    }
    const output = this.outputs[slot];
    if (!output) return false;
    if (output._floatingLinks) {
      for (const link of output._floatingLinks) {
        if (link.hasOrigin(this.id, slot)) {
          this.graph?.removeFloatingLink(link);
        }
      }
    }
    if (!output.links || output.links.length == 0) return false;
    const { links } = output;
    const graph = this.graph;
    if (!graph) throw new NullGraphError();
    if (target_node) {
      const target = typeof target_node === "number" ? graph.getNodeById(target_node) : target_node;
      if (!target) throw "Target Node not found";
      for (const [i, link_id] of links.entries()) {
        const link_info = graph._links.get(link_id);
        if (link_info?.target_id != target.id) continue;
        links.splice(i, 1);
        const input = target.inputs[link_info.target_slot];
        input.link = null;
        if (input.widget) {
          graph.trigger("node:slot-links:changed", {
            nodeId: target.id,
            slotType: NodeSlotType.INPUT,
            slotIndex: link_info.target_slot,
            connected: false,
            linkId: link_info.id
          });
        }
        link_info.disconnect(graph, "input");
        graph._version++;
        target.onConnectionsChange?.(
          NodeSlotType.INPUT,
          link_info.target_slot,
          false,
          link_info,
          input
        );
        this.onConnectionsChange?.(
          NodeSlotType.OUTPUT,
          slot,
          false,
          link_info,
          output
        );
        break;
      }
    } else {
      for (const link_id of links) {
        const link_info = graph._links.get(link_id);
        if (!link_info) continue;
        if (link_info.target_id === SUBGRAPH_OUTPUT_ID && graph instanceof Subgraph) {
          const targetSlot = graph.outputNode.slots[link_info.target_slot];
          if (targetSlot) {
            targetSlot.linkIds.length = 0;
          } else {
            console.error("Missing subgraphOutput slot when disconnecting link");
          }
        }
        const target = graph.getNodeById(link_info.target_id);
        graph._version++;
        if (target) {
          const input = target.inputs[link_info.target_slot];
          input.link = null;
          if (input.widget) {
            graph.trigger("node:slot-links:changed", {
              nodeId: target.id,
              slotType: NodeSlotType.INPUT,
              slotIndex: link_info.target_slot,
              connected: false,
              linkId: link_info.id
            });
          }
          target.onConnectionsChange?.(
            NodeSlotType.INPUT,
            link_info.target_slot,
            false,
            link_info,
            input
          );
        }
        link_info.disconnect(graph, "input");
        this.onConnectionsChange?.(
          NodeSlotType.OUTPUT,
          slot,
          false,
          link_info,
          output
        );
      }
      output.links = null;
    }
    this.setDirtyCanvas(false, true);
    return true;
  }
  /**
   * Disconnect one input
   * @param slot Input slot index, or the name of the slot
   * @param keepReroutes If `true`, reroutes will not be garbage collected.
   * @returns true if disconnected successfully or already disconnected, otherwise false
   */
  disconnectInput(slot, keepReroutes) {
    if (typeof slot === "string") {
      slot = this.findInputSlot(slot);
      if (slot == -1) {
        if (LiteGraph.debug)
          console.error(`Connect: Error, no slot of name ${slot}`);
        return false;
      }
    } else if (!this.inputs || slot >= this.inputs.length) {
      if (LiteGraph.debug) {
        console.error("Connect: Error, slot number not found");
      }
      return false;
    }
    const input = this.inputs[slot];
    if (!input) {
      console.error("disconnectInput: input not found", slot, this.inputs);
      return false;
    }
    const { graph } = this;
    if (!graph) throw new NullGraphError();
    if (input._floatingLinks?.size) {
      for (const link of input._floatingLinks) {
        graph.removeFloatingLink(link);
      }
    }
    const link_id = this.inputs[slot].link;
    if (link_id != null) {
      this.inputs[slot].link = null;
      if (input.widget) {
        graph.trigger("node:slot-links:changed", {
          nodeId: this.id,
          slotType: NodeSlotType.INPUT,
          slotIndex: slot,
          connected: false,
          linkId: link_id
        });
      }
      const link_info = graph._links.get(link_id);
      if (link_info) {
        if (link_info.origin_id === -10 && "inputNode" in graph) {
          graph.inputNode._disconnectNodeInput(this, input, link_info);
          return true;
        }
        const target_node = graph.getNodeById(link_info.origin_id);
        if (!target_node) {
          console.error(
            "disconnectInput: output not found",
            link_info.origin_slot
          );
          return false;
        }
        const output = target_node.outputs[link_info.origin_slot];
        if (!output?.links?.length) {
          return false;
        }
        let i = 0;
        for (const l = output.links.length; i < l; i++) {
          if (output.links[i] == link_id) {
            output.links.splice(i, 1);
            break;
          }
        }
        link_info.disconnect(graph, keepReroutes ? "output" : void 0);
        if (graph) graph._version++;
        this.onConnectionsChange?.(
          NodeSlotType.INPUT,
          slot,
          false,
          link_info,
          input
        );
        target_node.onConnectionsChange?.(
          NodeSlotType.OUTPUT,
          i,
          false,
          link_info,
          output
        );
      }
    }
    this.setDirtyCanvas(false, true);
    return true;
  }
  /**
   * @deprecated Use {@link getInputPos} or {@link getOutputPos} instead.
   * returns the center of a connection point in canvas coords
   * @param is_input true if if a input slot, false if it is an output
   * @param slot_number (could be the number of the slot or the string with the name of the slot)
   * @param out [optional] a place to store the output, to free garbage
   * @returns the position
   */
  getConnectionPos(is_input, slot_number, out) {
    out ||= [0, 0];
    const {
      pos: [nodeX, nodeY],
      inputs,
      outputs
    } = this;
    if (this.flags.collapsed) {
      const w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;
      out[0] = is_input ? nodeX : nodeX + w;
      out[1] = nodeY - LiteGraph.NODE_TITLE_HEIGHT * 0.5;
      return out;
    }
    if (is_input && slot_number == -1) {
      out[0] = nodeX + LiteGraph.NODE_TITLE_HEIGHT * 0.5;
      out[1] = nodeY + LiteGraph.NODE_TITLE_HEIGHT * 0.5;
      return out;
    }
    const inputPos = inputs?.[slot_number]?.pos;
    const outputPos = outputs?.[slot_number]?.pos;
    if (is_input && inputPos) {
      out[0] = nodeX + inputPos[0];
      out[1] = nodeY + inputPos[1];
      return out;
    } else if (!is_input && outputPos) {
      out[0] = nodeX + outputPos[0];
      out[1] = nodeY + outputPos[1];
      return out;
    }
    const offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5;
    const slotIndex = is_input ? this.#defaultVerticalInputs.indexOf(this.inputs[slot_number]) : this.#defaultVerticalOutputs.indexOf(this.outputs[slot_number]);
    out[0] = is_input ? nodeX + offset : nodeX + this.size[0] + 1 - offset;
    out[1] = nodeY + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0);
    return out;
  }
  /**
   * @internal The inputs that are not positioned with absolute coordinates.
   */
  get #defaultVerticalInputs() {
    return this.inputs.filter(
      (slot) => !slot.pos && !(this.widgets?.length && isWidgetInputSlot(slot))
    );
  }
  /**
   * @internal The outputs that are not positioned with absolute coordinates.
   */
  get #defaultVerticalOutputs() {
    return this.outputs.filter((slot) => !slot.pos);
  }
  /**
   * Get the context needed for slot position calculations
   * @internal
   */
  #getSlotPositionContext() {
    return {
      nodeX: this.pos[0],
      nodeY: this.pos[1],
      nodeWidth: this.size[0],
      nodeHeight: this.size[1],
      collapsed: this.flags.collapsed ?? false,
      collapsedWidth: this._collapsed_width,
      slotStartY: this.constructor.slot_start_y,
      inputs: this.inputs,
      outputs: this.outputs,
      widgets: this.widgets
    };
  }
  /**
   * Gets the position of an input slot, in graph co-ordinates.
   *
   * This method is preferred over the legacy {@link getConnectionPos} method.
   * @param slot Input slot index
   * @returns Position of the input slot
   */
  getInputPos(slot) {
    return calculateInputSlotPos(this.#getSlotPositionContext(), slot);
  }
  /**
   * Gets the position of an input slot, in graph co-ordinates.
   * @param input The actual node input object
   * @returns Position of the centre of the input slot in graph co-ordinates.
   */
  getInputSlotPos(input) {
    return calculateInputSlotPosFromSlot(this.#getSlotPositionContext(), input);
  }
  /**
   * Gets the position of an output slot, in graph co-ordinates.
   *
   * This method is preferred over the legacy {@link getConnectionPos} method.
   * @param outputSlotIndex Output slot index
   * @returns Position of the output slot
   */
  getOutputPos(outputSlotIndex) {
    return calculateOutputSlotPos(
      this.#getSlotPositionContext(),
      outputSlotIndex
    );
  }
  /**
   * Get slot position using layout tree if available, fallback to node's position * Unified implementation used by both LitegraphLinkAdapter and useLinkLayoutSync
   * @param slotIndex The slot index
   * @param isInput Whether this is an input slot
   * @returns Position of the slot center in graph coordinates
   */
  getSlotPosition(slotIndex, isInput) {
    return getSlotPosition(this, slotIndex, isInput);
  }
  /** @inheritdoc */
  snapToGrid(snapTo) {
    return this.pinned ? false : snapPoint(this.pos, snapTo);
  }
  /** @see {@link snapToGrid} */
  alignToGrid() {
    this.snapToGrid(LiteGraph.CANVAS_GRID_SIZE);
  }
  /* Console output */
  trace(msg) {
    this.console ||= [];
    this.console.push(msg);
    if (this.console.length > LGraphNode.MAX_CONSOLE) this.console.shift();
  }
  /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
  setDirtyCanvas(dirty_foreground, dirty_background) {
    this.graph?.canvasAction(
      (c2) => c2.setDirty(dirty_foreground, dirty_background)
    );
  }
  loadImage(url) {
    const img = new Image();
    img.src = LiteGraph.node_images_path + url;
    img.ready = false;
    const dirty = /* @__PURE__ */ __name(() => this.setDirtyCanvas(true), "dirty");
    img.addEventListener("load", function() {
      this.ready = true;
      dirty();
    });
    return img;
  }
  /**
   * Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus
   * @deprecated Use {@link LGraphCanvas.pointer} instead.
   */
  captureInput(v2) {
    warnDeprecated(
      "[DEPRECATED] captureInput will be removed in a future version. Please use LGraphCanvas.pointer (CanvasPointer) instead."
    );
    if (!this.graph || !this.graph.list_of_graphcanvas) return;
    const list = this.graph.list_of_graphcanvas;
    for (const c2 of list) {
      if (!v2 && c2.node_capturing_input != this) continue;
      c2.node_capturing_input = v2 ? this : null;
    }
  }
  get collapsed() {
    return !!this.flags.collapsed;
  }
  get collapsible() {
    return !this.pinned && this.constructor.collapsable !== false;
  }
  /**
   * Toggle node collapse (makes it smaller on the canvas)
   */
  collapse(force) {
    if (!this.collapsible && !force) return;
    if (!this.graph) throw new NullGraphError();
    this.graph._version++;
    this.flags.collapsed = !this.flags.collapsed;
    this.setDirtyCanvas(true, true);
  }
  /**
   * Toggles advanced mode of the node, showing advanced widgets
   */
  toggleAdvanced() {
    if (!this.widgets?.some((w) => w.advanced)) return;
    if (!this.graph) throw new NullGraphError();
    this.graph._version++;
    this.showAdvanced = !this.showAdvanced;
    this.expandToFitContent();
    this.setDirtyCanvas(true, true);
  }
  get pinned() {
    return !!this.flags.pinned;
  }
  /**
   * Prevents the node being accidentally moved or resized by mouse interaction.
   * Toggles pinned state if no value is provided.
   */
  pin(v2) {
    if (!this.graph) throw new NullGraphError();
    this.graph._version++;
    this.flags.pinned = v2 ?? !this.flags.pinned;
    this.resizable = !this.pinned;
    if (!this.pinned) this.flags.pinned = void 0;
  }
  unpin() {
    this.pin(false);
  }
  localToScreen(x2, y, dragAndScale) {
    return [
      (x2 + this.pos[0]) * dragAndScale.scale + dragAndScale.offset[0],
      (y + this.pos[1]) * dragAndScale.scale + dragAndScale.offset[1]
    ];
  }
  get width() {
    return this.collapsed ? this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH : this.size[0];
  }
  /**
   * Returns the height of the node, including the title bar.
   */
  get height() {
    return LiteGraph.NODE_TITLE_HEIGHT + this.bodyHeight;
  }
  /**
   * Returns the height of the node, excluding the title bar.
   */
  get bodyHeight() {
    return this.collapsed ? 0 : this.size[1];
  }
  drawBadges(ctx, { gap = 2 } = {}) {
    const badgeInstances = this.badges.map(
      (badge) => badge instanceof LGraphBadge ? badge : badge()
    );
    const isLeftAligned = this.badgePosition === BadgePosition.TopLeft;
    let currentX = isLeftAligned ? 0 : this.width - badgeInstances.reduce(
      (acc, badge) => acc + badge.getWidth(ctx) + gap,
      0
    );
    const y = -(LiteGraph.NODE_TITLE_HEIGHT + gap);
    for (const badge of badgeInstances) {
      badge.draw(ctx, currentX, y - badge.height);
      currentX += badge.getWidth(ctx) + gap;
    }
  }
  /**
   * Renders the node's title bar background
   */
  drawTitleBarBackground(ctx, {
    scale,
    title_height = LiteGraph.NODE_TITLE_HEIGHT,
    low_quality = false
  }) {
    const fgcolor = this.renderingColor;
    const shape2 = this.renderingShape;
    const size = this.renderingSize;
    if (this.onDrawTitleBar) {
      this.onDrawTitleBar(ctx, title_height, size, scale, fgcolor);
      return;
    }
    if (this.title_mode === TitleMode.TRANSPARENT_TITLE) {
      return;
    }
    if (this.collapsed) {
      ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;
    }
    ctx.fillStyle = this.constructor.title_color || fgcolor;
    ctx.beginPath();
    if (shape2 == RenderShape.BOX || low_quality) {
      ctx.rect(0, -title_height, size[0], title_height);
    } else if (shape2 == RenderShape.ROUND || shape2 == RenderShape.CARD) {
      ctx.roundRect(
        0,
        -title_height,
        size[0],
        title_height,
        this.collapsed ? [LiteGraph.ROUND_RADIUS] : [LiteGraph.ROUND_RADIUS, LiteGraph.ROUND_RADIUS, 0, 0]
      );
    }
    ctx.fill();
    ctx.shadowColor = "transparent";
  }
  /**
   * Renders the node's title box, i.e. the dot in front of the title text that
   * when clicked toggles the node's collapsed state. The term `title box` comes
   * from the original LiteGraph implementation.
   */
  drawTitleBox(ctx, {
    scale,
    low_quality = false,
    title_height = LiteGraph.NODE_TITLE_HEIGHT,
    box_size = 10
  }) {
    const size = this.renderingSize;
    const shape2 = this.renderingShape;
    if (this.onDrawTitleBox) {
      this.onDrawTitleBox(ctx, title_height, size, scale);
      return;
    }
    if ([RenderShape.ROUND, RenderShape.CIRCLE, RenderShape.CARD].includes(shape2)) {
      if (low_quality) {
        ctx.fillStyle = "black";
        ctx.beginPath();
        ctx.arc(
          title_height * 0.5,
          title_height * -0.5,
          box_size * 0.5 + 1,
          0,
          Math.PI * 2
        );
        ctx.fill();
      }
      ctx.fillStyle = this.renderingBoxColor;
      if (low_quality) {
        ctx.fillRect(
          title_height * 0.5 - box_size * 0.5,
          title_height * -0.5 - box_size * 0.5,
          box_size,
          box_size
        );
      } else {
        ctx.beginPath();
        ctx.arc(
          title_height * 0.5,
          title_height * -0.5,
          box_size * 0.5,
          0,
          Math.PI * 2
        );
        ctx.fill();
      }
    } else {
      if (low_quality) {
        ctx.fillStyle = "black";
        ctx.fillRect(
          (title_height - box_size) * 0.5 - 1,
          (title_height + box_size) * -0.5 - 1,
          box_size + 2,
          box_size + 2
        );
      }
      ctx.fillStyle = this.renderingBoxColor;
      ctx.fillRect(
        (title_height - box_size) * 0.5,
        (title_height + box_size) * -0.5,
        box_size,
        box_size
      );
    }
  }
  /**
   * Renders the node's title text.
   */
  drawTitleText(ctx, {
    scale,
    default_title_color,
    low_quality = false,
    title_height = LiteGraph.NODE_TITLE_HEIGHT
  }) {
    const size = this.renderingSize;
    const selected = this.selected;
    if (this.onDrawTitleText) {
      this.onDrawTitleText(
        ctx,
        title_height,
        size,
        scale,
        this.titleFontStyle,
        selected
      );
      return;
    }
    if (low_quality) {
      return;
    }
    ctx.font = this.titleFontStyle;
    const rawTitle = this.getTitle() ?? `❌ ${this.type}`;
    const title = String(rawTitle) + (this.pinned ? "📌" : "");
    if (title) {
      if (selected) {
        ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR;
      } else {
        ctx.fillStyle = this.constructor.title_text_color || default_title_color;
      }
      let availableWidth = size[0] - title_height * 2;
      if (this.title_buttons?.length > 0) {
        let buttonsWidth = 0;
        const savedFont = ctx.font;
        for (const button of this.title_buttons) {
          if (button.visible) {
            buttonsWidth += button.getWidth(ctx) + 2;
          }
        }
        ctx.font = savedFont;
        if (buttonsWidth > 0) {
          buttonsWidth -= 20;
          availableWidth -= buttonsWidth;
        }
      }
      let displayTitle = title;
      if (this.collapsed) {
        displayTitle = title.substr(0, 20);
      } else if (availableWidth > 0) {
        displayTitle = truncateText(ctx, title, availableWidth);
      }
      ctx.textAlign = "left";
      ctx.fillText(
        displayTitle,
        title_height,
        LiteGraph.NODE_TITLE_TEXT_Y - title_height
      );
    }
  }
  /**
   * Attempts to gracefully bypass this node in all of its connections by reconnecting all links.
   *
   * Each input is checked against each output.  This is done on a matching index basis, i.e. input 3 -> output 3.
   * If there are any input links remaining,
   * and {@link flags}.{@link INodeFlags.keepAllLinksOnBypass keepAllLinksOnBypass} is `true`,
   * each input will check for outputs that match, and take the first one that matches
   * `true`: Try the index matching first, then every input to every output.
   * `false`: Only matches indexes, e.g. input 3 to output 3.
   *
   * If {@link flags}.{@link INodeFlags.keepAllLinksOnBypass keepAllLinksOnBypass} is `undefined`, it will fall back to
   * the static {@link keepAllLinksOnBypass}.
   * @returns `true` if any new links were established, otherwise `false`.
   * @todo Decision: Change API to return array of new links instead?
   */
  connectInputToOutput() {
    const { inputs, outputs, graph } = this;
    if (!inputs || !outputs) return;
    if (!graph) throw new NullGraphError();
    const { _links } = graph;
    let madeAnyConnections = false;
    for (const [index2, input] of inputs.entries()) {
      if (input.link == null) continue;
      const output = outputs[index2];
      if (!output || !LiteGraph.isValidConnection(input.type, output.type))
        continue;
      const inLink = _links.get(input.link);
      if (!inLink) continue;
      const inNode = graph.getNodeById(inLink?.origin_id);
      if (!inNode) continue;
      bypassAllLinks(output, inNode, inLink, graph);
    }
    if (!(this.flags.keepAllLinksOnBypass ?? LGraphNode.keepAllLinksOnBypass))
      return madeAnyConnections;
    for (const input of inputs) {
      if (input.link == null) continue;
      const inLink = _links.get(input.link);
      if (!inLink) continue;
      const inNode = graph.getNodeById(inLink?.origin_id);
      if (!inNode) continue;
      for (const output of outputs) {
        if (!LiteGraph.isValidConnection(input.type, output.type)) continue;
        bypassAllLinks(output, inNode, inLink, graph);
        break;
      }
    }
    return madeAnyConnections;
    function bypassAllLinks(output, inNode, inLink, graph2) {
      const outLinks = output.links?.map((x2) => _links.get(x2)).filter((x2) => !!x2);
      if (!outLinks?.length) return;
      for (const outLink of outLinks) {
        const outNode = graph2.getNodeById(outLink.target_id);
        if (!outNode) continue;
        const result = inNode.connect(
          inLink.origin_slot,
          outNode,
          outLink.target_slot,
          inLink.parentId
        );
        madeAnyConnections ||= !!result;
      }
    }
    __name(bypassAllLinks, "bypassAllLinks");
  }
  /**
   * Returns `true` if the widget is visible, otherwise `false`.
   */
  isWidgetVisible(widget) {
    const isHidden = this.collapsed || widget.hidden || widget.advanced && !this.showAdvanced;
    return !isHidden;
  }
  updateComputedDisabled() {
    if (!this.widgets) return;
    for (const widget of this.widgets)
      widget.computedDisabled = widget.disabled || this.getSlotFromWidget(widget)?.link != null;
  }
  drawWidgets(ctx, { lowQuality = false, editorAlpha = 1 }) {
    if (!this.widgets) return;
    const nodeWidth = this.size[0];
    const { widgets: widgets2 } = this;
    const H = LiteGraph.NODE_WIDGET_HEIGHT;
    const showText = !lowQuality;
    ctx.save();
    ctx.globalAlpha = editorAlpha;
    this.updateComputedDisabled();
    for (const widget of widgets2) {
      if (!this.isWidgetVisible(widget)) continue;
      const { y } = widget;
      const outlineColour = widget.advanced ? LiteGraph.WIDGET_ADVANCED_OUTLINE_COLOR : LiteGraph.WIDGET_OUTLINE_COLOR;
      widget.last_y = y;
      ctx.strokeStyle = outlineColour;
      ctx.fillStyle = "#222";
      ctx.textAlign = "left";
      if (widget.computedDisabled) ctx.globalAlpha *= 0.5;
      const width2 = widget.width || nodeWidth;
      if (typeof widget.draw === "function") {
        widget.draw(ctx, this, width2, y, H, lowQuality);
      } else {
        toConcreteWidget(widget, this, false)?.drawWidget(ctx, {
          width: width2,
          showText
        });
      }
      ctx.globalAlpha = editorAlpha;
    }
    ctx.restore();
  }
  /**
   * When {@link LGraphNode.collapsed} is `true`, this method draws the node's collapsed slots.
   */
  drawCollapsedSlots(ctx) {
    for (const slot of this.#concreteInputs) {
      if (slot.link != null) {
        slot.drawCollapsed(ctx);
        break;
      }
    }
    for (const slot of this.#concreteOutputs) {
      if (slot.links?.length) {
        slot.drawCollapsed(ctx);
        break;
      }
    }
  }
  get slots() {
    return [...this.inputs, ...this.outputs];
  }
  #measureSlot(slot, slotIndex, isInput) {
    const pos = isInput ? this.getInputPos(slotIndex) : this.getOutputPos(slotIndex);
    slot.boundingRect[0] = pos[0] - LiteGraph.NODE_SLOT_HEIGHT * 0.5;
    slot.boundingRect[1] = pos[1] - LiteGraph.NODE_SLOT_HEIGHT * 0.5;
    slot.boundingRect[2] = slot.isWidgetInputSlot ? BaseWidget.margin : LiteGraph.NODE_SLOT_HEIGHT;
    slot.boundingRect[3] = LiteGraph.NODE_SLOT_HEIGHT;
  }
  #measureSlots() {
    const slots = [];
    for (const [slotIndex, slot] of this.#concreteInputs.entries()) {
      if (this.widgets?.length && isWidgetInputSlot(slot)) continue;
      this.#measureSlot(slot, slotIndex, true);
      slots.push(slot);
    }
    for (const [slotIndex, slot] of this.#concreteOutputs.entries()) {
      this.#measureSlot(slot, slotIndex, false);
      slots.push(slot);
    }
    return slots.length ? createBounds(slots, 0) : null;
  }
  #getMouseOverSlot(slot) {
    const isInput = isINodeInputSlot(slot);
    const mouseOverId = this.mouseOver?.[isInput ? "inputId" : "outputId"] ?? -1;
    if (mouseOverId === -1) {
      return null;
    }
    return isInput ? this.inputs[mouseOverId] : this.outputs[mouseOverId];
  }
  #isMouseOverSlot(slot) {
    return this.#getMouseOverSlot(slot) === slot;
  }
  #isMouseOverWidget(widget) {
    if (!widget) return false;
    return this.mouseOver?.overWidget === widget;
  }
  /**
   * Returns the input slot that is associated with the given widget.
   */
  getSlotFromWidget(widget) {
    if (widget)
      return this.inputs.find(
        (slot) => isWidgetInputSlot(slot) && slot.widget.name === widget.name
      );
  }
  /**
   * Returns the widget that is associated with the given input slot.
   */
  getWidgetFromSlot(slot) {
    if (!isWidgetInputSlot(slot)) return;
    return this.widgets?.find((w) => w.name === slot.widget.name);
  }
  /**
   * Draws the node's input and output slots.
   */
  drawSlots(ctx, { fromSlot, colorContext, editorAlpha, lowQuality }) {
    for (const slot of [...this.#concreteInputs, ...this.#concreteOutputs]) {
      const isValidTarget = fromSlot && slot.isValidTarget(fromSlot);
      const isMouseOverSlot = this.#isMouseOverSlot(slot);
      const isValid = !fromSlot || isValidTarget;
      const highlight = isValid && isMouseOverSlot;
      if (isMouseOverSlot || isValidTarget || !slot.isWidgetInputSlot || this.#isMouseOverWidget(this.getWidgetFromSlot(slot)) || slot.isConnected || slot.alwaysVisible) {
        ctx.globalAlpha = isValid ? editorAlpha : 0.4 * editorAlpha;
        slot.draw(ctx, {
          colorContext,
          lowQuality,
          highlight
        });
      }
    }
  }
  /**
   * Arranges the node's widgets vertically.
   * Sets following properties on each widget:
   * -  {@link IBaseWidget.computedHeight}
   * -  {@link IBaseWidget.y}
   * @param widgetStartY The y-coordinate of the first widget
   */
  #arrangeWidgets(widgetStartY) {
    if (!this.widgets || !this.widgets.length) return;
    const bodyHeight = this.bodyHeight;
    const startY = this.widgets_start_y ?? (this.widgets_up ? 0 : widgetStartY) + 2;
    let freeSpace = bodyHeight - startY;
    let fixedWidgetHeight = 0;
    const growableWidgets = [];
    const visibleWidgets = this.widgets.filter((w) => !w.hidden);
    for (const w of visibleWidgets) {
      if (w.computeSize) {
        const height = w.computeSize()[1] + 4;
        w.computedHeight = height;
        fixedWidgetHeight += height;
      } else if (w.computeLayoutSize) {
        const { minHeight, maxHeight } = w.computeLayoutSize(this);
        growableWidgets.push({
          minHeight,
          prefHeight: maxHeight,
          w
        });
      } else {
        const height = LiteGraph.NODE_WIDGET_HEIGHT + 4;
        w.computedHeight = height;
        fixedWidgetHeight += height;
      }
    }
    freeSpace -= fixedWidgetHeight;
    this.freeWidgetSpace = freeSpace;
    const spaceRequests = growableWidgets.map((d2) => ({
      minSize: d2.minHeight,
      maxSize: d2.prefHeight
    }));
    const allocations = distributeSpace(Math.max(0, freeSpace), spaceRequests);
    for (const [i, d2] of growableWidgets.entries()) {
      d2.w.computedHeight = allocations[i];
    }
    let y = startY;
    for (const w of visibleWidgets) {
      w.y = y;
      y += w.computedHeight ?? 0;
    }
    if (!this.graph) throw new NullGraphError();
    if (y > bodyHeight) {
      this.setSize([this.size[0], y]);
      this.graph.setDirtyCanvas(false, true);
    }
  }
  /**
   * Arranges the layout of the node's widget input slots.
   */
  #arrangeWidgetInputSlots() {
    if (!this.widgets) return;
    const slotByWidgetName = /* @__PURE__ */ new Map();
    for (const [i, slot] of this.inputs.entries()) {
      if (!isWidgetInputSlot(slot)) continue;
      slotByWidgetName.set(slot.widget.name, { ...slot, index: i });
    }
    if (!slotByWidgetName.size) return;
    if (!LiteGraph.vueNodesMode) {
      for (const widget of this.widgets) {
        const slot = slotByWidgetName.get(widget.name);
        if (!slot) continue;
        const actualSlot = this.#concreteInputs[slot.index];
        const offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5;
        actualSlot.pos = [offset, widget.y + offset];
        this.#measureSlot(actualSlot, slot.index, true);
      }
    } else {
      for (const widget of this.widgets) {
        const slot = slotByWidgetName.get(widget.name);
        if (!slot) continue;
        this.#measureSlot(this.#concreteInputs[slot.index], slot.index, true);
      }
    }
  }
  /**
   * @internal Sets the internal concrete slot arrays, ensuring they are instances of
   * {@link NodeInputSlot} or {@link NodeOutputSlot}.
   *
   * A temporary workaround until duck-typed inputs and outputs
   * have been removed from the ecosystem.
   */
  _setConcreteSlots() {
    this.#concreteInputs = this.inputs.map(
      (slot) => toClass(NodeInputSlot, slot, this)
    );
    this.#concreteOutputs = this.outputs.map(
      (slot) => toClass(NodeOutputSlot, slot, this)
    );
  }
  /**
   * Arranges node elements in preparation for rendering (slots & widgets).
   */
  arrange() {
    const slotsBounds = this.#measureSlots();
    const widgetStartY = slotsBounds ? slotsBounds[1] + slotsBounds[3] - this.pos[1] : 0;
    this.#arrangeWidgets(widgetStartY);
    this.#arrangeWidgetInputSlots();
  }
  /**
   * Draws a progress bar on the node.
   * @param ctx The canvas context to draw on
   */
  drawProgressBar(ctx) {
    if (!this.progress) return;
    const originalFillStyle = ctx.fillStyle;
    ctx.fillStyle = "green";
    ctx.fillRect(0, 0, this.width * this.progress, 6);
    ctx.fillStyle = originalFillStyle;
  }
}
class LGraphGroup {
  static {
    __name(this, "LGraphGroup");
  }
  static minWidth = 140;
  static minHeight = 80;
  static resizeLength = 10;
  static padding = 4;
  static defaultColour = "#335";
  id;
  color;
  title;
  font;
  font_size = LiteGraph.DEFAULT_GROUP_FONT || 24;
  _bounding = new Rectangle(10, 10, LGraphGroup.minWidth, LGraphGroup.minHeight);
  _pos = this._bounding.pos;
  _size = this._bounding.size;
  /** @deprecated See {@link _children} */
  _nodes = [];
  _children = /* @__PURE__ */ new Set();
  graph;
  flags = {};
  selected;
  constructor(title, id2) {
    this.id = id2 ?? -1;
    this.title = title || "Group";
    const { pale_blue } = LGraphCanvas.node_colors;
    this.color = pale_blue ? pale_blue.groupcolor : "#AAA";
  }
  /** @inheritdoc {@link IColorable.setColorOption} */
  setColorOption(colorOption) {
    if (colorOption == null) {
      delete this.color;
    } else {
      this.color = colorOption.groupcolor;
    }
  }
  /** @inheritdoc {@link IColorable.getColorOption} */
  getColorOption() {
    return Object.values(LGraphCanvas.node_colors).find(
      (colorOption) => colorOption.groupcolor === this.color
    ) ?? null;
  }
  /** Position of the group, as x,y co-ordinates in graph space */
  get pos() {
    return this._pos;
  }
  set pos(v2) {
    if (!v2 || v2.length < 2) return;
    this._pos[0] = v2[0];
    this._pos[1] = v2[1];
  }
  /** Size of the group, as width,height in graph units */
  get size() {
    return this._size;
  }
  set size(v2) {
    if (!v2 || v2.length < 2) return;
    this._size[0] = Math.max(LGraphGroup.minWidth, v2[0]);
    this._size[1] = Math.max(LGraphGroup.minHeight, v2[1]);
  }
  get boundingRect() {
    return this._bounding;
  }
  getBounding() {
    return this._bounding;
  }
  get nodes() {
    return this._nodes;
  }
  get titleHeight() {
    return this.font_size * 1.4;
  }
  get children() {
    return this._children;
  }
  get pinned() {
    return !!this.flags.pinned;
  }
  /**
   * Prevents the group being accidentally moved or resized by mouse interaction.
   * Toggles pinned state if no value is provided.
   */
  pin(value) {
    const newState = value === void 0 ? !this.pinned : value;
    if (newState) this.flags.pinned = true;
    else delete this.flags.pinned;
  }
  unpin() {
    this.pin(false);
  }
  configure(o) {
    this.id = o.id;
    this.title = o.title;
    this._bounding.set(o.bounding);
    this.color = o.color;
    this.flags = o.flags || this.flags;
    if (o.font_size) this.font_size = o.font_size;
  }
  serialize() {
    const b = this._bounding;
    return {
      id: this.id,
      title: this.title,
      bounding: [...b],
      color: this.color,
      font_size: this.font_size,
      flags: this.flags
    };
  }
  /**
   * Draws the group on the canvas
   * @param graphCanvas
   * @param ctx
   */
  draw(graphCanvas, ctx) {
    const { padding, resizeLength, defaultColour } = LGraphGroup;
    const font_size = this.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;
    const [x2, y] = this._pos;
    const [width2, height] = this._size;
    const color2 = this.color || defaultColour;
    ctx.globalAlpha = 0.25 * graphCanvas.editor_alpha;
    ctx.fillStyle = color2;
    ctx.strokeStyle = color2;
    ctx.beginPath();
    ctx.rect(x2 + 0.5, y + 0.5, width2, font_size * 1.4);
    ctx.fill();
    ctx.fillStyle = color2;
    ctx.strokeStyle = color2;
    ctx.beginPath();
    ctx.rect(x2 + 0.5, y + 0.5, width2, height);
    ctx.fill();
    ctx.globalAlpha = graphCanvas.editor_alpha;
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(x2 + width2, y + height);
    ctx.lineTo(x2 + width2 - resizeLength, y + height);
    ctx.lineTo(x2 + width2, y + height - resizeLength);
    ctx.fill();
    ctx.font = `${font_size}px ${LiteGraph.GROUP_FONT}`;
    ctx.textAlign = "left";
    ctx.fillText(
      this.title + (this.pinned ? "📌" : ""),
      x2 + padding,
      y + font_size
    );
    if (LiteGraph.highlight_selected_group && this.selected) {
      strokeShape(ctx, this._bounding, {
        title_height: this.titleHeight,
        padding
      });
    }
  }
  resize(width2, height) {
    if (this.pinned) return false;
    this._size[0] = Math.max(LGraphGroup.minWidth, width2);
    this._size[1] = Math.max(LGraphGroup.minHeight, height);
    return true;
  }
  move(deltaX, deltaY, skipChildren = false) {
    if (this.pinned) return;
    this._pos[0] += deltaX;
    this._pos[1] += deltaY;
    if (skipChildren === true) return;
    for (const item of this._children) {
      item.move(deltaX, deltaY);
    }
  }
  /** @inheritdoc */
  snapToGrid(snapTo) {
    return this.pinned ? false : snapPoint(this.pos, snapTo);
  }
  recomputeInsideNodes() {
    if (!this.graph) throw new NullGraphError();
    const { nodes, reroutes, groups } = this.graph;
    const children = this._children;
    this._nodes.length = 0;
    children.clear();
    for (const node2 of nodes) {
      if (containsCentre(this._bounding, node2.boundingRect)) {
        this._nodes.push(node2);
        children.add(node2);
      }
    }
    for (const reroute of reroutes.values()) {
      if (isPointInRect(reroute.pos, this._bounding)) children.add(reroute);
    }
    for (const group of groups) {
      if (containsRect(this._bounding, group._bounding)) children.add(group);
    }
    groups.sort((a, b) => {
      if (a === this) {
        return children.has(b) ? -1 : 0;
      } else if (b === this) {
        return children.has(a) ? 1 : 0;
      } else {
        return 0;
      }
    });
  }
  /**
   * Resizes and moves the group to neatly fit all given {@link objects}.
   * @param objects All objects that should be inside the group
   * @param padding Value in graph units to add to all sides of the group.  Default: 10
   */
  resizeTo(objects, padding = 10) {
    const boundingBox = createBounds(objects, padding);
    if (boundingBox === null) return;
    this.pos[0] = boundingBox[0];
    this.pos[1] = boundingBox[1] - this.titleHeight;
    this.size[0] = boundingBox[2];
    this.size[1] = boundingBox[3] + this.titleHeight;
  }
  /**
   * Add nodes to the group and adjust the group's position and size accordingly
   * @param nodes The nodes to add to the group
   * @param padding The padding around the group
   */
  addNodes(nodes, padding = 10) {
    if (!this._nodes && nodes.length === 0) return;
    this.resizeTo([...this.children, ...this._nodes, ...nodes], padding);
  }
  getMenuOptions() {
    return [
      {
        content: this.pinned ? "Unpin" : "Pin",
        callback: /* @__PURE__ */ __name(() => {
          if (this.pinned) this.unpin();
          else this.pin();
          this.setDirtyCanvas(false, true);
        }, "callback")
      },
      null,
      { content: "Title", callback: LGraphCanvas.onShowPropertyEditor },
      {
        content: "Color",
        has_submenu: true,
        callback: LGraphCanvas.onMenuNodeColors
      },
      {
        content: "Font size",
        property: "font_size",
        type: "Number",
        callback: LGraphCanvas.onShowPropertyEditor
      },
      null,
      { content: "Remove", callback: LGraphCanvas.onMenuNodeRemove }
    ];
  }
  isPointInTitlebar(x2, y) {
    const b = this.boundingRect;
    return isInRectangle(x2, y, b[0], b[1], b[2], this.titleHeight);
  }
  isInResize(x2, y) {
    const b = this.boundingRect;
    const right = b[0] + b[2];
    const bottom = b[1] + b[3];
    return x2 < right && y < bottom && x2 - right + (y - bottom) > -LGraphGroup.resizeLength;
  }
  isPointInside = LGraphNode.prototype.isPointInside;
  setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas;
}
class FloatingRenderLink {
  static {
    __name(this, "FloatingRenderLink");
  }
  constructor(network, link, toType, fromReroute, dragDirection = LinkDirection.CENTER) {
    this.network = network;
    this.link = link;
    this.toType = toType;
    this.fromReroute = fromReroute;
    this.dragDirection = dragDirection;
    const {
      origin_id: outputNodeId,
      target_id: inputNodeId,
      origin_slot: outputIndex,
      target_slot: inputIndex
    } = link;
    if (outputNodeId !== -1) {
      const outputNode = network.getNodeById(outputNodeId) ?? void 0;
      if (!outputNode)
        throw new Error(
          `Creating DraggingRenderLink for link [${link.id}] failed: Output node [${outputNodeId}] not found.`
        );
      const outputSlot = outputNode?.outputs.at(outputIndex);
      if (!outputSlot)
        throw new Error(
          `Creating DraggingRenderLink for link [${link.id}] failed: Output slot [${outputIndex}] not found.`
        );
      this.outputNodeId = outputNodeId;
      this.outputNode = outputNode;
      this.outputSlot = outputSlot;
      this.outputIndex = outputIndex;
      this.outputPos = outputNode.getOutputPos(outputIndex);
      this.node = outputNode;
      this.fromSlot = outputSlot;
      this.fromPos = fromReroute?.pos ?? this.outputPos;
      this.fromDirection = LinkDirection.LEFT;
      this.dragDirection = LinkDirection.RIGHT;
      this.fromSlotIndex = outputIndex;
    } else {
      const inputNode = network.getNodeById(inputNodeId) ?? void 0;
      if (!inputNode)
        throw new Error(
          `Creating DraggingRenderLink for link [${link.id}] failed: Input node [${inputNodeId}] not found.`
        );
      const inputSlot = inputNode?.inputs.at(inputIndex);
      if (!inputSlot)
        throw new Error(
          `Creating DraggingRenderLink for link [${link.id}] failed: Input slot [${inputIndex}] not found.`
        );
      this.inputNodeId = inputNodeId;
      this.inputNode = inputNode;
      this.inputSlot = inputSlot;
      this.inputIndex = inputIndex;
      this.inputPos = inputNode.getInputPos(inputIndex);
      this.node = inputNode;
      this.fromSlot = inputSlot;
      this.fromDirection = LinkDirection.RIGHT;
      this.fromSlotIndex = inputIndex;
    }
    this.fromPos = fromReroute.pos;
  }
  node;
  fromSlot;
  fromPos;
  fromDirection;
  fromSlotIndex;
  outputNodeId = -1;
  outputNode;
  outputSlot;
  outputIndex = -1;
  outputPos;
  inputNodeId = -1;
  inputNode;
  inputSlot;
  inputIndex = -1;
  inputPos;
  canConnectToInput() {
    return this.toType === "input";
  }
  canConnectToOutput() {
    return this.toType === "output";
  }
  canConnectToReroute(reroute) {
    if (this.toType === "input") {
      if (reroute.origin_id === this.inputNode?.id) return false;
    } else {
      if (reroute.origin_id === this.outputNode?.id) return false;
    }
    return true;
  }
  canConnectToSubgraphInput(input) {
    return this.toType === "output" && input.isValidTarget(this.fromSlot);
  }
  connectToInput(node2, input, _events) {
    const floatingLink = this.link;
    floatingLink.target_id = node2.id;
    floatingLink.target_slot = node2.inputs.indexOf(input);
    node2.disconnectInput(node2.inputs.indexOf(input));
    this.fromSlot._floatingLinks?.delete(floatingLink);
    input._floatingLinks ??= /* @__PURE__ */ new Set();
    input._floatingLinks.add(floatingLink);
  }
  connectToOutput(node2, output, _events) {
    const floatingLink = this.link;
    floatingLink.origin_id = node2.id;
    floatingLink.origin_slot = node2.outputs.indexOf(output);
    this.fromSlot._floatingLinks?.delete(floatingLink);
    output._floatingLinks ??= /* @__PURE__ */ new Set();
    output._floatingLinks.add(floatingLink);
  }
  connectToSubgraphInput(input, _events) {
    const floatingLink = this.link;
    floatingLink.origin_id = SUBGRAPH_INPUT_ID;
    floatingLink.origin_slot = input.parent.slots.indexOf(input);
    this.fromSlot._floatingLinks?.delete(floatingLink);
    input._floatingLinks ??= /* @__PURE__ */ new Set();
    input._floatingLinks.add(floatingLink);
  }
  connectToSubgraphOutput(output, _events) {
    const floatingLink = this.link;
    floatingLink.origin_id = SUBGRAPH_OUTPUT_ID;
    floatingLink.origin_slot = output.parent.slots.indexOf(output);
    this.fromSlot._floatingLinks?.delete(floatingLink);
    output._floatingLinks ??= /* @__PURE__ */ new Set();
    output._floatingLinks.add(floatingLink);
  }
  connectToRerouteInput(reroute, { node: inputNode, input }, events) {
    const floatingLink = this.link;
    floatingLink.target_id = inputNode.id;
    floatingLink.target_slot = inputNode.inputs.indexOf(input);
    this.fromSlot._floatingLinks?.delete(floatingLink);
    input._floatingLinks ??= /* @__PURE__ */ new Set();
    input._floatingLinks.add(floatingLink);
    events.dispatch("input-moved", this);
  }
  connectToRerouteOutput(reroute, outputNode, output, events) {
    const floatingLink = this.link;
    floatingLink.origin_id = outputNode.id;
    floatingLink.origin_slot = outputNode.outputs.indexOf(output);
    this.fromSlot._floatingLinks?.delete(floatingLink);
    output._floatingLinks ??= /* @__PURE__ */ new Set();
    output._floatingLinks.add(floatingLink);
    events.dispatch("output-moved", this);
  }
}
class MovingLinkBase {
  static {
    __name(this, "MovingLinkBase");
  }
  constructor(network, link, toType, fromReroute, dragDirection = LinkDirection.CENTER) {
    this.network = network;
    this.link = link;
    this.toType = toType;
    this.fromReroute = fromReroute;
    this.dragDirection = dragDirection;
    const {
      origin_id: outputNodeId,
      target_id: inputNodeId,
      origin_slot: outputIndex,
      target_slot: inputIndex
    } = link;
    const outputNode = network.getNodeById(outputNodeId) ?? void 0;
    if (!outputNode)
      throw new Error(
        `Creating MovingRenderLink for link [${link.id}] failed: Output node [${outputNodeId}] not found.`
      );
    const outputSlot = outputNode.outputs.at(outputIndex);
    if (!outputSlot)
      throw new Error(
        `Creating MovingRenderLink for link [${link.id}] failed: Output slot [${outputIndex}] not found.`
      );
    this.outputNodeId = outputNodeId;
    this.outputNode = outputNode;
    this.outputSlot = outputSlot;
    this.outputIndex = outputIndex;
    this.outputPos = outputNode.getOutputPos(outputIndex);
    const inputNode = network.getNodeById(inputNodeId) ?? void 0;
    if (!inputNode)
      throw new Error(
        `Creating DraggingRenderLink for link [${link.id}] failed: Input node [${inputNodeId}] not found.`
      );
    const inputSlot = inputNode.inputs.at(inputIndex);
    if (!inputSlot)
      throw new Error(
        `Creating DraggingRenderLink for link [${link.id}] failed: Input slot [${inputIndex}] not found.`
      );
    this.inputNodeId = inputNodeId;
    this.inputNode = inputNode;
    this.inputSlot = inputSlot;
    this.inputIndex = inputIndex;
    this.inputPos = inputNode.getInputPos(inputIndex);
  }
  outputNodeId;
  outputNode;
  outputSlot;
  outputIndex;
  outputPos;
  inputNodeId;
  inputNode;
  inputSlot;
  inputIndex;
  inputPos;
}
class MovingInputLink extends MovingLinkBase {
  static {
    __name(this, "MovingInputLink");
  }
  toType = "input";
  node;
  fromSlot;
  fromPos;
  fromDirection;
  fromSlotIndex;
  constructor(network, link, fromReroute, dragDirection = LinkDirection.CENTER) {
    super(network, link, "input", fromReroute, dragDirection);
    this.node = this.outputNode;
    this.fromSlot = this.outputSlot;
    this.fromPos = fromReroute?.pos ?? this.outputPos;
    this.fromDirection = LinkDirection.NONE;
    this.fromSlotIndex = this.outputIndex;
  }
  canConnectToInput(inputNode, input) {
    return this.node.canConnectTo(inputNode, input, this.outputSlot);
  }
  canConnectToOutput() {
    return false;
  }
  canConnectToReroute(reroute) {
    return reroute.origin_id !== this.inputNode.id;
  }
  connectToInput(inputNode, input, events) {
    if (input === this.inputSlot) return;
    this.inputNode.disconnectInput(this.inputIndex, true);
    const link = this.outputNode.connectSlots(
      this.outputSlot,
      inputNode,
      input,
      this.fromReroute?.id
    );
    if (link) events.dispatch("input-moved", this);
    return link;
  }
  connectToOutput() {
    throw new Error("MovingInputLink cannot connect to an output.");
  }
  connectToSubgraphInput() {
    throw new Error("MovingInputLink cannot connect to a subgraph input.");
  }
  connectToSubgraphOutput(output, events) {
    const newLink = output.connect(
      this.fromSlot,
      this.node,
      this.fromReroute?.id
    );
    events?.dispatch("link-created", newLink);
  }
  connectToRerouteInput(reroute, {
    node: inputNode,
    input,
    link: existingLink
  }, events, originalReroutes) {
    const { outputNode, outputSlot, fromReroute } = this;
    for (const reroute2 of originalReroutes) {
      if (reroute2.id === this.link.parentId) break;
      if (reroute2.totalLinks === 1) reroute2.remove();
    }
    reroute.parentId = fromReroute?.id;
    const newLink = outputNode.connectSlots(
      outputSlot,
      inputNode,
      input,
      existingLink.parentId
    );
    if (newLink) events.dispatch("input-moved", this);
  }
  connectToRerouteOutput() {
    throw new Error("MovingInputLink cannot connect to an output.");
  }
  disconnect() {
    return this.inputNode.disconnectInput(this.inputIndex, true);
  }
}
class MovingOutputLink extends MovingLinkBase {
  static {
    __name(this, "MovingOutputLink");
  }
  toType = "output";
  node;
  fromSlot;
  fromPos;
  fromDirection;
  fromSlotIndex;
  constructor(network, link, fromReroute, dragDirection = LinkDirection.CENTER) {
    super(network, link, "output", fromReroute, dragDirection);
    this.node = this.inputNode;
    this.fromSlot = this.inputSlot;
    this.fromPos = fromReroute?.pos ?? this.inputPos;
    this.fromDirection = LinkDirection.LEFT;
    this.fromSlotIndex = this.inputIndex;
  }
  canConnectToInput() {
    return false;
  }
  canConnectToOutput(outputNode, output) {
    return outputNode.canConnectTo(this.node, this.inputSlot, output);
  }
  canConnectToReroute(reroute) {
    return reroute.origin_id !== this.outputNode.id;
  }
  canConnectToSubgraphInput(input) {
    return input.isValidTarget(this.fromSlot);
  }
  connectToInput() {
    throw new Error("MovingOutputLink cannot connect to an input.");
  }
  connectToOutput(outputNode, output, events) {
    if (output === this.outputSlot) return;
    const link = outputNode.connectSlots(
      output,
      this.inputNode,
      this.inputSlot,
      this.link.parentId
    );
    if (link) events.dispatch("output-moved", this);
    return link;
  }
  connectToSubgraphInput(input, events) {
    const newLink = input.connect(
      this.fromSlot,
      this.node,
      this.fromReroute?.id
    );
    events?.dispatch("link-created", newLink);
  }
  connectToSubgraphOutput() {
    throw new Error("MovingOutputLink cannot connect to a subgraph output.");
  }
  connectToRerouteInput() {
    throw new Error("MovingOutputLink cannot connect to an input.");
  }
  connectToRerouteOutput(reroute, outputNode, output, events) {
    const { inputNode, inputSlot, fromReroute } = this;
    const floatingTerminus = reroute?.floating?.slotType === "output";
    if (fromReroute) {
      fromReroute.parentId = reroute.id;
    } else {
      this.link.parentId = reroute.id;
    }
    outputNode.connectSlots(output, inputNode, inputSlot, this.link.parentId);
    if (floatingTerminus) reroute.removeAllFloatingLinks();
    events.dispatch("output-moved", this);
  }
  disconnect() {
    return this.outputNode.disconnectOutput(this.outputIndex, this.inputNode);
  }
}
class ToInputFromIoNodeLink {
  static {
    __name(this, "ToInputFromIoNodeLink");
  }
  constructor(network, node2, fromSlot, fromReroute, dragDirection = LinkDirection.CENTER, existingLink) {
    this.network = network;
    this.node = node2;
    this.fromSlot = fromSlot;
    this.fromReroute = fromReroute;
    this.dragDirection = dragDirection;
    const outputIndex = node2.slots.indexOf(fromSlot);
    if (outputIndex === -1 && fromSlot !== node2.emptySlot) {
      throw new Error(
        `Creating render link for node [${this.node.id}] failed: Slot index not found.`
      );
    }
    this.fromSlotIndex = outputIndex;
    this.fromPos = fromReroute ? fromReroute.pos : fromSlot.pos;
    this.existingLink = existingLink;
  }
  toType = "input";
  fromSlotIndex;
  fromPos;
  fromDirection = LinkDirection.RIGHT;
  existingLink;
  canConnectToInput(inputNode, input) {
    return this.node.canConnectTo(inputNode, input, this.fromSlot);
  }
  canConnectToOutput() {
    return false;
  }
  connectToInput(node2, input, events) {
    const { fromSlot, fromReroute, existingLink } = this;
    const newLink = fromSlot.connect(input, node2, fromReroute?.id);
    if (existingLink) {
      const { input: input2, inputNode } = existingLink.resolve(this.network);
      if (inputNode && input2)
        this.node._disconnectNodeInput(inputNode, input2, existingLink);
      events.dispatch("input-moved", this);
    } else {
      events.dispatch("link-created", newLink);
    }
  }
  connectToSubgraphOutput() {
    throw new Error("Not implemented");
  }
  connectToRerouteInput(reroute, {
    node: inputNode,
    input,
    link
  }, events, originalReroutes) {
    const { fromSlot, fromReroute } = this;
    const floatingTerminus = fromReroute?.floating?.slotType === "output";
    reroute.parentId = fromReroute?.id;
    const newLink = fromSlot.connect(input, inputNode, link.parentId);
    if (floatingTerminus) fromReroute.removeAllFloatingLinks();
    for (const reroute2 of originalReroutes) {
      if (reroute2.id === fromReroute?.id) break;
      reroute2.removeLink(link);
      if (reroute2.totalLinks === 0) {
        if (link.isFloating) {
          reroute2.remove();
        } else {
          const cl = link.toFloating("output", reroute2.id);
          this.network.addFloatingLink(cl);
          reroute2.floating = { slotType: "output" };
        }
      }
    }
    if (this.existingLink) {
      events.dispatch("input-moved", this);
    } else {
      events.dispatch("link-created", newLink);
    }
  }
  connectToOutput() {
    throw new Error("ToInputRenderLink cannot connect to an output.");
  }
  connectToSubgraphInput() {
    throw new Error("ToInputRenderLink cannot connect to a subgraph input.");
  }
  connectToRerouteOutput() {
    throw new Error("ToInputRenderLink cannot connect to an output.");
  }
  disconnect() {
    if (!this.existingLink) return false;
    const { input, inputNode } = this.existingLink.resolve(this.network);
    if (!inputNode || !input) return false;
    this.node._disconnectNodeInput(inputNode, input, this.existingLink);
    return true;
  }
}
class ToInputRenderLink {
  static {
    __name(this, "ToInputRenderLink");
  }
  constructor(network, node2, fromSlot, fromReroute, dragDirection = LinkDirection.CENTER) {
    this.network = network;
    this.node = node2;
    this.fromSlot = fromSlot;
    this.fromReroute = fromReroute;
    this.dragDirection = dragDirection;
    const outputIndex = node2.outputs.indexOf(fromSlot);
    if (outputIndex === -1)
      throw new Error(
        `Creating render link for node [${this.node.id}] failed: Slot index not found.`
      );
    this.fromSlotIndex = outputIndex;
    this.fromPos = fromReroute ? fromReroute.pos : this.node.getOutputPos(outputIndex);
  }
  toType = "input";
  fromPos;
  fromSlotIndex;
  fromDirection = LinkDirection.RIGHT;
  canConnectToInput(inputNode, input) {
    return this.node.canConnectTo(inputNode, input, this.fromSlot);
  }
  canConnectToOutput() {
    return false;
  }
  connectToInput(node2, input, events) {
    const { node: outputNode, fromSlot, fromReroute } = this;
    if (node2 === outputNode) return;
    const newLink = outputNode.connectSlots(
      fromSlot,
      node2,
      input,
      fromReroute?.id
    );
    events.dispatch("link-created", newLink);
  }
  connectToSubgraphOutput(output, events) {
    const newLink = output.connect(
      this.fromSlot,
      this.node,
      this.fromReroute?.id
    );
    events.dispatch("link-created", newLink);
  }
  connectToRerouteInput(reroute, {
    node: inputNode,
    input,
    link
  }, events, originalReroutes) {
    const { node: outputNode, fromSlot, fromReroute } = this;
    const floatingTerminus = fromReroute?.floating?.slotType === "output";
    reroute.parentId = fromReroute?.id;
    const newLink = outputNode.connectSlots(
      fromSlot,
      inputNode,
      input,
      link.parentId
    );
    if (floatingTerminus) fromReroute.removeAllFloatingLinks();
    for (const reroute2 of originalReroutes) {
      if (reroute2.id === fromReroute?.id) break;
      reroute2.removeLink(link);
      if (reroute2.totalLinks === 0) {
        if (link.isFloating) {
          reroute2.remove();
        } else {
          const cl = link.toFloating("output", reroute2.id);
          this.network.addFloatingLink(cl);
          reroute2.floating = { slotType: "output" };
        }
      }
    }
    events.dispatch("link-created", newLink);
  }
  connectToOutput() {
    throw new Error("ToInputRenderLink cannot connect to an output.");
  }
  connectToSubgraphInput() {
    throw new Error("ToInputRenderLink cannot connect to a subgraph input.");
  }
  connectToRerouteOutput() {
    throw new Error("ToInputRenderLink cannot connect to an output.");
  }
}
class ToOutputFromIoNodeLink {
  static {
    __name(this, "ToOutputFromIoNodeLink");
  }
  constructor(network, node2, fromSlot, fromReroute, dragDirection = LinkDirection.CENTER) {
    this.network = network;
    this.node = node2;
    this.fromSlot = fromSlot;
    this.fromReroute = fromReroute;
    this.dragDirection = dragDirection;
    const inputIndex = node2.slots.indexOf(fromSlot);
    if (inputIndex === -1 && fromSlot !== node2.emptySlot) {
      throw new Error(
        `Creating render link for node [${this.node.id}] failed: Slot index not found.`
      );
    }
    this.fromSlotIndex = inputIndex;
    this.fromPos = fromReroute ? fromReroute.pos : fromSlot.pos;
  }
  toType = "output";
  fromPos;
  fromSlotIndex;
  fromDirection = LinkDirection.LEFT;
  canConnectToInput() {
    return false;
  }
  canConnectToOutput(outputNode, output) {
    return this.node.canConnectTo(outputNode, this.fromSlot, output);
  }
  canConnectToReroute(reroute) {
    if (reroute.origin_id === this.node.id) return false;
    return true;
  }
  connectToOutput(node2, output, events) {
    const { fromSlot, fromReroute } = this;
    const newLink = fromSlot.connect(output, node2, fromReroute?.id);
    events.dispatch("link-created", newLink);
  }
  connectToSubgraphInput() {
    throw new Error("Not implemented");
  }
  connectToRerouteOutput(reroute, outputNode, output, events) {
    const { fromSlot } = this;
    const newLink = fromSlot.connect(output, outputNode, reroute?.id);
    events.dispatch("link-created", newLink);
  }
  connectToInput() {
    throw new Error("ToOutputRenderLink cannot connect to an input.");
  }
  connectToSubgraphOutput() {
    throw new Error("ToOutputRenderLink cannot connect to a subgraph output.");
  }
  connectToRerouteInput() {
    throw new Error("ToOutputRenderLink cannot connect to an input.");
  }
}
class ToOutputRenderLink {
  static {
    __name(this, "ToOutputRenderLink");
  }
  constructor(network, node2, fromSlot, fromReroute, dragDirection = LinkDirection.CENTER) {
    this.network = network;
    this.node = node2;
    this.fromSlot = fromSlot;
    this.fromReroute = fromReroute;
    this.dragDirection = dragDirection;
    const inputIndex = node2.inputs.indexOf(fromSlot);
    if (inputIndex === -1)
      throw new Error(
        `Creating render link for node [${this.node.id}] failed: Slot index not found.`
      );
    this.fromSlotIndex = inputIndex;
    this.fromPos = fromReroute ? fromReroute.pos : this.node.getInputPos(inputIndex);
  }
  toType = "output";
  fromPos;
  fromSlotIndex;
  fromDirection = LinkDirection.LEFT;
  canConnectToInput() {
    return false;
  }
  canConnectToOutput(outputNode, output) {
    return this.node.canConnectTo(outputNode, this.fromSlot, output);
  }
  canConnectToReroute(reroute) {
    if (reroute.origin_id === this.node.id) return false;
    return true;
  }
  canConnectToSubgraphInput(input) {
    return input.isValidTarget(this.fromSlot);
  }
  connectToOutput(node2, output, events) {
    const { node: inputNode, fromSlot, fromReroute } = this;
    if (!inputNode) return;
    const newLink = node2.connectSlots(
      output,
      inputNode,
      fromSlot,
      fromReroute?.id
    );
    events.dispatch("link-created", newLink);
  }
  connectToSubgraphInput(input, events) {
    const newLink = input.connect(
      this.fromSlot,
      this.node,
      this.fromReroute?.id
    );
    events?.dispatch("link-created", newLink);
  }
  connectToRerouteOutput(reroute, outputNode, output, events) {
    const { node: inputNode, fromSlot } = this;
    const newLink = outputNode.connectSlots(
      output,
      inputNode,
      fromSlot,
      reroute?.id
    );
    events.dispatch("link-created", newLink);
  }
  connectToInput() {
    throw new Error("ToOutputRenderLink cannot connect to an input.");
  }
  connectToSubgraphOutput() {
    throw new Error("ToOutputRenderLink cannot connect to a subgraph output.");
  }
  connectToRerouteInput() {
    throw new Error("ToOutputRenderLink cannot connect to an input.");
  }
}
class ToOutputFromRerouteLink extends ToOutputRenderLink {
  static {
    __name(this, "ToOutputFromRerouteLink");
  }
  constructor(network, node2, fromSlot, fromReroute, linkConnector) {
    super(network, node2, fromSlot, fromReroute);
    this.fromReroute = fromReroute;
    this.linkConnector = linkConnector;
  }
  canConnectToReroute() {
    return false;
  }
  connectToOutput(node2, output) {
    const nuRenderLink = new ToInputRenderLink(this.network, node2, output);
    this.linkConnector._connectOutputToReroute(this.fromReroute, nuRenderLink);
  }
}
class LinkConnector {
  static {
    __name(this, "LinkConnector");
  }
  /**
   * Link connection state POJO. Source of truth for state of link drag operations.
   *
   * Can be replaced or proxied to allow notifications.
   * Is always dereferenced at the start of an operation.
   */
  state = {
    connectingTo: void 0,
    multi: false,
    draggingExistingLinks: false,
    snapLinksPos: void 0
  };
  events = new CustomEventTarget();
  /** Contains information for rendering purposes only. */
  renderLinks = [];
  /** Existing links that are being moved **to** a new input slot. */
  inputLinks = [];
  /** Existing links that are being moved **to** a new output slot. */
  outputLinks = [];
  /** Existing floating links that are being moved to a new slot. */
  floatingLinks = [];
  hiddenReroutes = /* @__PURE__ */ new Set();
  /** The widget beneath the pointer, if it is a valid connection target. */
  overWidget;
  /** The type (returned by downstream callback) for {@link overWidget} */
  overWidgetType;
  /** The reroute beneath the pointer, if it is a valid connection target. */
  overReroute;
  #setConnectingLinks;
  constructor(setConnectingLinks) {
    this.#setConnectingLinks = setConnectingLinks;
  }
  get isConnecting() {
    return this.state.connectingTo !== void 0;
  }
  get draggingExistingLinks() {
    return this.state.draggingExistingLinks;
  }
  /** Drag an existing link to a different input. */
  moveInputLink(network, input) {
    if (this.isConnecting) throw new Error("Already dragging links.");
    const { state: state2, inputLinks, renderLinks } = this;
    const linkId = input.link;
    if (linkId == null) {
      const floatingLink = input._floatingLinks?.values().next().value;
      if (floatingLink?.parentId == null) return;
      try {
        const reroute = network.reroutes.get(floatingLink.parentId);
        if (!reroute)
          throw new Error(
            `Invalid reroute id: [${floatingLink.parentId}] for floating link id: [${floatingLink.id}].`
          );
        const renderLink = new FloatingRenderLink(
          network,
          floatingLink,
          "input",
          reroute
        );
        const mayContinue = this.events.dispatch(
          "before-move-input",
          renderLink
        );
        if (mayContinue === false) return;
        renderLinks.push(renderLink);
      } catch (error) {
        console.warn(
          `Could not create render link for link id: [${floatingLink.id}].`,
          floatingLink,
          error
        );
      }
      floatingLink._dragging = true;
      this.floatingLinks.push(floatingLink);
    } else {
      const link = network.links.get(linkId);
      if (!link) return;
      if (link.origin_id === SUBGRAPH_INPUT_ID) {
        const subgraphInput = network.inputNode?.slots[link.origin_slot];
        if (!subgraphInput) {
          console.warn(
            `Could not find subgraph input for slot [${link.origin_slot}]`
          );
          return;
        }
        try {
          const reroute = network.getReroute(link.parentId);
          const renderLink = new ToInputFromIoNodeLink(
            network,
            network.inputNode,
            subgraphInput,
            reroute,
            LinkDirection.CENTER,
            link
          );
          renderLinks.push(renderLink);
          this.listenUntilReset("input-moved", () => {
            link.disconnect(network, "input");
          });
        } catch (error) {
          console.warn(
            `Could not create render link for subgraph input link id: [${link.id}].`,
            link,
            error
          );
          return;
        }
        link._dragging = true;
        inputLinks.push(link);
      } else {
        try {
          const reroute = network.getReroute(link.parentId);
          const renderLink = new MovingInputLink(network, link, reroute);
          const mayContinue = this.events.dispatch(
            "before-move-input",
            renderLink
          );
          if (mayContinue === false) return;
          renderLinks.push(renderLink);
          this.listenUntilReset("input-moved", (e2) => {
            if ("link" in e2.detail && e2.detail.link) {
              e2.detail.link.disconnect(network, "output");
            }
          });
        } catch (error) {
          console.warn(
            `Could not create render link for link id: [${link.id}].`,
            link,
            error
          );
          return;
        }
        link._dragging = true;
        inputLinks.push(link);
      }
    }
    state2.connectingTo = "input";
    state2.draggingExistingLinks = true;
    this.#setLegacyLinks(false);
  }
  /** Drag all links from an output to a new output. */
  moveOutputLink(network, output) {
    if (this.isConnecting) throw new Error("Already dragging links.");
    const { state: state2, renderLinks } = this;
    if (output._floatingLinks?.size) {
      for (const floatingLink of output._floatingLinks.values()) {
        try {
          const reroute = LLink.getFirstReroute(network, floatingLink);
          if (!reroute)
            throw new Error(
              `Invalid reroute id: [${floatingLink.parentId}] for floating link id: [${floatingLink.id}].`
            );
          const renderLink = new FloatingRenderLink(
            network,
            floatingLink,
            "output",
            reroute
          );
          const mayContinue = this.events.dispatch(
            "before-move-output",
            renderLink
          );
          if (mayContinue === false) continue;
          renderLinks.push(renderLink);
          this.floatingLinks.push(floatingLink);
        } catch (error) {
          console.warn(
            `Could not create render link for link id: [${floatingLink.id}].`,
            floatingLink,
            error
          );
        }
      }
    }
    if (output.links?.length) {
      for (const linkId of output.links) {
        const link = network.links.get(linkId);
        if (!link) continue;
        const firstReroute = LLink.getFirstReroute(network, link);
        if (firstReroute) {
          firstReroute._dragging = true;
          this.hiddenReroutes.add(firstReroute);
        } else {
          link._dragging = true;
        }
        this.outputLinks.push(link);
        try {
          if (link.target_id === SUBGRAPH_OUTPUT_ID) {
            if (!(network instanceof Subgraph)) {
              console.warn(
                "Subgraph output link found in non-subgraph network."
              );
              continue;
            }
            const output2 = network.outputs.at(link.target_slot);
            if (!output2) throw new Error("No subgraph output found for link.");
            const renderLink2 = new ToOutputFromIoNodeLink(
              network,
              network.outputNode,
              output2
            );
            renderLink2.fromDirection = LinkDirection.NONE;
            renderLinks.push(renderLink2);
            continue;
          }
          const renderLink = new MovingOutputLink(
            network,
            link,
            firstReroute,
            LinkDirection.RIGHT
          );
          const mayContinue = this.events.dispatch(
            "before-move-output",
            renderLink
          );
          if (mayContinue === false) continue;
          renderLinks.push(renderLink);
        } catch (error) {
          console.warn(
            `Could not create render link for link id: [${link.id}].`,
            link,
            error
          );
          continue;
        }
      }
    }
    if (renderLinks.length === 0) return;
    state2.draggingExistingLinks = true;
    state2.multi = true;
    state2.connectingTo = "output";
    this.#setLegacyLinks(true);
  }
  /**
   * Drags a new link from an output slot to an input slot.
   * @param network The network that the link being connected belongs to
   * @param node The node the link is being dragged from
   * @param output The output slot that the link is being dragged from
   */
  dragNewFromOutput(network, node2, output, fromReroute) {
    if (this.isConnecting) throw new Error("Already dragging links.");
    const { state: state2 } = this;
    const renderLink = new ToInputRenderLink(network, node2, output, fromReroute);
    this.renderLinks.push(renderLink);
    state2.connectingTo = "input";
    this.#setLegacyLinks(false);
  }
  /**
   * Drags a new link from an input slot to an output slot.
   * @param network The network that the link being connected belongs to
   * @param node The node the link is being dragged from
   * @param input The input slot that the link is being dragged from
   */
  dragNewFromInput(network, node2, input, fromReroute) {
    if (this.isConnecting) throw new Error("Already dragging links.");
    const { state: state2 } = this;
    const renderLink = new ToOutputRenderLink(network, node2, input, fromReroute);
    this.renderLinks.push(renderLink);
    state2.connectingTo = "output";
    this.#setLegacyLinks(true);
  }
  dragNewFromSubgraphInput(network, inputNode, input, fromReroute) {
    if (this.isConnecting) throw new Error("Already dragging links.");
    const renderLink = new ToInputFromIoNodeLink(
      network,
      inputNode,
      input,
      fromReroute
    );
    this.renderLinks.push(renderLink);
    this.state.connectingTo = "input";
    this.#setLegacyLinks(false);
  }
  dragNewFromSubgraphOutput(network, outputNode, output, fromReroute) {
    if (this.isConnecting) throw new Error("Already dragging links.");
    const renderLink = new ToOutputFromIoNodeLink(
      network,
      outputNode,
      output,
      fromReroute
    );
    this.renderLinks.push(renderLink);
    this.state.connectingTo = "output";
    this.#setLegacyLinks(true);
  }
  /**
   * Drags a new link from a reroute to an input slot.
   * @param network The network that the link being connected belongs to
   * @param reroute The reroute that the link is being dragged from
   */
  dragFromReroute(network, reroute) {
    if (this.isConnecting) throw new Error("Already dragging links.");
    const link = reroute.firstLink ?? reroute.firstFloatingLink;
    if (!link) {
      console.warn("No link found for reroute.");
      return;
    }
    if (link.origin_id === SUBGRAPH_INPUT_ID) {
      if (!(network instanceof Subgraph)) {
        console.warn("Subgraph input link found in non-subgraph network.");
        return;
      }
      const input = network.inputs.at(link.origin_slot);
      if (!input) throw new Error("No subgraph input found for link.");
      const renderLink2 = new ToInputFromIoNodeLink(
        network,
        network.inputNode,
        input,
        reroute
      );
      renderLink2.fromDirection = LinkDirection.NONE;
      this.renderLinks.push(renderLink2);
      this.state.connectingTo = "input";
      this.#setLegacyLinks(false);
      return;
    }
    const outputNode = network.getNodeById(link.origin_id);
    if (!outputNode) {
      console.warn("No output node found for link.", link);
      return;
    }
    const outputSlot = outputNode.outputs.at(link.origin_slot);
    if (!outputSlot) {
      console.warn("No output slot found for link.", link);
      return;
    }
    const renderLink = new ToInputRenderLink(
      network,
      outputNode,
      outputSlot,
      reroute
    );
    renderLink.fromDirection = LinkDirection.NONE;
    this.renderLinks.push(renderLink);
    this.state.connectingTo = "input";
    this.#setLegacyLinks(false);
  }
  /**
   * Drags a new link from a reroute to an output slot.
   * @param network The network that the link being connected belongs to
   * @param reroute The reroute that the link is being dragged from
   */
  dragFromRerouteToOutput(network, reroute) {
    if (this.isConnecting) throw new Error("Already dragging links.");
    const link = reroute.firstLink ?? reroute.firstFloatingLink;
    if (!link) {
      console.warn("No link found for reroute.");
      return;
    }
    if (link.target_id === SUBGRAPH_OUTPUT_ID) {
      if (!(network instanceof Subgraph)) {
        console.warn("Subgraph output link found in non-subgraph network.");
        return;
      }
      const output = network.outputs.at(link.target_slot);
      if (!output) throw new Error("No subgraph output found for link.");
      const renderLink2 = new ToOutputFromIoNodeLink(
        network,
        network.outputNode,
        output,
        reroute
      );
      renderLink2.fromDirection = LinkDirection.NONE;
      this.renderLinks.push(renderLink2);
      this.state.connectingTo = "output";
      this.#setLegacyLinks(false);
      return;
    }
    const inputNode = network.getNodeById(link.target_id);
    if (!inputNode) {
      console.warn("No input node found for link.", link);
      return;
    }
    const inputSlot = inputNode.inputs.at(link.target_slot);
    if (!inputSlot) {
      console.warn("No input slot found for link.", link);
      return;
    }
    const renderLink = new ToOutputFromRerouteLink(
      network,
      inputNode,
      inputSlot,
      reroute,
      this
    );
    renderLink.fromDirection = LinkDirection.LEFT;
    this.renderLinks.push(renderLink);
    this.state.connectingTo = "output";
    this.#setLegacyLinks(true);
  }
  dragFromLinkSegment(network, linkSegment) {
    if (this.isConnecting) throw new Error("Already dragging links.");
    const { state: state2 } = this;
    if (linkSegment.origin_id == null || linkSegment.origin_slot == null) return;
    const node2 = network.getNodeById(linkSegment.origin_id);
    if (!node2) return;
    const slot = node2.outputs.at(linkSegment.origin_slot);
    if (!slot) return;
    const reroute = network.getReroute(linkSegment.parentId);
    const renderLink = new ToInputRenderLink(network, node2, slot, reroute);
    renderLink.fromDirection = LinkDirection.NONE;
    this.renderLinks.push(renderLink);
    state2.connectingTo = "input";
    this.#setLegacyLinks(false);
  }
  /**
   * Connects the links being dropped
   * @param event Contains the drop location, in canvas space
   */
  dropLinks(locator, event) {
    if (!this.isConnecting) {
      const mayContinue = this.events.dispatch("before-drop-links", {
        renderLinks: this.renderLinks,
        event
      });
      if (mayContinue === false) return;
    }
    try {
      const { canvasX, canvasY } = event;
      const ioNode = locator.getIoNodeOnPos?.(canvasX, canvasY);
      if (ioNode) {
        this.dropOnIoNode(ioNode, event);
        return;
      }
      const node2 = locator.getNodeOnPos(canvasX, canvasY) ?? void 0;
      if (node2) {
        this.dropOnNode(node2, event);
      } else {
        const reroute = locator.getRerouteOnPos(canvasX, canvasY);
        if (reroute && this.isRerouteValidDrop(reroute)) {
          this.dropOnReroute(reroute, event);
        } else {
          this.dropOnNothing(event);
        }
      }
    } finally {
      this.events.dispatch("after-drop-links", {
        renderLinks: this.renderLinks,
        event
      });
    }
  }
  dropOnIoNode(ioNode, event) {
    const { renderLinks, state: state2 } = this;
    const { connectingTo } = state2;
    const { canvasX, canvasY } = event;
    if (connectingTo === "input" && ioNode instanceof SubgraphOutputNode) {
      const output = ioNode.getSlotInPosition(canvasX, canvasY);
      if (!output) {
        this.dropOnNothing(event);
        return;
      }
      let targetSlot = output;
      for (const link of renderLinks) {
        link.connectToSubgraphOutput(targetSlot, this.events);
        if (output instanceof EmptySubgraphOutput && ioNode.slots.length > 0) {
          const createdSlot = ioNode.slots[ioNode.slots.length - 1];
          const nextLink = renderLinks[renderLinks.indexOf(link) + 1];
          if (nextLink && link.fromSlot.type === nextLink.fromSlot.type) {
            targetSlot = createdSlot;
          } else {
            targetSlot = output;
          }
        }
      }
    } else if (connectingTo === "output" && ioNode instanceof SubgraphInputNode) {
      const input = ioNode.getSlotInPosition(canvasX, canvasY);
      if (!input) {
        this.dropOnNothing(event);
        return;
      }
      let targetSlot = input;
      for (const link of renderLinks) {
        if ("canConnectToSubgraphInput" in link && !link.canConnectToSubgraphInput(targetSlot)) {
          console.warn(
            "Invalid connection type",
            link.fromSlot.type,
            "->",
            targetSlot.type
          );
          continue;
        }
        link.connectToSubgraphInput(targetSlot, this.events);
        if (input instanceof EmptySubgraphInput && ioNode.slots.length > 0) {
          const createdSlot = ioNode.slots[ioNode.slots.length - 1];
          const nextLink = renderLinks[renderLinks.indexOf(link) + 1];
          if (nextLink && link.fromSlot.type === nextLink.fromSlot.type) {
            targetSlot = createdSlot;
          } else {
            targetSlot = input;
          }
        }
      }
    } else {
      console.error(
        "Invalid connectingTo state &/ ioNode",
        connectingTo,
        ioNode
      );
    }
  }
  dropOnNode(node2, event) {
    const { renderLinks, state: state2 } = this;
    const { connectingTo } = state2;
    const { canvasX, canvasY } = event;
    if (renderLinks.every((link) => link.node === node2)) return;
    if (connectingTo === "output") {
      const output = node2.getOutputOnPos([canvasX, canvasY]);
      if (output) {
        this.#dropOnOutput(node2, output);
      } else {
        this.connectToNode(node2, event);
      }
    } else if (connectingTo === "input") {
      const input = node2.getInputOnPos([canvasX, canvasY]);
      const inputOrSocket = input ?? node2.getSlotFromWidget(this.overWidget);
      if (inputOrSocket) {
        this.#dropOnInput(node2, inputOrSocket);
      } else {
        this.connectToNode(node2, event);
      }
    }
  }
  dropOnReroute(reroute, event) {
    const mayContinue = this.events.dispatch("dropped-on-reroute", {
      reroute,
      event
    });
    if (mayContinue === false) return;
    if (this.state.connectingTo === "input") {
      if (this.renderLinks.length !== 1)
        throw new Error(
          `Attempted to connect ${this.renderLinks.length} input links to a reroute.`
        );
      const renderLink = this.renderLinks[0];
      this._connectOutputToReroute(reroute, renderLink);
      return;
    }
    for (const link of this.renderLinks) {
      if (link.toType !== "output") continue;
      const result = reroute.findSourceOutput();
      if (!result) continue;
      const { node: node2, output } = result;
      if (!link.canConnectToOutput(node2, output)) continue;
      link.connectToRerouteOutput(reroute, node2, output, this.events);
    }
  }
  /** @internal Temporary workaround - requires refactor. */
  _connectOutputToReroute(reroute, renderLink) {
    const results = reroute.findTargetInputs();
    if (!results?.length) return;
    const maybeReroutes = reroute.getReroutes();
    if (maybeReroutes === null) throw new Error("Reroute loop detected.");
    const originalReroutes = maybeReroutes.slice(0, -1).reverse();
    if (renderLink instanceof ToInputRenderLink) {
      const { node: node2, fromSlot, fromSlotIndex, fromReroute } = renderLink;
      reroute.setFloatingLinkOrigin(node2, fromSlot, fromSlotIndex);
      if (fromReroute != null) {
        for (const originalReroute of originalReroutes) {
          if (originalReroute.id === fromReroute.id) break;
          for (const linkId of reroute.floatingLinkIds) {
            originalReroute.floatingLinkIds.delete(linkId);
          }
        }
      }
    }
    const filtered = results.filter(
      (result) => renderLink.toType === "input" && canConnectInputLinkToReroute(
        renderLink,
        result.node,
        result.input,
        reroute
      )
    );
    for (const result of filtered) {
      renderLink.connectToRerouteInput(
        reroute,
        result,
        this.events,
        originalReroutes
      );
    }
    return;
  }
  dropOnNothing(event) {
    const mayContinue = this.events.dispatch("dropped-on-canvas", event);
    if (mayContinue === false) return;
    this.disconnectLinks();
  }
  /**
   * Disconnects all moving links.
   * @remarks This is called when the links are dropped on the canvas.
   * May be called by consumers to e.g. drag links into a bin / void.
   */
  disconnectLinks() {
    for (const link of this.renderLinks) {
      if (link instanceof MovingLinkBase || link instanceof ToInputFromIoNodeLink) {
        link.disconnect();
      }
    }
  }
  /**
   * Connects the links being dropped onto a node to the first matching slot.
   * @param node The node that the links are being dropped on
   * @param event Contains the drop location, in canvas space
   */
  connectToNode(node2, event) {
    const {
      state: { connectingTo }
    } = this;
    const mayContinue = this.events.dispatch("dropped-on-node", { node: node2, event });
    if (mayContinue === false) return;
    const firstLink = this.renderLinks[0];
    if (!firstLink) return;
    if (connectingTo === "output") {
      const output = node2.findOutputByType(firstLink.fromSlot.type)?.slot;
      if (output === void 0) {
        console.warn(
          `Could not find slot for link type: [${firstLink.fromSlot.type}].`
        );
        return;
      }
      this.#dropOnOutput(node2, output);
    } else if (connectingTo === "input") {
      const input = node2.findInputByType(firstLink.fromSlot.type)?.slot;
      if (input === void 0) {
        console.warn(
          `Could not find slot for link type: [${firstLink.fromSlot.type}].`
        );
        return;
      }
      this.#dropOnInput(node2, input);
    }
  }
  #dropOnInput(node2, input) {
    for (const link of this.renderLinks) {
      if (!link.canConnectToInput(node2, input)) continue;
      link.connectToInput(node2, input, this.events);
    }
  }
  #dropOnOutput(node2, output) {
    for (const link of this.renderLinks) {
      if (!link.canConnectToOutput(node2, output)) {
        if (link instanceof MovingOutputLink && link.link.parentId !== void 0) {
          link.outputNode.connectSlots(
            link.outputSlot,
            link.inputNode,
            link.inputSlot,
            void 0
          );
        }
        continue;
      }
      link.connectToOutput(node2, output, this.events);
    }
  }
  isInputValidDrop(node2, input) {
    return this.renderLinks.some((link) => link.canConnectToInput(node2, input));
  }
  isNodeValidDrop(node2) {
    if (this.state.connectingTo === "output") {
      return node2.outputs.some(
        (output) => this.renderLinks.some((link) => link.canConnectToOutput(node2, output))
      );
    }
    return node2.inputs.some(
      (input) => this.renderLinks.some((link) => link.canConnectToInput(node2, input))
    );
  }
  isSubgraphInputValidDrop(input) {
    return this.renderLinks.some(
      (link) => "canConnectToSubgraphInput" in link && link.canConnectToSubgraphInput(input)
    );
  }
  /**
   * Checks if a reroute is a valid drop target for any of the links being connected.
   * @param reroute The reroute that would be dropped on.
   * @returns `true` if any of the current links being connected are valid for the given reroute.
   */
  isRerouteValidDrop(reroute) {
    if (this.state.connectingTo === "input") {
      const results = reroute.findTargetInputs();
      if (!results?.length) return false;
      for (const { node: node2, input } of results) {
        for (const renderLink of this.renderLinks) {
          if (renderLink.toType !== "input") continue;
          if (canConnectInputLinkToReroute(renderLink, node2, input, reroute))
            return true;
        }
      }
    } else {
      const result = reroute.findSourceOutput();
      if (!result) return false;
      const { node: node2, output } = result;
      for (const renderLink of this.renderLinks) {
        if (renderLink.toType !== "output") continue;
        if (!renderLink.canConnectToReroute(reroute)) continue;
        if (renderLink.canConnectToOutput(node2, output)) return true;
      }
    }
    return false;
  }
  /** Sets connecting_links, used by some extensions still. */
  #setLegacyLinks(fromSlotIsInput) {
    const links = this.renderLinks.map((link) => {
      const input = fromSlotIsInput ? link.fromSlot : null;
      const output = fromSlotIsInput ? null : link.fromSlot;
      const afterRerouteId = link instanceof MovingLinkBase ? link.link?.parentId : link.fromReroute?.id;
      return {
        node: link.node,
        slot: link.fromSlotIndex,
        input,
        output,
        pos: link.fromPos,
        afterRerouteId
      };
    });
    this.#setConnectingLinks(links);
  }
  /**
   * Exports the current state of the link connector.
   * @param network The network that the links being connected belong to.
   * @returns A POJO with the state of the link connector, links being connected, and their network.
   * @remarks Other than {@link network}, all properties are shallow cloned.
   */
  export(network) {
    return {
      renderLinks: [...this.renderLinks],
      inputLinks: [...this.inputLinks],
      outputLinks: [...this.outputLinks],
      floatingLinks: [...this.floatingLinks],
      state: { ...this.state },
      network
    };
  }
  /**
   * Adds an event listener that will be automatically removed when the reset event is fired.
   * @param eventName The event to listen for.
   * @param listener The listener to call when the event is fired.
   */
  listenUntilReset(eventName, listener, options) {
    this.events.addEventListener(eventName, listener, options);
    this.events.addEventListener(
      "reset",
      () => this.events.removeEventListener(eventName, listener),
      { once: true }
    );
  }
  /**
   * Resets everything to its initial state.
   *
   * Effectively cancels moving or connecting links.
   */
  reset(force = false) {
    const mayContinue = this.events.dispatch("reset", force);
    if (mayContinue === false) return;
    const {
      state: state2,
      outputLinks,
      inputLinks,
      hiddenReroutes,
      renderLinks,
      floatingLinks
    } = this;
    if (!force && state2.connectingTo === void 0) return;
    state2.connectingTo = void 0;
    for (const link of outputLinks) delete link._dragging;
    for (const link of inputLinks) delete link._dragging;
    for (const link of floatingLinks) delete link._dragging;
    for (const reroute of hiddenReroutes) delete reroute._dragging;
    renderLinks.length = 0;
    inputLinks.length = 0;
    outputLinks.length = 0;
    floatingLinks.length = 0;
    hiddenReroutes.clear();
    state2.multi = false;
    state2.draggingExistingLinks = false;
    state2.snapLinksPos = void 0;
  }
}
function canConnectInputLinkToReroute(link, inputNode, input, reroute) {
  const { fromReroute } = link;
  if (!link.canConnectToInput(inputNode, input) || // Would result in no change
  fromReroute?.id === reroute.id || // Cannot connect from child to parent reroute
  fromReroute?.getReroutes()?.includes(reroute)) {
    return false;
  }
  if (link instanceof ToInputRenderLink) {
    if (reroute.parentId == null) {
      if (reroute.firstLink?.hasOrigin(link.node.id, link.fromSlotIndex))
        return false;
    } else if (link.fromReroute?.id === reroute.parentId) {
      return false;
    }
  }
  return true;
}
__name(canConnectInputLinkToReroute, "canConnectInputLinkToReroute");
class RecursionError extends Error {
  static {
    __name(this, "RecursionError");
  }
  constructor(subject) {
    super(subject);
    this.name = "RecursionError";
  }
}
class InvalidLinkError extends Error {
  static {
    __name(this, "InvalidLinkError");
  }
  constructor(message = "Attempted to access a link that was invalid.", cause) {
    super(message, { cause });
    this.name = "InvalidLinkError";
  }
}
class SlotIndexError extends Error {
  static {
    __name(this, "SlotIndexError");
  }
  constructor(message = "Attempted to access a slot that was out of bounds.", cause) {
    super(message, { cause });
    this.name = "SlotIndexError";
  }
}
class ExecutableNodeDTO {
  static {
    __name(this, "ExecutableNodeDTO");
  }
  constructor(node2, subgraphNodePath, nodesByExecutionId, subgraphNode) {
    this.node = node2;
    this.subgraphNodePath = subgraphNodePath;
    this.nodesByExecutionId = nodesByExecutionId;
    this.subgraphNode = subgraphNode;
    if (!node2.graph) throw new NullGraphError();
    this.#id = [...this.subgraphNodePath, this.node.id].join(":");
    this.graph = node2.graph;
    this.inputs = this.node.inputs.map((x2) => ({
      linkId: x2.link,
      name: x2.name,
      type: x2.type
    }));
    if (this.node.applyToGraph) {
      this.applyToGraph = (...args) => this.node.applyToGraph?.(...args);
    }
  }
  /** The graph that this node is a part of. */
  graph;
  inputs;
  /** Backing field for {@link id}. */
  #id;
  /**
   * The path to the actual node through subgraph instances, represented as a list of all subgraph node IDs (instances),
   * followed by the actual original node ID within the subgraph. Each segment is separated by `:`.
   *
   * e.g. `1:2:3`:
   * - `1` is the node ID of the first subgraph node in the parent workflow
   * - `2` is the node ID of the second subgraph node in the first subgraph
   * - `3` is the node ID of the actual node in the subgraph definition
   */
  get id() {
    return this.#id;
  }
  get type() {
    return this.node.type;
  }
  get title() {
    return this.node.title;
  }
  get mode() {
    return this.node.mode;
  }
  get comfyClass() {
    return this.node.comfyClass;
  }
  get isVirtualNode() {
    return this.node.isVirtualNode;
  }
  get widgets() {
    return this.node.widgets;
  }
  get subgraphId() {
    return this.subgraphNode?.subgraph.id;
  }
  /** Returns either the DTO itself, or the DTOs of the inner nodes of the subgraph. */
  getInnerNodes() {
    return this.node.isSubgraphNode() ? this.node.getInnerNodes(this.nodesByExecutionId, this.subgraphNodePath) : [this];
  }
  /**
   * Resolves the executable node & link IDs for a given input slot.
   * @param slot The slot index of the input.
   * @param visited Leave empty unless overriding this method.
   * A set of unique IDs, used to guard against infinite recursion.
   * If overriding, ensure that the set is passed on all recursive calls.
   * @returns The node and the origin ID / slot index of the output.
   */
  resolveInput(slot, visited = /* @__PURE__ */ new Set(), type) {
    const uniqueId = `${this.subgraphNode?.subgraph.id}:${this.node.id}[I]${slot}`;
    if (visited.has(uniqueId)) {
      const nodeInfo = `${this.node.id}${this.node.title ? ` (${this.node.title})` : ""}`;
      const pathInfo = this.subgraphNodePath.length > 0 ? ` at path ${this.subgraphNodePath.join(":")}` : "";
      throw new RecursionError(
        `Circular reference detected while resolving input ${slot} of node ${nodeInfo}${pathInfo}. This creates an infinite loop in link resolution. UniqueID: [${uniqueId}]`
      );
    }
    visited.add(uniqueId);
    const input = this.inputs.at(slot);
    if (!input)
      throw new SlotIndexError(
        `No input found for flattened id [${this.id}] slot [${slot}]`
      );
    if (input.linkId == null) return;
    const link = this.graph.getLink(input.linkId);
    if (!link)
      throw new InvalidLinkError(
        `No link found in parent graph for id [${this.id}] slot [${slot}] ${input.name}`
      );
    const { subgraphNode } = this;
    if (subgraphNode && link.originIsIoNode) {
      const subgraphNodeInput = subgraphNode.inputs.at(link.origin_slot);
      if (!subgraphNodeInput)
        throw new SlotIndexError(
          `No input found for slot [${link.origin_slot}] ${input.name}`
        );
      const linkId = subgraphNodeInput.link;
      if (linkId == null) {
        const widget = subgraphNode.getWidgetFromSlot(subgraphNodeInput);
        if (!widget) return;
        return {
          node: this,
          origin_id: this.id,
          origin_slot: -1,
          widgetInfo: { value: widget.value }
        };
      }
      const outerLink = subgraphNode.graph.getLink(linkId);
      if (!outerLink)
        throw new InvalidLinkError(
          `No outer link found for slot [${link.origin_slot}] ${input.name}`
        );
      const subgraphNodeExecutionId = this.subgraphNodePath.join(":");
      const subgraphNodeDto = this.nodesByExecutionId.get(
        subgraphNodeExecutionId
      );
      if (!subgraphNodeDto)
        throw new Error(
          `No subgraph node DTO found for id [${subgraphNodeExecutionId}]`
        );
      return subgraphNodeDto.resolveInput(outerLink.target_slot, visited);
    }
    const outputNode = this.graph.getNodeById(link.origin_id);
    if (!outputNode)
      throw new InvalidLinkError(
        `No input node found for id [${this.id}] slot [${slot}] ${input.name}`
      );
    const outputNodeExecutionId = [
      ...this.subgraphNodePath,
      outputNode.id
    ].join(":");
    const outputNodeDto = this.nodesByExecutionId.get(outputNodeExecutionId);
    if (!outputNodeDto)
      throw new Error(
        `No output node DTO found for id [${outputNodeExecutionId}]`
      );
    return outputNodeDto.resolveOutput(
      link.origin_slot,
      type ?? input.type,
      visited
    );
  }
  /**
   * Determines whether this output is a valid endpoint for a link (non-virtual, non-bypass).
   * @param slot The slot index of the output.
   * @param type The type of the input
   * @param visited A set of unique IDs to guard against infinite recursion. See {@link resolveInput}.
   * @returns The node and the origin ID / slot index of the output.
   */
  resolveOutput(slot, type, visited) {
    const uniqueId = `${this.subgraphNode?.subgraph.id}:${this.node.id}[O]${slot}`;
    if (visited.has(uniqueId)) {
      const nodeInfo = `${this.node.id}${this.node.title ? ` (${this.node.title})` : ""}`;
      const pathInfo = this.subgraphNodePath.length > 0 ? ` at path ${this.subgraphNodePath.join(":")}` : "";
      throw new RecursionError(
        `Circular reference detected while resolving output ${slot} of node ${nodeInfo}${pathInfo}. This creates an infinite loop in link resolution. UniqueID: [${uniqueId}]`
      );
    }
    visited.add(uniqueId);
    if (this.mode === LGraphEventMode.BYPASS) {
      const matchingIndex = this.#getBypassSlotIndex(slot, type);
      if (matchingIndex === -1) {
        console.warn(
          `[ExecutableNodeDTO.resolveOutput] No input types match type [${type}] for id [${this.id}] slot [${slot}]`,
          this
        );
        return;
      }
      return this.resolveInput(matchingIndex, visited);
    }
    const { node: node2 } = this;
    if (node2.isSubgraphNode())
      return this.#resolveSubgraphOutput(slot, type, visited);
    if (node2.isVirtualNode) {
      const virtualLink = this.node.getInputLink(slot);
      if (virtualLink) {
        const { inputNode } = virtualLink.resolve(this.graph);
        if (!inputNode)
          throw new InvalidLinkError(
            `Virtual node failed to resolve parent [${this.id}] slot [${slot}]`
          );
        const inputNodeExecutionId = [
          ...this.subgraphNodePath,
          inputNode.id
        ].join(":");
        const inputNodeDto = this.nodesByExecutionId.get(inputNodeExecutionId);
        if (!inputNodeDto)
          throw new Error(`No input node DTO found for id [${inputNode.id}]`);
        return inputNodeDto.resolveInput(virtualLink.target_slot, visited, type);
      }
      return;
    }
    return {
      node: this,
      origin_id: this.id,
      origin_slot: slot
    };
  }
  /**
   * Finds the index of the input slot on this node that matches the given output {@link slot} index.
   * Used when bypassing nodes.
   * @param slot The output slot index on this node
   * @param type The type of the final target input (so type list matches are accurate)
   * @returns The index of the input slot on this node, otherwise `-1`.
   */
  #getBypassSlotIndex(slot, type) {
    const { inputs } = this;
    const oppositeInput = inputs[slot];
    const outputType = this.node.outputs[slot].type;
    if (type === "*" || type === "") {
      return inputs.length > slot ? slot : 0;
    }
    if (oppositeInput && LiteGraph.isValidConnection(oppositeInput.type, outputType) && LiteGraph.isValidConnection(oppositeInput.type, type)) {
      return slot;
    }
    const exactMatch = inputs.findIndex((input) => input.type === type);
    if (exactMatch !== -1) return exactMatch;
    return inputs.findIndex(
      (input) => LiteGraph.isValidConnection(input.type, outputType) && LiteGraph.isValidConnection(input.type, type)
    );
  }
  /**
   * Resolves the link inside a subgraph node, from the subgraph IO node to the node inside the subgraph.
   * @param slot The slot index of the output on the subgraph node.
   * @param visited A set of unique IDs to guard against infinite recursion. See {@link resolveInput}.
   * @returns A DTO for the node, and the origin ID / slot index of the output.
   */
  #resolveSubgraphOutput(slot, type, visited) {
    const { node: node2 } = this;
    const output = node2.outputs.at(slot);
    if (!output)
      throw new SlotIndexError(
        `No output found for flattened id [${this.id}] slot [${slot}]`
      );
    if (!node2.isSubgraphNode())
      throw new TypeError(`Node is not a subgraph node: ${node2.id}`);
    const innerResolved = node2.resolveSubgraphOutputLink(slot);
    if (!innerResolved) return;
    const innerNode = innerResolved.outputNode;
    if (!innerNode)
      throw new Error(
        `No output node found for id [${this.id}] slot [${slot}] ${output.name}`
      );
    const innerNodeExecutionId = [
      ...this.subgraphNodePath,
      node2.id,
      innerNode.id
    ].join(":");
    const innerNodeDto = this.nodesByExecutionId.get(innerNodeExecutionId);
    if (!innerNodeDto)
      throw new Error(
        `No inner node DTO found for id [${innerNodeExecutionId}]`
      );
    return innerNodeDto.resolveOutput(
      innerResolved.link.origin_slot,
      type,
      visited
    );
  }
}
const workflowSvg = new Image();
workflowSvg.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'%3E%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='white' stroke-linecap='round' stroke-width='1.3' d='M9.18613 3.09999H6.81377M9.18613 12.9H7.55288c-3.08678 0-5.35171-2.99581-4.60305-6.08843l.3054-1.26158M14.7486 2.1721l-.5931 2.45c-.132.54533-.6065.92789-1.1508.92789h-2.2993c-.77173 0-1.33797-.74895-1.1508-1.5221l.5931-2.45c.132-.54533.6065-.9279 1.1508-.9279h2.2993c.7717 0 1.3379.74896 1.1508 1.52211Zm-8.3033 0-.59309 2.45c-.13201.54533-.60646.92789-1.15076.92789H2.4021c-.7717 0-1.33793-.74895-1.15077-1.5221l.59309-2.45c.13201-.54533.60647-.9279 1.15077-.9279h2.29935c.77169 0 1.33792.74896 1.15076 1.52211Zm8.3033 9.8-.5931 2.45c-.132.5453-.6065.9279-1.1508.9279h-2.2993c-.77173 0-1.33797-.749-1.1508-1.5221l.5931-2.45c.132-.5453.6065-.9279 1.1508-.9279h2.2993c.7717 0 1.3379.7489 1.1508 1.5221Z'/%3E%3C/svg%3E %3C/svg%3E";
class SubgraphNode extends LGraphNode {
  static {
    __name(this, "SubgraphNode");
  }
  constructor(graph, subgraph, instanceData) {
    super(subgraph.name, subgraph.id);
    this.graph = graph;
    this.subgraph = subgraph;
    const subgraphEvents = this.subgraph.events;
    const { signal } = this.#eventAbortController;
    subgraphEvents.addEventListener(
      "input-added",
      (e2) => {
        const subgraphInput = e2.detail.input;
        const { name: name2, type } = subgraphInput;
        const existingInput = this.inputs.find((i) => i.name == name2);
        if (existingInput) {
          const linkId = subgraphInput.linkIds[0];
          const { inputNode, input: input2 } = subgraph.links[linkId].resolve(subgraph);
          const widget = inputNode?.widgets?.find?.((w) => w.name == name2);
          if (widget)
            this.#setWidget(subgraphInput, existingInput, widget, input2?.widget);
          return;
        }
        const input = this.addInput(name2, type);
        this.#addSubgraphInputListeners(subgraphInput, input);
      },
      { signal }
    );
    subgraphEvents.addEventListener(
      "removing-input",
      (e2) => {
        const widget = e2.detail.input._widget;
        if (widget) this.ensureWidgetRemoved(widget);
        this.removeInput(e2.detail.index);
        this.setDirtyCanvas(true, true);
      },
      { signal }
    );
    subgraphEvents.addEventListener(
      "output-added",
      (e2) => {
        const { name: name2, type } = e2.detail.output;
        this.addOutput(name2, type);
      },
      { signal }
    );
    subgraphEvents.addEventListener(
      "removing-output",
      (e2) => {
        this.removeOutput(e2.detail.index);
        this.setDirtyCanvas(true, true);
      },
      { signal }
    );
    subgraphEvents.addEventListener(
      "renaming-input",
      (e2) => {
        const { index: index2, newName } = e2.detail;
        const input = this.inputs.at(index2);
        if (!input) throw new Error("Subgraph input not found");
        input.label = newName;
        if (input._widget) {
          input._widget.label = newName;
        }
      },
      { signal }
    );
    subgraphEvents.addEventListener(
      "renaming-output",
      (e2) => {
        const { index: index2, newName } = e2.detail;
        const output = this.outputs.at(index2);
        if (!output) throw new Error("Subgraph output not found");
        output.label = newName;
      },
      { signal }
    );
    this.type = subgraph.id;
    this.configure(instanceData);
    this.addTitleButton({
      name: "enter_subgraph",
      text: "",
      // Unicode for pi-window-maximize
      yOffset: 0,
      // No vertical offset needed, button is centered
      xOffset: -10,
      fontSize: 16
    });
  }
  type;
  isVirtualNode = true;
  get rootGraph() {
    return this.graph.rootGraph;
  }
  get displayType() {
    return "Subgraph node";
  }
  isSubgraphNode() {
    return true;
  }
  widgets = [];
  /** Manages lifecycle of all subgraph event listeners */
  #eventAbortController = new AbortController();
  onTitleButtonClick(button, canvas2) {
    if (button.name === "enter_subgraph") {
      canvas2.openSubgraph(this.subgraph, this);
    } else {
      super.onTitleButtonClick(button, canvas2);
    }
  }
  #addSubgraphInputListeners(subgraphInput, input) {
    if (input._listenerController && typeof input._listenerController.abort === "function") {
      input._listenerController.abort();
    }
    input._listenerController = new AbortController();
    const { signal } = input._listenerController;
    subgraphInput.events.addEventListener(
      "input-connected",
      (e2) => {
        if (input._widget) return;
        const widget = subgraphInput._widget;
        if (!widget) return;
        const widgetLocator = e2.detail.input.widget;
        this.#setWidget(subgraphInput, input, widget, widgetLocator);
      },
      { signal }
    );
    subgraphInput.events.addEventListener(
      "input-disconnected",
      () => {
        const connectedWidgets = subgraphInput.getConnectedWidgets();
        if (connectedWidgets.length > 0) return;
        this.removeWidgetByName(input.name);
        delete input.pos;
        delete input.widget;
        input._widget = void 0;
      },
      { signal }
    );
  }
  configure(info) {
    for (const input of this.inputs) {
      if (input._listenerController && typeof input._listenerController.abort === "function") {
        input._listenerController.abort();
      }
    }
    this.inputs.length = 0;
    this.inputs.push(
      ...this.subgraph.inputNode.slots.map(
        (slot) => new NodeInputSlot(
          {
            name: slot.name,
            localized_name: slot.localized_name,
            label: slot.label,
            type: slot.type,
            link: null
          },
          this
        )
      )
    );
    this.outputs.length = 0;
    this.outputs.push(
      ...this.subgraph.outputNode.slots.map(
        (slot) => new NodeOutputSlot(
          {
            name: slot.name,
            localized_name: slot.localized_name,
            label: slot.label,
            type: slot.type,
            links: null
          },
          this
        )
      )
    );
    super.configure(info);
  }
  _internalConfigureAfterSlots() {
    this.widgets.length = 0;
    for (const input of this.inputs) {
      const subgraphInput = this.subgraph.inputNode.slots.find(
        (slot) => slot.name === input.name
      );
      if (!subgraphInput) {
        console.warn(
          `[SubgraphNode.configure] No subgraph input found for input ${input.name}, skipping`
        );
        continue;
      }
      this.#addSubgraphInputListeners(subgraphInput, input);
      for (const linkId of subgraphInput.linkIds) {
        const link = this.subgraph.getLink(linkId);
        if (!link) {
          console.warn(
            `[SubgraphNode.configure] No link found for link ID ${linkId}`,
            this
          );
          continue;
        }
        const { inputNode } = link.resolve(this.subgraph);
        if (!inputNode) {
          console.warn("Failed to resolve inputNode", link, this);
          continue;
        }
        const targetInput = inputNode.inputs.find((inp) => inp.link === linkId);
        if (!targetInput) {
          console.warn("Failed to find corresponding input", link, inputNode);
          continue;
        }
        const widget = inputNode.getWidgetFromSlot(targetInput);
        if (!widget) continue;
        this.#setWidget(subgraphInput, input, widget, targetInput.widget);
        break;
      }
    }
  }
  #setWidget(subgraphInput, input, widget, inputWidget) {
    const promotedWidget = toConcreteWidget(widget, this).createCopyForNode(
      this
    );
    if (widget instanceof AssetWidget)
      promotedWidget.options.nodeType ??= widget.node.type;
    Object.assign(promotedWidget, {
      get name() {
        return subgraphInput.name;
      },
      set name(value) {
        console.warn(
          "Promoted widget: setting name is not allowed",
          this,
          value
        );
      },
      get localized_name() {
        return subgraphInput.localized_name;
      },
      set localized_name(value) {
        console.warn(
          "Promoted widget: setting localized_name is not allowed",
          this,
          value
        );
      },
      get label() {
        return subgraphInput.label;
      },
      set label(value) {
        console.warn(
          "Promoted widget: setting label is not allowed",
          this,
          value
        );
      },
      get tooltip() {
        return widget.tooltip;
      },
      set tooltip(value) {
        console.warn(
          "Promoted widget: setting tooltip is not allowed",
          this,
          value
        );
      }
    });
    const widgetCount = this.inputs.filter((i) => i.widget).length;
    this.widgets.splice(widgetCount, 0, promotedWidget);
    this.subgraph.events.dispatch("widget-promoted", {
      widget: promotedWidget,
      subgraphNode: this
    });
    input.widget ??= { name: subgraphInput.name };
    input.widget.name = subgraphInput.name;
    if (inputWidget) Object.setPrototypeOf(input.widget, inputWidget);
    input._widget = promotedWidget;
  }
  /**
   * Ensures the subgraph slot is in the params before adding the input as normal.
   * @param name The name of the input slot.
   * @param type The type of the input slot.
   * @param inputProperties Properties that are directly assigned to the created input. Default: a new, empty object.
   * @returns The new input slot.
   * @remarks Assertion is required to instantiate empty generic POJO.
   */
  addInput(name2, type, inputProperties = {}) {
    return super.addInput(name2, type, inputProperties);
  }
  getInputLink(slot) {
    const innerLink = this.subgraph.outputNode.slots[slot].getLinks().at(0);
    if (!innerLink) {
      console.warn(
        `SubgraphNode.getInputLink: no inner link found for slot ${slot}`
      );
      return null;
    }
    const newLink = LLink.create(innerLink);
    newLink.origin_id = `${this.id}:${innerLink.origin_id}`;
    newLink.origin_slot = innerLink.origin_slot;
    return newLink;
  }
  /**
   * Finds the internal links connected to the given input slot inside the subgraph, and resolves the nodes / slots.
   * @param slot The slot index
   * @returns The resolved connections, or undefined if no input node is found.
   * @remarks This is used to resolve the input links when dragging a link from a subgraph input slot.
   */
  resolveSubgraphInputLinks(slot) {
    const inputSlot = this.subgraph.inputNode.slots[slot];
    const innerLinks = inputSlot.getLinks();
    if (innerLinks.length === 0) {
      console.warn(
        `[SubgraphNode.resolveSubgraphInputLinks] No inner links found for input slot [${slot}] ${inputSlot.name}`,
        this
      );
      return [];
    }
    return innerLinks.map((link) => link.resolve(this.subgraph));
  }
  /**
   * Finds the internal link connected to the given output slot inside the subgraph, and resolves the nodes / slots.
   * @param slot The slot index
   * @returns The output node if found, otherwise undefined.
   */
  resolveSubgraphOutputLink(slot) {
    const outputSlot = this.subgraph.outputNode.slots[slot];
    const innerLink = outputSlot.getLinks().at(0);
    if (innerLink) {
      return innerLink.resolve(this.subgraph);
    }
    console.warn(
      `[SubgraphNode.resolveSubgraphOutputLink] No inner link found for output slot [${slot}] ${outputSlot.name}`,
      this
    );
  }
  /** @internal Used to flatten the subgraph before execution. */
  getInnerNodes(executableNodes, subgraphNodePath = [], nodes = [], visited = /* @__PURE__ */ new Set()) {
    if (visited.has(this)) {
      const nodeInfo = `${this.id}${this.title ? ` (${this.title})` : ""}`;
      const subgraphInfo = `'${this.subgraph.name || "Unnamed Subgraph"}'`;
      const depth = subgraphNodePath.length;
      throw new RecursionError(
        `Circular reference detected at depth ${depth} in node ${nodeInfo} of subgraph ${subgraphInfo}. This creates an infinite loop in the subgraph hierarchy.`
      );
    }
    visited.add(this);
    const subgraphInstanceIdPath = [...subgraphNodePath, this.id];
    const parentSubgraphNode = this.graph.rootGraph.resolveSubgraphIdPath(subgraphNodePath).at(-1);
    const subgraphNodeDto = new ExecutableNodeDTO(
      this,
      subgraphNodePath,
      executableNodes,
      parentSubgraphNode
    );
    executableNodes.set(subgraphNodeDto.id, subgraphNodeDto);
    for (const node2 of this.subgraph.nodes) {
      if ("getInnerNodes" in node2 && node2.getInnerNodes) {
        node2.getInnerNodes(
          executableNodes,
          subgraphInstanceIdPath,
          nodes,
          new Set(visited)
        );
      } else {
        const aVeryRealNode = new ExecutableNodeDTO(
          node2,
          subgraphInstanceIdPath,
          executableNodes,
          this
        );
        executableNodes.set(aVeryRealNode.id, aVeryRealNode);
        nodes.push(aVeryRealNode);
      }
    }
    return nodes;
  }
  removeWidgetByName(name2) {
    const widget = this.widgets.find((w) => w.name === name2);
    if (widget) {
      this.subgraph.events.dispatch("widget-demoted", {
        widget,
        subgraphNode: this
      });
    }
    super.removeWidgetByName(name2);
  }
  ensureWidgetRemoved(widget) {
    if (this.widgets.includes(widget)) {
      this.subgraph.events.dispatch("widget-demoted", {
        widget,
        subgraphNode: this
      });
    }
    super.ensureWidgetRemoved(widget);
  }
  onRemoved() {
    this.#eventAbortController.abort();
    for (const widget of this.widgets) {
      if ("isProxyWidget" in widget && widget.isProxyWidget) continue;
      this.subgraph.events.dispatch("widget-demoted", {
        widget,
        subgraphNode: this
      });
    }
    for (const input of this.inputs) {
      if (input._listenerController && typeof input._listenerController.abort === "function") {
        input._listenerController.abort();
      }
    }
  }
  drawTitleBox(ctx, {
    scale,
    low_quality = false,
    title_height = LiteGraph.NODE_TITLE_HEIGHT,
    box_size = 10
  }) {
    if (this.onDrawTitleBox) {
      this.onDrawTitleBox(ctx, title_height, this.renderingSize, scale);
      return;
    }
    ctx.save();
    ctx.fillStyle = "#3b82f6";
    ctx.beginPath();
    ctx.roundRect(6, -24.5, 22, 20, 5);
    ctx.fill();
    if (!low_quality) {
      ctx.translate(25, 23);
      ctx.scale(-1.5, 1.5);
      ctx.drawImage(workflowSvg, 0, -title_height, box_size, box_size);
    }
    ctx.restore();
  }
  /**
   * Synchronizes widget values from this SubgraphNode instance to the
   * corresponding widgets in the subgraph definition before serialization.
   * This ensures nested subgraph widget values are preserved when saving.
   */
  serialize() {
    for (let i = 0; i < this.widgets.length; i++) {
      const widget = this.widgets[i];
      const input = this.inputs.find((inp) => inp.name === widget.name);
      if (input) {
        const subgraphInput = this.subgraph.inputNode.slots.find(
          (slot) => slot.name === input.name
        );
        if (subgraphInput) {
          const connectedWidgets = subgraphInput.getConnectedWidgets();
          for (const connectedWidget of connectedWidgets) {
            connectedWidget.value = widget.value;
          }
        }
      }
    }
    return super.serialize();
  }
  clone() {
    const clone2 = super.clone();
    this.properties.proxyWidgets = this.properties.proxyWidgets;
    return clone2;
  }
}
function getBoundaryNodes(nodes) {
  const valid = nodes?.find((x2) => x2);
  if (!valid) return null;
  let top = valid;
  let right = valid;
  let bottom = valid;
  let left = valid;
  for (const node2 of nodes) {
    if (!node2) continue;
    const [x2, y] = node2.pos;
    const [width2, height] = node2.size;
    if (y < top.pos[1]) top = node2;
    if (x2 + width2 > right.pos[0] + right.size[0]) right = node2;
    if (y + height > bottom.pos[1] + bottom.size[1]) bottom = node2;
    if (x2 < left.pos[0]) left = node2;
  }
  return {
    top,
    right,
    bottom,
    left
  };
}
__name(getBoundaryNodes, "getBoundaryNodes");
function distributeNodes(nodes, horizontal) {
  const nodeCount = nodes?.length;
  if (!(nodeCount > 1)) return [];
  const index2 = horizontal ? 0 : 1;
  let total = 0;
  let highest = -Infinity;
  for (const node2 of nodes) {
    total += node2.size[index2];
    const high = node2.pos[index2] + node2.size[index2];
    if (high > highest) highest = high;
  }
  const sorted = [...nodes].sort((a, b) => a.pos[index2] - b.pos[index2]);
  const lowest = sorted[0].pos[index2];
  const gap = (highest - lowest - total) / (nodeCount - 1);
  let startAt = lowest;
  for (let i = 0; i < nodeCount; i++) {
    const node2 = sorted[i];
    node2.pos[index2] = startAt + gap * i;
    startAt += node2.size[index2];
  }
  const newPositions = sorted.map(
    (node2) => ({
      node: node2,
      newPos: {
        x: node2.pos[0],
        y: node2.pos[1]
      }
    })
  );
  return newPositions;
}
__name(distributeNodes, "distributeNodes");
function alignNodes(nodes, direction, align_to) {
  if (!nodes) return [];
  const boundary = align_to === void 0 ? getBoundaryNodes(nodes) : { top: align_to, right: align_to, bottom: align_to, left: align_to };
  if (boundary === null) return [];
  const nodePositions = nodes.map((node2) => {
    switch (direction) {
      case "right":
        return {
          node: node2,
          newPos: {
            x: boundary.right.pos[0] + boundary.right.size[0] - node2.size[0],
            y: node2.pos[1]
          }
        };
      case "left":
        return {
          node: node2,
          newPos: {
            x: boundary.left.pos[0],
            y: node2.pos[1]
          }
        };
      case "top":
        return {
          node: node2,
          newPos: {
            x: node2.pos[0],
            y: boundary.top.pos[1]
          }
        };
      case "bottom":
        return {
          node: node2,
          newPos: {
            x: node2.pos[0],
            y: boundary.bottom.pos[1] + boundary.bottom.size[1] - node2.size[1]
          }
        };
    }
  });
  for (const { node: node2, newPos } of nodePositions) {
    node2.pos[0] = newPos.x;
    node2.pos[1] = newPos.y;
  }
  return nodePositions;
}
__name(alignNodes, "alignNodes");
function resolveConnectingLinkColor(type) {
  return type === LiteGraph.EVENT ? LiteGraph.EVENT_LINK_COLOR : LiteGraph.CONNECTING_LINK_COLOR;
}
__name(resolveConnectingLinkColor, "resolveConnectingLinkColor");
const cursors = {
  NE: "nesw-resize",
  SE: "nwse-resize",
  SW: "nesw-resize",
  NW: "nwse-resize"
};
const temp = new Rectangle();
const temp_vec2 = [0, 0];
const tmp_area = new Rectangle();
const margin_area = new Rectangle();
const link_bounding = new Rectangle();
class LGraphCanvas {
  static {
    __name(this, "LGraphCanvas");
  }
  static DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=";
  static DEFAULT_EVENT_LINK_COLOR = "#A86";
  /** Link type to colour dictionary. */
  static link_type_colors = {
    "-1": LGraphCanvas.DEFAULT_EVENT_LINK_COLOR,
    number: "#AAA",
    node: "#DCA"
  };
  static gradients = {};
  static search_limit = -1;
  static node_colors = {
    red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" },
    brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" },
    green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" },
    blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" },
    pale_blue: {
      color: "#2a363b",
      bgcolor: "#3f5159",
      groupcolor: "#3f789e"
    },
    cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" },
    purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" },
    yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" },
    black: { color: "#222", bgcolor: "#000", groupcolor: "#444" }
  };
  /**
   * @internal Exclusively a workaround for design limitation in {@link LGraphNode.computeSize}.
   */
  static _measureText;
  /**
   * The state of this canvas, e.g. whether it is being dragged, or read-only.
   *
   * Implemented as a POCO that can be proxied without side-effects.
   */
  state = {
    draggingItems: false,
    draggingCanvas: false,
    readOnly: false,
    hoveringOver: CanvasItem.Nothing,
    shouldSetCursor: true,
    selectionChanged: false
  };
  #subgraph;
  get subgraph() {
    return this.#subgraph;
  }
  set subgraph(value) {
    if (value !== this.#subgraph) {
      this.#subgraph = value;
      if (value)
        this.dispatch("litegraph:set-graph", {
          oldGraph: this.#subgraph,
          newGraph: value
        });
    }
  }
  /**
   * The location of the fps info widget. Leaving an element unset will use the default position for that element.
   */
  fpsInfoLocation;
  dispatch(type, detail) {
    const event = new CustomEvent(type, { detail, bubbles: true });
    return this.canvas.dispatchEvent(event);
  }
  dispatchEvent(type, detail) {
    this.canvas.dispatchEvent(new CustomEvent(type, { detail }));
  }
  #updateCursorStyle() {
    if (!this.state.shouldSetCursor) return;
    const crosshairItems = CanvasItem.Node | CanvasItem.RerouteSlot | CanvasItem.SubgraphIoNode | CanvasItem.SubgraphIoSlot;
    let cursor = "default";
    if (this.state.draggingCanvas) {
      cursor = "grabbing";
    } else if (this.state.readOnly) {
      cursor = "grab";
    } else if (this.pointer.resizeDirection) {
      cursor = cursors[this.pointer.resizeDirection] ?? cursors.SE;
    } else if (this.state.hoveringOver & crosshairItems) {
      cursor = "crosshair";
    } else if (this.state.hoveringOver & CanvasItem.Reroute) {
      cursor = "grab";
    }
    this.canvas.style.cursor = cursor;
  }
  // Whether the canvas was previously being dragged prior to pressing space key.
  // null if space key is not pressed.
  _previously_dragging_canvas = null;
  // #region Legacy accessors
  /** @deprecated @inheritdoc {@link LGraphCanvasState.readOnly} */
  get read_only() {
    return this.state.readOnly;
  }
  set read_only(value) {
    this.state.readOnly = value;
    this.#updateCursorStyle();
  }
  get isDragging() {
    return this.state.draggingItems;
  }
  set isDragging(value) {
    this.state.draggingItems = value;
  }
  get hoveringOver() {
    return this.state.hoveringOver;
  }
  set hoveringOver(value) {
    this.state.hoveringOver = value;
    this.#updateCursorStyle();
  }
  /** @deprecated Replace all references with {@link pointer}.{@link CanvasPointer.isDown isDown}. */
  get pointer_is_down() {
    return this.pointer.isDown;
  }
  /** @deprecated Replace all references with {@link pointer}.{@link CanvasPointer.isDouble isDouble}. */
  get pointer_is_double() {
    return this.pointer.isDouble;
  }
  /** @deprecated @inheritdoc {@link LGraphCanvasState.draggingCanvas} */
  get dragging_canvas() {
    return this.state.draggingCanvas;
  }
  set dragging_canvas(value) {
    this.state.draggingCanvas = value;
    this.#updateCursorStyle();
  }
  /**
   * @deprecated Use {@link LGraphNode.titleFontStyle} instead.
   */
  get title_text_font() {
    return `${LiteGraph.NODE_TEXT_SIZE}px ${LiteGraph.NODE_FONT}`;
  }
  // #endregion Legacy accessors
  get inner_text_font() {
    return `normal ${LiteGraph.NODE_SUBTEXT_SIZE}px ${LiteGraph.NODE_FONT}`;
  }
  #maximumFrameGap = 0;
  /** Maximum frames per second to render. 0: unlimited. Default: 0 */
  get maximumFps() {
    return this.#maximumFrameGap > Number.EPSILON ? this.#maximumFrameGap / 1e3 : 0;
  }
  set maximumFps(value) {
    this.#maximumFrameGap = value > Number.EPSILON ? 1e3 / value : 0;
  }
  /**
   * @deprecated Use {@link LiteGraphGlobal.ROUND_RADIUS} instead.
   */
  get round_radius() {
    return LiteGraph.ROUND_RADIUS;
  }
  /**
   * @deprecated Use {@link LiteGraphGlobal.ROUND_RADIUS} instead.
   */
  set round_radius(value) {
    LiteGraph.ROUND_RADIUS = value;
  }
  // Cached LOD threshold values for performance
  _lowQualityZoomThreshold = 0;
  _isLowQuality = false;
  /**
   * Updates the low quality zoom threshold based on current settings.
   * Called when min_font_size_for_lod or DPR changes.
   */
  updateLowQualityThreshold() {
    if (this._min_font_size_for_lod === 0) {
      this._lowQualityZoomThreshold = 0;
      this._isLowQuality = false;
      return;
    }
    const baseFontSize = LiteGraph.NODE_TEXT_SIZE;
    const dprAdjustment = Math.sqrt(window.devicePixelRatio || 1);
    this._lowQualityZoomThreshold = this._min_font_size_for_lod / (baseFontSize * dprAdjustment);
    this._isLowQuality = this.ds.scale < this._lowQualityZoomThreshold;
  }
  /**
   * Render low quality when zoomed out based on minimum readable font size.
   */
  get low_quality() {
    return this._isLowQuality;
  }
  options;
  background_image;
  ds;
  pointer;
  zoom_modify_alpha;
  zoom_speed;
  node_title_color;
  default_link_color;
  default_connection_color;
  default_connection_color_byType;
  default_connection_color_byTypeOff;
  /** Gets link colours. Extremely basic impl. until the legacy object dictionaries are removed. */
  colourGetter = {
    getConnectedColor: /* @__PURE__ */ __name((type) => this.default_connection_color_byType[type] || this.default_connection_color.output_on, "getConnectedColor"),
    getDisconnectedColor: /* @__PURE__ */ __name((type) => this.default_connection_color_byTypeOff[type] || this.default_connection_color_byType[type] || this.default_connection_color.output_off, "getDisconnectedColor")
  };
  highquality_render;
  use_gradients;
  editor_alpha;
  pause_rendering;
  clear_background;
  clear_background_color;
  render_only_selected;
  show_info;
  allow_dragcanvas;
  allow_dragnodes;
  allow_interaction;
  multi_select;
  allow_searchbox;
  allow_reconnect_links;
  align_to_grid;
  drag_mode;
  dragging_rectangle;
  filter;
  set_canvas_dirty_on_mouse_event;
  always_render_background;
  render_shadows;
  render_canvas_border;
  render_connections_shadows;
  render_connections_border;
  render_curved_connections;
  render_connection_arrows;
  render_collapsed_slots;
  render_execution_order;
  render_link_tooltip;
  /** Shape of the markers shown at the midpoint of links.  Default: Circle */
  linkMarkerShape = LinkMarkerShape.Circle;
  links_render_mode;
  /** Minimum font size in pixels before switching to low quality rendering.
   * This initializes first and if we can't get the value from the settings we default to 8px
   */
  _min_font_size_for_lod = 8;
  get min_font_size_for_lod() {
    return this._min_font_size_for_lod;
  }
  set min_font_size_for_lod(value) {
    if (this._min_font_size_for_lod !== value) {
      this._min_font_size_for_lod = value;
      this.updateLowQualityThreshold();
    }
  }
  /** mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle */
  mouse;
  /** mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle */
  graph_mouse;
  /** @deprecated LEGACY: REMOVE THIS, USE {@link graph_mouse} INSTEAD */
  canvas_mouse;
  /** to personalize the search box */
  onSearchBox;
  onSearchBoxSelection;
  onMouse;
  /** to render background objects (behind nodes and connections) in the canvas affected by transform */
  onDrawBackground;
  /** to render foreground objects (above nodes and connections) in the canvas affected by transform */
  onDrawForeground;
  connections_width;
  /** The current node being drawn by {@link drawNode}.  This should NOT be used to determine the currently selected node.  See {@link selectedItems} */
  current_node;
  /** used for widgets */
  node_widget;
  /** The link to draw a tooltip for. */
  over_link_center;
  last_mouse_position;
  /** The visible area of this canvas.  Tightly coupled with {@link ds}. */
  visible_area;
  /** Contains all links and reroutes that were rendered.  Repopulated every render cycle. */
  renderedPaths = /* @__PURE__ */ new Set();
  /** @deprecated Replaced by {@link renderedPaths}, but length is set to 0 by some extensions. */
  visible_links = [];
  /** @deprecated This array is populated and cleared to support legacy extensions. The contents are ignored by Litegraph. */
  connecting_links;
  linkConnector = new LinkConnector((links) => this.connecting_links = links);
  /** The viewport of this canvas.  Tightly coupled with {@link ds}. */
  viewport;
  autoresize;
  static active_canvas;
  frame = 0;
  last_draw_time = 0;
  render_time = 0;
  fps = 0;
  /** @deprecated See {@link LGraphCanvas.selectedItems} */
  selected_nodes = {};
  /** All selected nodes, groups, and reroutes */
  selectedItems = /* @__PURE__ */ new Set();
  /** The group currently being resized. */
  resizingGroup = null;
  /** @deprecated See {@link LGraphCanvas.selectedItems} */
  selected_group = null;
  /** The nodes that are currently visible on the canvas. */
  visible_nodes = [];
  /**
   * The IDs of the nodes that are currently visible on the canvas. More
   * performant than {@link visible_nodes} for visibility checks.
   */
  #visible_node_ids = /* @__PURE__ */ new Set();
  node_over;
  node_capturing_input;
  highlighted_links = {};
  #visibleReroutes = /* @__PURE__ */ new Set();
  dirty_canvas = true;
  dirty_bgcanvas = true;
  /** A map of nodes that require selective-redraw */
  dirty_nodes = /* @__PURE__ */ new Map();
  dirty_area;
  /** @deprecated Unused */
  node_in_panel;
  last_mouse = [0, 0];
  last_mouseclick = 0;
  graph;
  get _graph() {
    if (!this.graph) throw new NullGraphError();
    return this.graph;
  }
  canvas;
  bgcanvas;
  ctx;
  _events_binded;
  bgctx;
  is_rendering;
  /** @deprecated Panels */
  block_click;
  /** @deprecated Panels */
  last_click_position;
  resizing_node;
  /** @deprecated See {@link LGraphCanvas.resizingGroup} */
  selected_group_resizing;
  /** @deprecated See {@link pointer}.{@link CanvasPointer.dragStarted dragStarted} */
  last_mouse_dragging;
  onMouseDown;
  _highlight_pos;
  _highlight_input;
  // TODO: Check if panels are used
  /** @deprecated Panels */
  node_panel;
  /** @deprecated Panels */
  options_panel;
  _bg_img;
  _pattern;
  _pattern_img;
  bg_tint;
  // TODO: This looks like another panel thing
  prompt_box;
  search_box;
  /** @deprecated Panels */
  SELECTED_NODE;
  /** @deprecated Panels */
  NODEPANEL_IS_OPEN;
  /** Once per frame check of snap to grid value.  @todo Update on change. */
  #snapToGrid;
  /** Set on keydown, keyup. @todo */
  #shiftDown = false;
  /** Link rendering adapter for litegraph-to-canvas integration */
  linkRenderer = null;
  /** If true, enable drag zoom. Ctrl+Shift+Drag Up/Down: zoom canvas. */
  dragZoomEnabled = false;
  /** The start position of the drag zoom and original read-only state. */
  #dragZoomStart = null;
  /** If true, enable live selection during drag. Nodes are selected/deselected in real-time. */
  liveSelection = false;
  static active_node;
  onClear;
  /** called after moving a node @deprecated Does not handle multi-node move, and can return the wrong node. */
  onNodeMoved;
  /** @deprecated Called with the deprecated {@link selected_nodes} when the selection changes. Replacement not yet impl. */
  onSelectionChange;
  /** called when rendering a tooltip */
  onDrawLinkTooltip;
  /** to render foreground objects not affected by transform (for GUIs) */
  onDrawOverlay;
  onRenderBackground;
  onNodeDblClicked;
  onShowNodePanel;
  onNodeSelected;
  onNodeDeselected;
  onRender;
  /**
   * Creates a new instance of LGraphCanvas.
   * @param canvas The canvas HTML element (or its id) to use, or null / undefined to leave blank.
   * @param graph The graph that owns this canvas.
   * @param options
   */
  constructor(canvas2, graph, options) {
    options ||= {};
    this.options = options;
    this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE;
    this.ds = new DragAndScale(canvas2);
    this.pointer = new CanvasPointer(canvas2);
    this.ds.onChanged = (scale, _offset) => {
      if (this._lowQualityZoomThreshold > 0) {
        this._isLowQuality = scale < this._lowQualityZoomThreshold;
      }
    };
    if (graph) {
      this.linkRenderer = new LitegraphLinkAdapter(false);
    }
    this.linkConnector.events.addEventListener(
      "link-created",
      () => this.#dirty()
    );
    this.linkConnector.events.addEventListener("reset", () => {
      this.connecting_links = null;
      this.dirty_bgcanvas = true;
    });
    this.linkConnector.events.addEventListener(
      "dropped-on-canvas",
      (customEvent) => {
        if (!this.connecting_links) return;
        const e2 = customEvent.detail;
        this.emitEvent({
          subType: "empty-release",
          originalEvent: e2,
          linkReleaseContext: { links: this.connecting_links }
        });
        const firstLink = this.linkConnector.renderLinks[0];
        if (LiteGraph.release_link_on_empty_shows_menu) {
          const linkReleaseContext = this.linkConnector.state.connectingTo === "input" ? {
            node_from: firstLink.node,
            slot_from: firstLink.fromSlot,
            type_filter_in: firstLink.fromSlot.type
          } : {
            node_to: firstLink.node,
            slot_to: firstLink.fromSlot,
            type_filter_out: firstLink.fromSlot.type
          };
          const afterRerouteId = firstLink.fromReroute?.id;
          if ("shiftKey" in e2 && e2.shiftKey) {
            if (this.allow_searchbox) {
              this.showSearchBox(
                e2,
                linkReleaseContext
              );
            }
          } else if (this.linkConnector.state.connectingTo === "input") {
            this.showConnectionMenu({
              nodeFrom: firstLink.node,
              slotFrom: firstLink.fromSlot,
              e: e2,
              afterRerouteId
            });
          } else {
            this.showConnectionMenu({
              nodeTo: firstLink.node,
              slotTo: firstLink.fromSlot,
              e: e2,
              afterRerouteId
            });
          }
        }
      }
    );
    this.zoom_modify_alpha = true;
    this.zoom_speed = 1.1;
    this.node_title_color = LiteGraph.NODE_TITLE_COLOR;
    this.default_link_color = LiteGraph.LINK_COLOR;
    this.default_connection_color = {
      input_off: "#778",
      input_on: "#7F7",
      output_off: "#778",
      output_on: "#7F7"
    };
    this.default_connection_color_byType = {
      /* number: "#7F7",
            string: "#77F",
            boolean: "#F77", */
    };
    this.default_connection_color_byTypeOff = {
      /* number: "#474",
            string: "#447",
            boolean: "#744", */
    };
    this.highquality_render = true;
    this.use_gradients = false;
    this.editor_alpha = 1;
    this.pause_rendering = false;
    this.clear_background = true;
    this.clear_background_color = "#222";
    this.render_only_selected = true;
    this.show_info = true;
    this.allow_dragcanvas = true;
    this.allow_dragnodes = true;
    this.allow_interaction = true;
    this.multi_select = false;
    this.allow_searchbox = true;
    this.allow_reconnect_links = true;
    this.align_to_grid = false;
    this.drag_mode = false;
    this.dragging_rectangle = null;
    this.filter = null;
    this.set_canvas_dirty_on_mouse_event = true;
    this.always_render_background = false;
    this.render_shadows = true;
    this.render_canvas_border = true;
    this.render_connections_shadows = false;
    this.render_connections_border = true;
    this.render_curved_connections = false;
    this.render_connection_arrows = false;
    this.render_collapsed_slots = true;
    this.render_execution_order = false;
    this.render_link_tooltip = true;
    this.links_render_mode = LinkRenderType.SPLINE_LINK;
    this.mouse = [0, 0];
    this.graph_mouse = [0, 0];
    this.canvas_mouse = this.graph_mouse;
    this.connections_width = 3;
    this.current_node = null;
    this.node_widget = null;
    this.last_mouse_position = [0, 0];
    this.visible_area = this.ds.visible_area;
    this.connecting_links = null;
    this.viewport = options.viewport || null;
    this.graph = graph;
    graph?.attachCanvas(this);
    this.canvas = void 0;
    this.bgcanvas = void 0;
    this.ctx = void 0;
    this.setCanvas(canvas2, options.skip_events);
    this.clear();
    LGraphCanvas._measureText = (text, fontStyle = this.inner_text_font) => {
      const { ctx } = this;
      const { font } = ctx;
      try {
        ctx.font = fontStyle;
        return ctx.measureText(text).width;
      } finally {
        ctx.font = font;
      }
    };
    if (!options.skip_render) {
      this.startRendering();
    }
    this.autoresize = options.autoresize;
    this.updateLowQualityThreshold();
  }
  static onGroupAdd(info, entry, mouse_event) {
    const canvas2 = LGraphCanvas.active_canvas;
    const group = new LiteGraph.LGraphGroup();
    group.pos = canvas2.convertEventToCanvasOffset(mouse_event);
    if (!canvas2.graph) throw new NullGraphError();
    canvas2.graph.add(group);
  }
  /**
   * @deprecated Functionality moved to {@link getBoundaryNodes}.  The new function returns null on failure, instead of an object with all null properties.
   * Determines the furthest nodes in each direction
   * @param nodes the nodes to from which boundary nodes will be extracted
   * @returns
   */
  static getBoundaryNodes(nodes) {
    const _nodes = Array.isArray(nodes) ? nodes : Object.values(nodes);
    return getBoundaryNodes(_nodes) ?? {
      top: null,
      right: null,
      bottom: null,
      left: null
    };
  }
  /**
   * @deprecated Functionality moved to {@link alignNodes}.  The new function does not set dirty canvas.
   * @param nodes a list of nodes
   * @param direction Direction to align the nodes
   * @param align_to Node to align to (if null, align to the furthest node in the given direction)
   */
  static alignNodes(nodes, direction, align_to) {
    const newPositions = alignNodes(Object.values(nodes), direction, align_to);
    LGraphCanvas.active_canvas.repositionNodesVueMode(newPositions);
    LGraphCanvas.active_canvas.setDirty(true, true);
  }
  static onNodeAlign(value, options, event, prev_menu, node2) {
    new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], {
      event,
      callback: inner_clicked,
      parentMenu: prev_menu
    });
    function inner_clicked(value2) {
      const newPositions = alignNodes(
        Object.values(LGraphCanvas.active_canvas.selected_nodes),
        value2.toLowerCase(),
        node2
      );
      LGraphCanvas.active_canvas.repositionNodesVueMode(newPositions);
      LGraphCanvas.active_canvas.setDirty(true, true);
    }
    __name(inner_clicked, "inner_clicked");
  }
  static onGroupAlign(value, options, event, prev_menu) {
    new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], {
      event,
      callback: inner_clicked,
      parentMenu: prev_menu
    });
    function inner_clicked(value2) {
      const newPositions = alignNodes(
        Object.values(LGraphCanvas.active_canvas.selected_nodes),
        value2.toLowerCase()
      );
      LGraphCanvas.active_canvas.repositionNodesVueMode(newPositions);
      LGraphCanvas.active_canvas.setDirty(true, true);
    }
    __name(inner_clicked, "inner_clicked");
  }
  static createDistributeMenu(value, options, event, prev_menu) {
    new LiteGraph.ContextMenu(["Vertically", "Horizontally"], {
      event,
      callback: inner_clicked,
      parentMenu: prev_menu
    });
    function inner_clicked(value2) {
      const canvas2 = LGraphCanvas.active_canvas;
      const newPositions = distributeNodes(
        Object.values(canvas2.selected_nodes),
        value2 === "Horizontally"
      );
      canvas2.repositionNodesVueMode(newPositions);
      canvas2.setDirty(true, true);
    }
    __name(inner_clicked, "inner_clicked");
  }
  static onMenuAdd(value, options, e2, prev_menu, callback) {
    const canvas2 = LGraphCanvas.active_canvas;
    const ref_window = canvas2.getCanvasWindow();
    const { graph } = canvas2;
    if (!graph) return;
    inner_onMenuAdded("", prev_menu);
    return false;
    function inner_onMenuAdded(base_category, prev_menu2) {
      if (!graph) return;
      const categories = LiteGraph.getNodeTypesCategories(
        canvas2.filter || graph.filter
      ).filter((category) => category.startsWith(base_category));
      const entries = [];
      for (const category of categories) {
        if (!category) continue;
        const base_category_regex = new RegExp(`^(${base_category})`);
        const category_name = category.replace(base_category_regex, "").split("/", 1)[0];
        const category_path = base_category === "" ? `${category_name}/` : `${base_category}${category_name}/`;
        let name2 = category_name;
        if (name2.includes("::")) name2 = name2.split("::", 2)[1];
        const index2 = entries.findIndex(
          (entry) => entry.value === category_path
        );
        if (index2 === -1) {
          entries.push({
            value: category_path,
            content: name2,
            has_submenu: true,
            callback: /* @__PURE__ */ __name(function(value2, event, mouseEvent, contextMenu2) {
              inner_onMenuAdded(value2.value, contextMenu2);
            }, "callback")
          });
        }
      }
      const nodes = LiteGraph.getNodeTypesInCategory(
        base_category.slice(0, -1),
        canvas2.filter || graph.filter
      );
      for (const node2 of nodes) {
        if (node2.skip_list) continue;
        const entry = {
          value: node2.type,
          content: node2.title,
          has_submenu: false,
          callback: /* @__PURE__ */ __name(function(value2, event, mouseEvent, contextMenu2) {
            if (!canvas2.graph) throw new NullGraphError();
            const first_event = contextMenu2.getFirstEvent();
            canvas2.graph.beforeChange();
            const node22 = LiteGraph.createNode(value2.value);
            if (node22) {
              if (!first_event)
                throw new TypeError(
                  "Context menu event was null. This should not occur in normal usage."
                );
              node22.pos = canvas2.convertEventToCanvasOffset(first_event);
              canvas2.graph.add(node22);
            } else {
              console.warn("Failed to create node of type:", value2.value);
            }
            callback?.(node22);
            canvas2.graph.afterChange();
          }, "callback")
        };
        entries.push(entry);
      }
      new LiteGraph.ContextMenu(
        entries,
        { event: e2, parentMenu: prev_menu2 },
        // @ts-expect-error - extra parameter
        ref_window
      );
    }
    __name(inner_onMenuAdded, "inner_onMenuAdded");
  }
  static onMenuCollapseAll() {
  }
  static onMenuNodeEdit() {
  }
  /** @param _options Parameter is never used */
  static showMenuNodeOptionalOutputs(v2, _options, e2, prev_menu, node2) {
    if (!node2) return;
    const canvas2 = LGraphCanvas.active_canvas;
    let entries = [];
    if (LiteGraph.do_add_triggers_slots && node2.findOutputSlot("onExecuted") == -1) {
      entries.push({
        content: "On Executed",
        value: ["onExecuted", LiteGraph.EVENT, { nameLocked: true }],
        className: "event"
      });
    }
    const retEntries = node2.onMenuNodeOutputs?.(entries);
    if (retEntries) entries = retEntries;
    if (!entries.length) return;
    new LiteGraph.ContextMenu(entries, {
      event: e2,
      callback: inner_clicked,
      parentMenu: prev_menu,
      node: node2
    });
    function inner_clicked(v22, e22, prev) {
      if (!node2) return;
      if (v22.callback) void v22.callback.call(this, node2, v22, e22, prev);
      if (!v22.value) return;
      const value = v22.value[1];
      if (value && (typeof value === "object" || Array.isArray(value))) {
        const entries2 = [];
        for (const i in value) {
          entries2.push({ content: i, value: value[i] });
        }
        new LiteGraph.ContextMenu(entries2, {
          event: e22,
          callback: inner_clicked,
          parentMenu: prev_menu,
          node: node2
        });
        return false;
      }
      const { graph } = node2;
      if (!graph) throw new NullGraphError();
      graph.beforeChange();
      node2.addOutput(v22.value[0], v22.value[1], v22.value[2]);
      node2.onNodeOutputAdd?.(v22.value);
      canvas2.setDirty(true, true);
      graph.afterChange();
    }
    __name(inner_clicked, "inner_clicked");
    return false;
  }
  /** @param value Parameter is never used */
  static onShowMenuNodeProperties(value, options, e2, prev_menu, node2) {
    if (!node2 || !node2.properties) return;
    const canvas2 = LGraphCanvas.active_canvas;
    const ref_window = canvas2.getCanvasWindow();
    const entries = [];
    for (const i in node2.properties) {
      value = node2.properties[i] !== void 0 ? node2.properties[i] : " ";
      if (typeof value == "object") value = JSON.stringify(value);
      const info = node2.getPropertyInfo(i);
      if (info.type == "enum" || info.type == "combo")
        value = LGraphCanvas.getPropertyPrintableValue(value, info.values);
      value = LGraphCanvas.decodeHTML(toString(value));
      entries.push({
        content: `<span class='property_name'>${info.label || i}</span><span class='property_value'>${value}</span>`,
        value: i
      });
    }
    if (!entries.length) {
      return;
    }
    new LiteGraph.ContextMenu(
      entries,
      {
        event: e2,
        callback: inner_clicked,
        parentMenu: prev_menu,
        allow_html: true,
        node: node2
      },
      // @ts-expect-error Unused
      ref_window
    );
    function inner_clicked(v2) {
      if (!node2) return;
      const rect = this.getBoundingClientRect();
      canvas2.showEditPropertyValue(node2, v2.value, {
        position: [rect.left, rect.top]
      });
    }
    __name(inner_clicked, "inner_clicked");
    return false;
  }
  /** @deprecated */
  static decodeHTML(str) {
    const e2 = document.createElement("div");
    e2.textContent = str;
    return e2.innerHTML;
  }
  static onMenuResizeNode(value, options, e2, menu2, node2) {
    if (!node2) return;
    const fApplyMultiNode = /* @__PURE__ */ __name(function(node22) {
      node22.setSize(node22.computeSize());
    }, "fApplyMultiNode");
    const canvas2 = LGraphCanvas.active_canvas;
    if (!canvas2.selected_nodes || Object.keys(canvas2.selected_nodes).length <= 1) {
      fApplyMultiNode(node2);
    } else {
      for (const i in canvas2.selected_nodes) {
        fApplyMultiNode(canvas2.selected_nodes[i]);
      }
    }
    canvas2.setDirty(true, true);
  }
  // TODO refactor :: this is used fot title but not for properties!
  static onShowPropertyEditor(item, options, e2, menu2, node2) {
    const property = item.property || "title";
    const value = node2[property];
    const title = document.createElement("span");
    title.className = "name";
    title.textContent = property;
    const input = document.createElement("input");
    Object.assign(input, { type: "text", className: "value", autofocus: true });
    const button = document.createElement("button");
    button.textContent = "OK";
    const dialog = Object.assign(document.createElement("div"), {
      is_modified: false,
      className: "graphdialog",
      close: /* @__PURE__ */ __name(() => dialog.remove(), "close")
    });
    dialog.append(title, input, button);
    input.value = String(value);
    input.addEventListener("blur", function() {
      this.focus();
    });
    input.addEventListener("keydown", (e22) => {
      dialog.is_modified = true;
      if (e22.key == "Escape") {
        dialog.close();
      } else if (e22.key == "Enter") {
        inner();
      } else if (!e22.target || !("localName" in e22.target) || e22.target.localName != "textarea") {
        return;
      }
      e22.preventDefault();
      e22.stopPropagation();
    });
    const canvas2 = LGraphCanvas.active_canvas;
    const canvasEl = canvas2.canvas;
    const rect = canvasEl.getBoundingClientRect();
    const offsetx = rect ? -20 - rect.left : -20;
    const offsety = rect ? -20 - rect.top : -20;
    if (e2) {
      dialog.style.left = `${e2.clientX + offsetx}px`;
      dialog.style.top = `${e2.clientY + offsety}px`;
    } else {
      dialog.style.left = `${canvasEl.width * 0.5 + offsetx}px`;
      dialog.style.top = `${canvasEl.height * 0.5 + offsety}px`;
    }
    button.addEventListener("click", inner);
    if (canvasEl.parentNode == null)
      throw new TypeError("canvasEl.parentNode was null");
    canvasEl.parentNode.append(dialog);
    input.focus();
    let dialogCloseTimer;
    dialog.addEventListener("mouseleave", function() {
      if (LiteGraph.dialog_close_on_mouse_leave) {
        if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) {
          dialogCloseTimer = setTimeout(
            dialog.close,
            LiteGraph.dialog_close_on_mouse_leave_delay
          );
        }
      }
    });
    dialog.addEventListener("mouseenter", function() {
      if (LiteGraph.dialog_close_on_mouse_leave) {
        if (dialogCloseTimer) clearTimeout(dialogCloseTimer);
      }
    });
    function inner() {
      if (input) setValue(input.value);
    }
    __name(inner, "inner");
    function setValue(value2) {
      if (item.type == "Number") {
        value2 = Number(value2);
      } else if (item.type == "Boolean") {
        value2 = Boolean(value2);
      }
      node2[property] = value2;
      dialog.remove();
      canvas2.setDirty(true, true);
    }
    __name(setValue, "setValue");
  }
  static getPropertyPrintableValue(value, values) {
    if (!values) return String(value);
    if (Array.isArray(values)) {
      return String(value);
    }
    if (typeof values === "object") {
      let desc_value = "";
      for (const k in values) {
        if (values[k] != value) continue;
        desc_value = k;
        break;
      }
      return `${String(value)} (${desc_value})`;
    }
  }
  static onMenuNodeCollapse(value, options, e2, menu2, node2) {
    if (!node2.graph) throw new NullGraphError();
    node2.graph.beforeChange();
    const fApplyMultiNode = /* @__PURE__ */ __name(function(node22) {
      node22.collapse();
    }, "fApplyMultiNode");
    const graphcanvas = LGraphCanvas.active_canvas;
    if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) {
      fApplyMultiNode(node2);
    } else {
      for (const i in graphcanvas.selected_nodes) {
        fApplyMultiNode(graphcanvas.selected_nodes[i]);
      }
    }
    node2.graph.afterChange();
  }
  static onMenuToggleAdvanced(value, options, e2, menu2, node2) {
    if (!node2.graph) throw new NullGraphError();
    node2.graph.beforeChange();
    const fApplyMultiNode = /* @__PURE__ */ __name(function(node22) {
      node22.toggleAdvanced();
    }, "fApplyMultiNode");
    const graphcanvas = LGraphCanvas.active_canvas;
    if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) {
      fApplyMultiNode(node2);
    } else {
      for (const i in graphcanvas.selected_nodes) {
        fApplyMultiNode(graphcanvas.selected_nodes[i]);
      }
    }
    node2.graph.afterChange();
  }
  static onMenuNodeMode(value, options, e2, menu2, node2) {
    new LiteGraph.ContextMenu(LiteGraph.NODE_MODES, {
      event: e2,
      callback: inner_clicked,
      parentMenu: menu2,
      node: node2
    });
    function inner_clicked(v2) {
      if (!node2) return;
      const kV = Object.values(LiteGraph.NODE_MODES).indexOf(v2);
      const fApplyMultiNode = /* @__PURE__ */ __name(function(node22) {
        if (kV !== -1 && LiteGraph.NODE_MODES[kV]) {
          node22.changeMode(kV);
        } else {
          console.warn(`unexpected mode: ${v2}`);
          node22.changeMode(LGraphEventMode.ALWAYS);
        }
      }, "fApplyMultiNode");
      const graphcanvas = LGraphCanvas.active_canvas;
      if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) {
        fApplyMultiNode(node2);
      } else {
        for (const i in graphcanvas.selected_nodes) {
          fApplyMultiNode(graphcanvas.selected_nodes[i]);
        }
      }
    }
    __name(inner_clicked, "inner_clicked");
    return false;
  }
  /** @param value Parameter is never used */
  static onMenuNodeColors(value, options, e2, menu2, node2) {
    if (!node2) throw "no node for color";
    const values = [];
    values.push({
      value: null,
      content: "<span style='display: block; padding-left: 4px;'>No color</span>"
    });
    for (const i in LGraphCanvas.node_colors) {
      const color2 = LGraphCanvas.node_colors[i];
      value = {
        value: i,
        content: `<span style='display: block; color: #999; padding-left: 4px; border-left: 8px solid ${color2.color}; background-color:${color2.bgcolor}'>${i}</span>`
      };
      values.push(value);
    }
    new LiteGraph.ContextMenu(values, {
      event: e2,
      callback: inner_clicked,
      parentMenu: menu2,
      node: node2
    });
    function inner_clicked(v2) {
      if (!node2) return;
      const fApplyColor = /* @__PURE__ */ __name(function(item) {
        const colorOption = v2.value ? LGraphCanvas.node_colors[v2.value] : null;
        item.setColorOption(colorOption);
      }, "fApplyColor");
      const canvas2 = LGraphCanvas.active_canvas;
      if (!canvas2.selected_nodes || Object.keys(canvas2.selected_nodes).length <= 1) {
        fApplyColor(node2);
      } else {
        for (const i in canvas2.selected_nodes) {
          fApplyColor(canvas2.selected_nodes[i]);
        }
      }
      canvas2.setDirty(true, true);
    }
    __name(inner_clicked, "inner_clicked");
    return false;
  }
  static onMenuNodeShapes(value, options, e2, menu2, node2) {
    if (!node2) throw "no node passed";
    new LiteGraph.ContextMenu(
      LiteGraph.VALID_SHAPES,
      {
        event: e2,
        callback: inner_clicked,
        parentMenu: menu2,
        node: node2
      }
    );
    function inner_clicked(v2) {
      if (!node2) return;
      if (!node2.graph) throw new NullGraphError();
      node2.graph.beforeChange();
      const fApplyMultiNode = /* @__PURE__ */ __name(function(node22) {
        node22.shape = v2;
      }, "fApplyMultiNode");
      const canvas2 = LGraphCanvas.active_canvas;
      if (!canvas2.selected_nodes || Object.keys(canvas2.selected_nodes).length <= 1) {
        fApplyMultiNode(node2);
      } else {
        for (const i in canvas2.selected_nodes) {
          fApplyMultiNode(canvas2.selected_nodes[i]);
        }
      }
      node2.graph.afterChange();
      canvas2.setDirty(true);
    }
    __name(inner_clicked, "inner_clicked");
    return false;
  }
  static onMenuNodeRemove() {
    LGraphCanvas.active_canvas.deleteSelected();
  }
  static onMenuNodeClone(_value, _options, _e, _menu, node2) {
    const canvas2 = LGraphCanvas.active_canvas;
    const nodes = canvas2.selectedItems.size ? [...canvas2.selectedItems] : [node2];
    if (nodes.length) LGraphCanvas.cloneNodes(nodes);
  }
  static cloneNodes(nodes) {
    const canvas2 = LGraphCanvas.active_canvas;
    let offsetX = Infinity;
    let offsetY = Infinity;
    for (const item of nodes) {
      if (item.pos == null)
        throw new TypeError(
          "Invalid node encountered on clone.  `pos` was null."
        );
      offsetX = Math.min(offsetX, item.pos[0]);
      offsetY = Math.min(offsetY, item.pos[1]);
    }
    return canvas2._deserializeItems(canvas2._serializeItems(nodes), {
      position: [offsetX + 5, offsetY + 5]
    });
  }
  /**
   * clears all the data inside
   *
   */
  clear() {
    this.frame = 0;
    this.last_draw_time = 0;
    this.render_time = 0;
    this.fps = 0;
    this.dragging_rectangle = null;
    this.selected_nodes = {};
    this.selected_group = null;
    this.selectedItems.clear();
    this.state.selectionChanged = true;
    this.onSelectionChange?.(this.selected_nodes);
    this.visible_nodes = [];
    this.node_over = void 0;
    this.node_capturing_input = null;
    this.connecting_links = null;
    this.highlighted_links = {};
    this.dragging_canvas = false;
    this.#dirty();
    this.dirty_area = null;
    this.node_in_panel = null;
    this.node_widget = null;
    this.last_mouse = [0, 0];
    this.last_mouseclick = 0;
    this.pointer.reset();
    this.visible_area.set([0, 0, 0, 0]);
    this.onClear?.();
  }
  /**
   * Assigns a new graph to this canvas.
   */
  setGraph(newGraph) {
    const { graph } = this;
    if (newGraph === graph) return;
    this.clear();
    newGraph.attachCanvas(this);
    this.linkRenderer = new LitegraphLinkAdapter(false);
    this.dispatch("litegraph:set-graph", { newGraph, oldGraph: graph });
    this.#dirty();
  }
  openSubgraph(subgraph, fromNode) {
    const { graph } = this;
    if (!graph) throw new NullGraphError();
    const options = {
      bubbles: true,
      detail: { subgraph, closingGraph: graph, fromNode },
      cancelable: true
    };
    const mayContinue = this.canvas.dispatchEvent(
      new CustomEvent("subgraph-opening", options)
    );
    if (!mayContinue) return;
    this.clear();
    this.subgraph = subgraph;
    this.setGraph(subgraph);
    this.canvas.dispatchEvent(new CustomEvent("subgraph-opened", options));
  }
  /**
   * @returns the visually active graph (in case there are more in the stack)
   */
  getCurrentGraph() {
    return this.graph;
  }
  /**
   * Finds the canvas if required, throwing on failure.
   * @param canvas Canvas element, or its element ID
   * @returns The canvas element
   * @throws If {@link canvas} is an element ID that does not belong to a valid HTML canvas element
   */
  #validateCanvas(canvas2) {
    if (typeof canvas2 === "string") {
      const el = document.getElementById(canvas2);
      if (!(el instanceof HTMLCanvasElement))
        throw "Error validating LiteGraph canvas: Canvas element not found";
      return el;
    }
    return canvas2;
  }
  /**
   * Sets the current HTML canvas element.
   * Calls bindEvents to add input event listeners, and (re)creates the background canvas.
   * @param canvas The canvas element to assign, or its HTML element ID.  If null or undefined, the current reference is cleared.
   * @param skip_events If true, events on the previous canvas will not be removed.  Has no effect on the first invocation.
   */
  setCanvas(canvas2, skip_events) {
    const element = this.#validateCanvas(canvas2);
    if (element === this.canvas) return;
    if (!element && this.canvas && !skip_events) this.unbindEvents();
    this.canvas = element;
    this.ds.element = element;
    this.pointer.element = element;
    if (!element) return;
    element.className += " lgraphcanvas";
    element.data = this;
    this.bgcanvas = document.createElement("canvas");
    this.bgcanvas.width = this.canvas.width;
    this.bgcanvas.height = this.canvas.height;
    const ctx = element.getContext?.("2d");
    if (ctx == null) {
      if (element.localName != "canvas") {
        throw `Element supplied for LGraphCanvas must be a <canvas> element, you passed a ${element.localName}`;
      }
      throw "This browser doesn't support Canvas";
    }
    this.ctx = ctx;
    if (!skip_events) this.bindEvents();
  }
  /** Captures an event and prevents default - returns false. */
  _doNothing(e2) {
    e2.preventDefault();
    return false;
  }
  /** Captures an event and prevents default - returns true. */
  _doReturnTrue(e2) {
    e2.preventDefault();
    return true;
  }
  /**
   * binds mouse, keyboard, touch and drag events to the canvas
   */
  bindEvents() {
    if (this._events_binded) {
      console.warn("LGraphCanvas: events already bound");
      return;
    }
    const { canvas: canvas2 } = this;
    const { document: document2 } = this.getCanvasWindow();
    this._mousedown_callback = this.processMouseDown.bind(this);
    this._mousewheel_callback = this.processMouseWheel.bind(this);
    this._mousemove_callback = this.processMouseMove.bind(this);
    this._mouseup_callback = this.processMouseUp.bind(this);
    this._mouseout_callback = this.processMouseOut.bind(this);
    this._mousecancel_callback = this.processMouseCancel.bind(this);
    canvas2.addEventListener("pointerdown", this._mousedown_callback, true);
    canvas2.addEventListener("wheel", this._mousewheel_callback, false);
    canvas2.addEventListener("pointerup", this._mouseup_callback, true);
    canvas2.addEventListener("pointermove", this._mousemove_callback);
    canvas2.addEventListener("pointerout", this._mouseout_callback);
    canvas2.addEventListener("pointercancel", this._mousecancel_callback, true);
    canvas2.addEventListener("contextmenu", this._doNothing);
    this._key_callback = this.processKey.bind(this);
    canvas2.addEventListener("keydown", this._key_callback, true);
    document2.addEventListener("keyup", this._key_callback, true);
    canvas2.addEventListener("dragover", this._doNothing, false);
    canvas2.addEventListener("dragend", this._doNothing, false);
    canvas2.addEventListener("dragenter", this._doReturnTrue, false);
    this._events_binded = true;
  }
  /**
   * unbinds mouse events from the canvas
   */
  unbindEvents() {
    if (!this._events_binded) {
      console.warn("LGraphCanvas: no events bound");
      return;
    }
    const { document: document2 } = this.getCanvasWindow();
    const { canvas: canvas2 } = this;
    canvas2.removeEventListener("pointercancel", this._mousecancel_callback);
    canvas2.removeEventListener("pointerout", this._mouseout_callback);
    canvas2.removeEventListener("pointermove", this._mousemove_callback);
    canvas2.removeEventListener("pointerup", this._mouseup_callback);
    canvas2.removeEventListener("pointerdown", this._mousedown_callback);
    canvas2.removeEventListener("wheel", this._mousewheel_callback);
    canvas2.removeEventListener("keydown", this._key_callback);
    document2.removeEventListener("keyup", this._key_callback);
    canvas2.removeEventListener("contextmenu", this._doNothing);
    canvas2.removeEventListener("dragenter", this._doReturnTrue);
    this._mousedown_callback = void 0;
    this._mousewheel_callback = void 0;
    this._key_callback = void 0;
    this._events_binded = false;
  }
  /**
   * Ensures the canvas will be redrawn on the next frame by setting the dirty flag(s).
   * Without parameters, this function does nothing.
   * @todo Impl. `setDirty()` or similar as shorthand to redraw everything.
   * @param fgcanvas If true, marks the foreground canvas as dirty (nodes and anything drawn on top of them).  Default: false
   * @param bgcanvas If true, mark the background canvas as dirty (background, groups, links).  Default: false
   */
  setDirty(fgcanvas, bgcanvas) {
    if (fgcanvas) this.dirty_canvas = true;
    if (bgcanvas) this.dirty_bgcanvas = true;
  }
  /** Marks the entire canvas as dirty. */
  #dirty() {
    this.dirty_canvas = true;
    this.dirty_bgcanvas = true;
  }
  #linkConnectorDrop() {
    const { graph, linkConnector, pointer } = this;
    if (!graph) throw new NullGraphError();
    pointer.onDragEnd = (upEvent) => linkConnector.dropLinks(graph, upEvent);
    pointer.finally = () => this.linkConnector.reset(true);
  }
  /**
   * Used to attach the canvas in a popup
   * @returns returns the window where the canvas is attached (the DOM root node)
   */
  getCanvasWindow() {
    if (!this.canvas) return window;
    const doc = this.canvas.ownerDocument;
    return doc.defaultView || doc.parentWindow;
  }
  /**
   * starts rendering the content of the canvas when needed
   *
   */
  startRendering() {
    if (this.is_rendering) return;
    this.is_rendering = true;
    renderFrame.call(this);
    function renderFrame() {
      if (!this.pause_rendering) {
        this.draw();
      }
      const window2 = this.getCanvasWindow();
      if (this.is_rendering) {
        if (this.#maximumFrameGap > 0) {
          const gap = this.#maximumFrameGap - (LiteGraph.getTime() - this.last_draw_time);
          setTimeout(renderFrame.bind(this), Math.max(1, gap));
        } else {
          window2.requestAnimationFrame(renderFrame.bind(this));
        }
      }
    }
    __name(renderFrame, "renderFrame");
  }
  /**
   * stops rendering the content of the canvas (to save resources)
   *
   */
  stopRendering() {
    this.is_rendering = false;
  }
  /* LiteGraphCanvas input */
  // used to block future mouse events (because of im gui)
  blockClick() {
    this.block_click = true;
    this.last_mouseclick = 0;
  }
  /**
   * Gets the widget at the current cursor position.
   * @param node Optional node to check for widgets under cursor
   * @returns The widget located at the current cursor position, if any is found.
   * @deprecated Use {@link LGraphNode.getWidgetOnPos} instead.
   * ```ts
   * const [x, y] = canvas.graph_mouse
   * const widget = canvas.node_over?.getWidgetOnPos(x, y, true)
   * ```
   */
  getWidgetAtCursor(node2) {
    node2 ??= this.node_over;
    return node2?.getWidgetOnPos(this.graph_mouse[0], this.graph_mouse[1], true);
  }
  /**
   * Clears highlight and mouse-over information from nodes that should not have it.
   *
   * Intended to be called when the pointer moves away from a node.
   * @param node The node that the mouse is now over
   * @param e MouseEvent that is triggering this
   */
  updateMouseOverNodes(node2, e2) {
    if (!this.graph) throw new NullGraphError();
    const { pointer } = this;
    const nodes = this.graph._nodes;
    for (const otherNode of nodes) {
      if (otherNode.mouseOver && node2 != otherNode) {
        if (!pointer.eDown) pointer.resizeDirection = void 0;
        otherNode.mouseOver = void 0;
        this._highlight_input = void 0;
        this._highlight_pos = void 0;
        this.linkConnector.overWidget = void 0;
        otherNode.lostFocusAt = LiteGraph.getTime();
        this.node_over?.onMouseLeave?.(e2);
        this.node_over = void 0;
        this.dirty_canvas = true;
      }
    }
  }
  processMouseDown(e2) {
    if (this.dragZoomEnabled && e2.ctrlKey && e2.shiftKey && !e2.altKey && e2.buttons) {
      this.#dragZoomStart = {
        pos: [e2.x, e2.y],
        scale: this.ds.scale,
        readOnly: this.read_only
      };
      this.read_only = true;
      return;
    }
    const { graph, pointer } = this;
    this.adjustMouseEvent(e2);
    if (e2.isPrimary) pointer.down(e2);
    if (this.set_canvas_dirty_on_mouse_event) this.dirty_canvas = true;
    if (!graph) return;
    const ref_window = this.getCanvasWindow();
    LGraphCanvas.active_canvas = this;
    const x2 = e2.clientX;
    const y = e2.clientY;
    this.ds.viewport = this.viewport;
    const is_inside = !this.viewport || isInRect(x2, y, this.viewport);
    if (!is_inside) return;
    const node2 = graph.getNodeOnPos(e2.canvasX, e2.canvasY, this.visible_nodes) ?? void 0;
    this.mouse[0] = x2;
    this.mouse[1] = y;
    this.graph_mouse[0] = e2.canvasX;
    this.graph_mouse[1] = e2.canvasY;
    this.last_click_position = [this.mouse[0], this.mouse[1]];
    pointer.isDouble = pointer.isDown && e2.isPrimary;
    pointer.isDown = true;
    this.canvas.focus();
    LiteGraph.closeAllContextMenus(ref_window);
    if (this.onMouse?.(e2) == true) return;
    if (e2.button === 0 && !pointer.isDouble) {
      this.#processPrimaryButton(e2, node2);
    } else if (e2.button === 1) {
      this.#processMiddleButton(e2, node2);
    } else if ((e2.button === 2 || pointer.isDouble) && this.allow_interaction && !this.read_only) {
      const { linkConnector, subgraph } = this;
      if (subgraph?.inputNode.containsPoint(this.graph_mouse)) {
        this.processSelect(subgraph.inputNode, e2, true);
        subgraph.inputNode.onPointerDown(e2, pointer, linkConnector);
      } else if (subgraph?.outputNode.containsPoint(this.graph_mouse)) {
        this.processSelect(subgraph.outputNode, e2, true);
        subgraph.outputNode.onPointerDown(e2, pointer, linkConnector);
      } else {
        if (node2) {
          this.processSelect(node2, e2, true);
        } else if (this.links_render_mode !== LinkRenderType.HIDDEN_LINK) {
          const rerouteLayout = layoutStore.queryRerouteAtPoint({
            x: e2.canvasX,
            y: e2.canvasY
          });
          let reroute;
          if (rerouteLayout) {
            reroute = graph.getReroute(rerouteLayout.id);
          } else {
            reroute = graph.getRerouteOnPos(
              e2.canvasX,
              e2.canvasY,
              this.#visibleReroutes
            );
          }
          if (reroute) {
            if (e2.altKey) {
              pointer.onClick = (upEvent) => {
                if (upEvent.altKey) {
                  if (reroute.selected) {
                    this.deselect(reroute);
                    this.onSelectionChange?.(this.selected_nodes);
                  }
                  reroute.remove();
                }
              };
            } else {
              this.processSelect(reroute, e2, true);
            }
          }
        }
        pointer.onClick ??= () => this.processContextMenu(node2, e2);
      }
    }
    this.last_mouse = [x2, y];
    this.last_mouseclick = LiteGraph.getTime();
    this.last_mouse_dragging = true;
    graph.change();
    if (!ref_window.document.activeElement || ref_window.document.activeElement.nodeName.toLowerCase() != "input" && ref_window.document.activeElement.nodeName.toLowerCase() != "textarea") {
      e2.preventDefault();
    }
    e2.stopPropagation();
    this.onMouseDown?.(e2);
  }
  /**
   * Returns the first matching positionable item at the given co-ordinates.
   *
   * Order of preference:
   * - Subgraph IO Nodes
   * - Reroutes
   * - Group titlebars
   * @param x The x coordinate in canvas space
   * @param y The y coordinate in canvas space
   * @returns The positionable item or undefined
   */
  #getPositionableOnPos(x2, y) {
    const ioNode = this.subgraph?.getIoNodeOnPos(x2, y);
    if (ioNode) return ioNode;
    for (const reroute of this.#visibleReroutes) {
      if (reroute.containsPoint([x2, y])) return reroute;
    }
    return this.graph?.getGroupTitlebarOnPos(x2, y);
  }
  #processPrimaryButton(e2, node2) {
    const { pointer, graph, linkConnector, subgraph } = this;
    if (!graph) throw new NullGraphError();
    const x2 = e2.canvasX;
    const y = e2.canvasY;
    const ctrlOrMeta = e2.ctrlKey || e2.metaKey;
    if (ctrlOrMeta && !e2.altKey && LiteGraph.leftMouseClickBehavior === "panning") {
      this.#setupNodeSelectionDrag(e2, pointer, node2);
      return;
    }
    if (this.read_only) {
      pointer.finally = () => this.dragging_canvas = false;
      this.dragging_canvas = true;
      return;
    }
    if (!LiteGraph.vueNodesMode && LiteGraph.alt_drag_do_clone_nodes && e2.altKey && !e2.ctrlKey && node2 && this.allow_interaction) {
      const items = this._deserializeItems(this._serializeItems([node2]), {
        position: node2.pos
      });
      const cloned = items?.created[0];
      if (!cloned) return;
      cloned.pos[0] += 5;
      cloned.pos[1] += 5;
      if (this.allow_dragnodes) {
        pointer.onDragStart = (pointer2) => {
          this.#startDraggingItems(cloned, pointer2);
        };
        pointer.onDragEnd = (e22) => this.#processDraggedItems(e22);
      }
      return;
    }
    if (node2 && (this.allow_interaction || node2.flags.allow_interaction)) {
      this.#processNodeClick(e2, ctrlOrMeta, node2);
    } else {
      if (subgraph) {
        let processSubgraphIONode = /* @__PURE__ */ __name(function(canvas2, ioNode) {
          if (!ioNode.containsPoint([x2, y])) return false;
          ioNode.onPointerDown(e2, pointer, linkConnector);
          pointer.onClick ??= () => canvas2.processSelect(ioNode, e2);
          pointer.onDragStart ??= () => canvas2.#startDraggingItems(ioNode, pointer, true);
          pointer.onDragEnd ??= (eUp) => canvas2.#processDraggedItems(eUp);
          return true;
        }, "processSubgraphIONode");
        const { inputNode, outputNode } = subgraph;
        if (processSubgraphIONode(this, inputNode)) return;
        if (processSubgraphIONode(this, outputNode)) return;
      }
      if (this.links_render_mode !== LinkRenderType.HIDDEN_LINK) {
        const rerouteLayout = layoutStore.queryRerouteAtPoint({ x: x2, y });
        let foundReroute;
        if (rerouteLayout) {
          foundReroute = graph.getReroute(rerouteLayout.id);
        }
        for (const reroute of this.#visibleReroutes) {
          const overReroute = foundReroute === reroute || reroute.containsPoint([x2, y]);
          if (!reroute.isSlotHovered && !overReroute) continue;
          if (overReroute) {
            pointer.onClick = () => this.processSelect(reroute, e2);
            if (!e2.shiftKey) {
              pointer.onDragStart = (pointer2) => this.#startDraggingItems(reroute, pointer2, true);
              pointer.onDragEnd = (e22) => this.#processDraggedItems(e22);
            }
          }
          if (reroute.isOutputHovered || overReroute && e2.shiftKey) {
            linkConnector.dragFromReroute(graph, reroute);
            this.#linkConnectorDrop();
          }
          if (reroute.isInputHovered) {
            linkConnector.dragFromRerouteToOutput(graph, reroute);
            this.#linkConnectorDrop();
          }
          reroute.hideSlots();
          this.dirty_bgcanvas = true;
          return;
        }
      }
      const { lineWidth } = this.ctx;
      this.ctx.lineWidth = this.connections_width + 7;
      const dpi = Math.max(window?.devicePixelRatio ?? 1, 1);
      const hitSegment = layoutStore.queryLinkSegmentAtPoint({ x: x2, y }, this.ctx);
      for (const linkSegment of this.renderedPaths) {
        const centre = linkSegment._pos;
        if (!centre) continue;
        let isLinkHit = hitSegment && linkSegment.id === (linkSegment instanceof Reroute ? hitSegment.rerouteId : hitSegment.linkId);
        if (!isLinkHit && linkSegment.path) {
          isLinkHit = this.ctx.isPointInStroke(
            linkSegment.path,
            x2 * dpi,
            y * dpi
          );
        }
        if ((e2.shiftKey || e2.altKey) && isLinkHit) {
          this.ctx.lineWidth = lineWidth;
          if (e2.shiftKey && !e2.altKey) {
            linkConnector.dragFromLinkSegment(graph, linkSegment);
            this.#linkConnectorDrop();
            return;
          } else if (e2.altKey && !e2.shiftKey) {
            const newReroute = graph.createReroute([x2, y], linkSegment);
            pointer.onDragStart = (pointer2) => this.#startDraggingItems(newReroute, pointer2);
            pointer.onDragEnd = (e22) => this.#processDraggedItems(e22);
            return;
          }
        } else if (this.linkMarkerShape !== LinkMarkerShape.None && isInRectangle(x2, y, centre[0] - 4, centre[1] - 4, 8, 8)) {
          this.ctx.lineWidth = lineWidth;
          pointer.onClick = () => this.showLinkMenu(linkSegment, e2);
          pointer.onDragStart = () => this.dragging_canvas = true;
          pointer.finally = () => this.dragging_canvas = false;
          this.over_link_center = void 0;
          return;
        }
      }
      this.ctx.lineWidth = lineWidth;
      const group = graph.getGroupOnPos(x2, y);
      this.selected_group = group ?? null;
      if (group) {
        if (group.isInResize(x2, y)) {
          const b = group.boundingRect;
          const offsetX = x2 - (b[0] + b[2]);
          const offsetY = y - (b[1] + b[3]);
          pointer.onDragStart = () => this.resizingGroup = group;
          pointer.onDrag = (eMove) => {
            if (this.read_only) return;
            const pos = [
              eMove.canvasX - group.pos[0] - offsetX,
              eMove.canvasY - group.pos[1] - offsetY
            ];
            if (this.#snapToGrid) snapPoint(pos, this.#snapToGrid);
            const resized = group.resize(pos[0], pos[1]);
            if (resized) this.dirty_bgcanvas = true;
          };
          pointer.finally = () => this.resizingGroup = null;
        } else {
          const f = group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE;
          const headerHeight = f * 1.4;
          if (isInRectangle(
            x2,
            y,
            group.pos[0],
            group.pos[1],
            group.size[0],
            headerHeight
          )) {
            pointer.onClick = () => this.processSelect(group, e2);
            pointer.onDragStart = (pointer2) => {
              group.recomputeInsideNodes();
              this.#startDraggingItems(group, pointer2, true);
            };
            pointer.onDragEnd = (e22) => this.#processDraggedItems(e22);
          }
        }
        pointer.onDoubleClick = () => {
          this.emitEvent({
            subType: "group-double-click",
            originalEvent: e2,
            group
          });
        };
      } else {
        pointer.onDoubleClick = () => {
          if (this.allow_searchbox) {
            this.showSearchBox(e2);
            e2.preventDefault();
          }
          this.emitEvent({
            subType: "empty-double-click",
            originalEvent: e2
          });
        };
      }
    }
    if (!pointer.onDragStart && !pointer.onClick && !pointer.onDrag && this.allow_dragcanvas) {
      if (LiteGraph.leftMouseClickBehavior === "panning" || this.read_only) {
        pointer.onClick = () => this.processSelect(null, e2);
        pointer.finally = () => this.dragging_canvas = false;
        this.dragging_canvas = true;
      } else {
        this.#setupNodeSelectionDrag(e2, pointer);
      }
    }
  }
  #setupNodeSelectionDrag(e2, pointer, node2) {
    const dragRect = [0, 0, 0, 0];
    dragRect[0] = e2.canvasX;
    dragRect[1] = e2.canvasY;
    dragRect[2] = 1;
    dragRect[3] = 1;
    pointer.onClick = (eUp) => {
      const clickedItem = node2 ?? this.#getPositionableOnPos(eUp.canvasX, eUp.canvasY);
      this.processSelect(clickedItem, eUp);
    };
    pointer.onDragStart = () => this.dragging_rectangle = dragRect;
    if (this.liveSelection) {
      const initialSelection = new Set(this.selectedItems);
      pointer.onDrag = (eMove) => this.handleLiveSelect(eMove, dragRect, initialSelection);
      pointer.onDragEnd = () => this.finalizeLiveSelect();
    } else {
      pointer.onDragEnd = (upEvent) => this.#handleMultiSelect(upEvent, dragRect);
    }
    pointer.finally = () => this.dragging_rectangle = null;
  }
  /**
   * Processes a pointerdown event inside the bounds of a node.  Part of {@link processMouseDown}.
   * @param e The pointerdown event
   * @param ctrlOrMeta Ctrl or meta key is pressed
   * @param node The node to process a click event for
   */
  #processNodeClick(e2, ctrlOrMeta, node2) {
    if (LiteGraph.vueNodesMode) {
      return;
    }
    const { pointer, graph, linkConnector } = this;
    if (!graph) throw new NullGraphError();
    const x2 = e2.canvasX;
    const y = e2.canvasY;
    pointer.onClick = () => this.processSelect(node2, e2);
    if (!node2.flags.pinned) {
      this.bringToFront(node2);
    }
    const inCollapse = node2.isPointInCollapse(x2, y);
    if (inCollapse) {
      pointer.onClick = () => {
        node2.collapse();
        this.setDirty(true, true);
      };
    } else if (!node2.flags.collapsed) {
      let hasRelevantOutputLinks = /* @__PURE__ */ __name(function(output, network) {
        const outputLinks = [
          ...output.links ?? [],
          ...[...output._floatingLinks ?? /* @__PURE__ */ new Set()]
        ];
        return outputLinks.some(
          (linkId) => typeof linkId === "number" && network.getLink(linkId) !== void 0
        );
      }, "hasRelevantOutputLinks");
      const { inputs, outputs } = node2;
      if (outputs) {
        for (const [i, output] of outputs.entries()) {
          const link_pos = node2.getOutputPos(i);
          if (isInRectangle(x2, y, link_pos[0] - 15, link_pos[1] - 10, 30, 20)) {
            if (e2.shiftKey && hasRelevantOutputLinks(output, graph)) {
              linkConnector.moveOutputLink(graph, output);
              this.#linkConnectorDrop();
              return;
            }
            linkConnector.dragNewFromOutput(graph, node2, output);
            this.#linkConnectorDrop();
            if (LiteGraph.shift_click_do_break_link_from) {
              if (e2.shiftKey) {
                node2.disconnectOutput(i);
              }
            } else if (LiteGraph.ctrl_alt_click_do_break_link) {
              if (ctrlOrMeta && e2.altKey && !e2.shiftKey) {
                node2.disconnectOutput(i);
              }
            }
            pointer.onDoubleClick = () => node2.onOutputDblClick?.(i, e2);
            pointer.onClick = () => node2.onOutputClick?.(i, e2);
            return;
          }
        }
      }
      if (inputs) {
        for (const [i, input] of inputs.entries()) {
          const link_pos = node2.getInputPos(i);
          const isInSlot = input instanceof NodeInputSlot ? isInRect(x2, y, input.boundingRect) : isInRectangle(x2, y, link_pos[0] - 15, link_pos[1] - 10, 30, 20);
          if (isInSlot) {
            pointer.onDoubleClick = () => node2.onInputDblClick?.(i, e2);
            pointer.onClick = () => node2.onInputClick?.(i, e2);
            const shouldBreakLink = LiteGraph.ctrl_alt_click_do_break_link && ctrlOrMeta && e2.altKey && !e2.shiftKey;
            if (input.link !== null || input._floatingLinks?.size) {
              if (shouldBreakLink || LiteGraph.click_do_break_link_to) {
                node2.disconnectInput(i, true);
              } else if (e2.shiftKey || this.allow_reconnect_links) {
                linkConnector.moveInputLink(graph, input);
              }
            }
            if (!linkConnector.isConnecting) {
              linkConnector.dragNewFromInput(graph, node2, input);
            }
            this.#linkConnectorDrop();
            this.dirty_bgcanvas = true;
            return;
          }
        }
      }
    }
    const pos = [x2 - node2.pos[0], y - node2.pos[1]];
    const widget = node2.getWidgetOnPos(x2, y);
    if (widget) {
      this.processWidgetClick(e2, node2, widget);
      this.node_widget = [node2, widget];
    } else {
      pointer.onDoubleClick = () => {
        if (pos[1] < 0 && !inCollapse) {
          node2.onNodeTitleDblClick?.(e2, pos, this);
        } else if (node2 instanceof SubgraphNode) {
          this.openSubgraph(node2.subgraph, node2);
        }
        node2.onDblClick?.(e2, pos, this);
        this.emitEvent({
          subType: "node-double-click",
          originalEvent: e2,
          node: node2
        });
        this.processNodeDblClicked(node2);
      };
      if (node2.title_buttons?.length && !node2.flags.collapsed) {
        const nodeRelativeX = pos[0];
        const nodeRelativeY = pos[1];
        for (let i = 0; i < node2.title_buttons.length; i++) {
          const button = node2.title_buttons[i];
          if (button.visible && button.isPointInside(nodeRelativeX, nodeRelativeY)) {
            node2.onTitleButtonClick(button, this);
            pointer.onClick = () => {
            };
            return;
          }
        }
      }
      if (node2.onMouseDown?.(e2, pos, this)) {
        pointer.onClick = () => {
        };
        return;
      }
      if (!this.allow_dragnodes) return;
      if (!node2.flags.collapsed) {
        const resizeDirection = node2.findResizeDirection(x2, y);
        if (resizeDirection) {
          pointer.resizeDirection = resizeDirection;
          const startBounds = new Rectangle(
            node2.pos[0],
            node2.pos[1],
            node2.size[0],
            node2.size[1]
          );
          pointer.onDragStart = () => {
            graph.beforeChange();
            this.resizing_node = node2;
          };
          pointer.onDrag = (eMove) => {
            if (this.read_only) return;
            const deltaX = eMove.canvasX - x2;
            const deltaY = eMove.canvasY - y;
            const newBounds = new Rectangle(
              startBounds.x,
              startBounds.y,
              startBounds.width,
              startBounds.height
            );
            switch (resizeDirection) {
              case "NE":
                newBounds.y = startBounds.y + deltaY;
                newBounds.width = startBounds.width + deltaX;
                newBounds.height = startBounds.height - deltaY;
                break;
              case "SE":
                newBounds.width = startBounds.width + deltaX;
                newBounds.height = startBounds.height + deltaY;
                break;
              case "SW":
                newBounds.x = startBounds.x + deltaX;
                newBounds.width = startBounds.width - deltaX;
                newBounds.height = startBounds.height + deltaY;
                break;
              case "NW":
                newBounds.x = startBounds.x + deltaX;
                newBounds.y = startBounds.y + deltaY;
                newBounds.width = startBounds.width - deltaX;
                newBounds.height = startBounds.height - deltaY;
                break;
            }
            if (this.#snapToGrid) {
              if (resizeDirection.includes("N") || resizeDirection.includes("W")) {
                const originalX = newBounds.x;
                const originalY = newBounds.y;
                snapPoint(newBounds.pos, this.#snapToGrid);
                if (resizeDirection.includes("N")) {
                  newBounds.height += originalY - newBounds.y;
                }
                if (resizeDirection.includes("W")) {
                  newBounds.width += originalX - newBounds.x;
                }
              }
              snapPoint(newBounds.size, this.#snapToGrid);
            }
            const min = node2.computeSize();
            if (newBounds.width < min[0]) {
              if (resizeDirection.includes("W")) {
                newBounds.x = startBounds.x + startBounds.width - min[0];
              }
              newBounds.width = min[0];
            }
            if (newBounds.height < min[1]) {
              if (resizeDirection.includes("N")) {
                newBounds.y = startBounds.y + startBounds.height - min[1];
              }
              newBounds.height = min[1];
            }
            node2.pos = newBounds.pos;
            node2.setSize(newBounds.size);
            this.#dirty();
          };
          pointer.onDragEnd = () => {
            this.#dirty();
            graph.afterChange(node2);
          };
          pointer.finally = () => {
            this.resizing_node = null;
            pointer.resizeDirection = void 0;
          };
          this.canvas.style.cursor = cursors[resizeDirection];
          return;
        }
      }
      pointer.onDragStart = (pointer2) => this.#startDraggingItems(node2, pointer2, true);
      pointer.onDragEnd = (e22) => this.#processDraggedItems(e22);
    }
    this.dirty_canvas = true;
  }
  processWidgetClick(e2, node2, widget, pointer = this.pointer) {
    if (typeof widget.onPointerDown === "function") {
      const handled = widget.onPointerDown(pointer, node2, this);
      if (handled) return;
    }
    const oldValue = widget.value;
    const pos = this.graph_mouse;
    const x2 = pos[0] - node2.pos[0];
    const y = pos[1] - node2.pos[1];
    const widgetInstance = toConcreteWidget(widget, node2, false);
    if (widgetInstance) {
      pointer.onClick = () => widgetInstance.onClick({
        e: e2,
        node: node2,
        canvas: this
      });
      pointer.onDrag = (eMove) => widgetInstance.onDrag?.({
        e: eMove,
        node: node2,
        canvas: this
      });
    } else if (widget.mouse) {
      const result = widget.mouse(e2, [x2, y], node2);
      if (result != null) this.dirty_canvas = result;
    }
    if (oldValue != widget.value) {
      node2.onWidgetChanged?.(widget.name, widget.value, oldValue, widget);
      if (!node2.graph) throw new NullGraphError();
      node2.graph._version++;
    }
    pointer.finally = () => {
      if (widget.mouse) {
        const { eUp } = pointer;
        if (!eUp) return;
        const { canvasX, canvasY } = eUp;
        widget.mouse(eUp, [canvasX - node2.pos[0], canvasY - node2.pos[1]], node2);
      }
      this.node_widget = null;
    };
  }
  /**
   * Pointer middle button click processing.  Part of {@link processMouseDown}.
   * @param e The pointerdown event
   * @param node The node to process a click event for
   */
  #processMiddleButton(e2, node2) {
    const { pointer } = this;
    if (LiteGraph.middle_click_slot_add_default_node && node2 && this.allow_interaction && !this.read_only && !this.connecting_links && !node2.flags.collapsed) {
      let mClikSlot = false;
      let mClikSlot_index = false;
      let mClikSlot_isOut = false;
      const { inputs, outputs } = node2;
      if (outputs) {
        for (const [i, output] of outputs.entries()) {
          const link_pos = node2.getOutputPos(i);
          if (isInRectangle(
            e2.canvasX,
            e2.canvasY,
            link_pos[0] - 15,
            link_pos[1] - 10,
            30,
            20
          )) {
            mClikSlot = output;
            mClikSlot_index = i;
            mClikSlot_isOut = true;
            break;
          }
        }
      }
      if (inputs) {
        for (const [i, input] of inputs.entries()) {
          const link_pos = node2.getInputPos(i);
          if (isInRectangle(
            e2.canvasX,
            e2.canvasY,
            link_pos[0] - 15,
            link_pos[1] - 10,
            30,
            20
          )) {
            mClikSlot = input;
            mClikSlot_index = i;
            mClikSlot_isOut = false;
            break;
          }
        }
      }
      if (mClikSlot && mClikSlot_index !== false) {
        const alphaPosY = 0.5 - (mClikSlot_index + 1) / (mClikSlot_isOut ? outputs.length : inputs.length);
        const node_bounding = node2.getBounding();
        const posRef = [
          !mClikSlot_isOut ? node_bounding[0] : node_bounding[0] + node_bounding[2],
          e2.canvasY - 80
        ];
        pointer.onClick = () => this.createDefaultNodeForSlot({
          nodeFrom: !mClikSlot_isOut ? null : node2,
          slotFrom: !mClikSlot_isOut ? null : mClikSlot_index,
          nodeTo: !mClikSlot_isOut ? node2 : null,
          slotTo: !mClikSlot_isOut ? mClikSlot_index : null,
          position: posRef,
          nodeType: "AUTO",
          posAdd: [!mClikSlot_isOut ? -30 : 30, -alphaPosY * 130],
          posSizeFix: [!mClikSlot_isOut ? -1 : 0, 0]
        });
      }
    }
    if (this.allow_dragcanvas) {
      pointer.onDragStart = () => this.dragging_canvas = true;
      pointer.finally = () => this.dragging_canvas = false;
    }
  }
  #processDragZoom(e2) {
    if (!e2.buttons) {
      this.#finishDragZoom();
      return;
    }
    const start = this.#dragZoomStart;
    if (!start) throw new TypeError("Drag-zoom state object was null");
    if (!this.graph) throw new NullGraphError();
    const deltaY = e2.y - start.pos[1];
    const startScale = start.scale;
    const scale = startScale - deltaY / 100;
    this.ds.changeScale(scale, start.pos);
    this.graph.change();
  }
  #finishDragZoom() {
    const start = this.#dragZoomStart;
    if (!start) return;
    this.#dragZoomStart = null;
    this.read_only = start.readOnly;
  }
  /**
   * Called when a mouse move event has to be processed
   */
  processMouseMove(e2) {
    if (this.dragZoomEnabled && e2.ctrlKey && e2.shiftKey && this.#dragZoomStart) {
      this.#processDragZoom(e2);
      return;
    }
    if (this.autoresize) this.resize();
    if (this.set_canvas_dirty_on_mouse_event) this.dirty_canvas = true;
    const { graph, resizingGroup, linkConnector, pointer, subgraph } = this;
    if (!graph) return;
    LGraphCanvas.active_canvas = this;
    this.adjustMouseEvent(e2);
    const mouse = [e2.clientX, e2.clientY];
    this.mouse[0] = mouse[0];
    this.mouse[1] = mouse[1];
    const delta2 = [mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1]];
    this.last_mouse = mouse;
    const { canvasX: x2, canvasY: y } = e2;
    this.graph_mouse[0] = x2;
    this.graph_mouse[1] = y;
    if (e2.isPrimary) pointer.move(e2);
    let underPointer = CanvasItem.Nothing;
    if (subgraph) {
      underPointer |= subgraph.inputNode.onPointerMove(e2);
      underPointer |= subgraph.outputNode.onPointerMove(e2);
    }
    if (this.block_click) {
      e2.preventDefault();
      return;
    }
    e2.dragging = this.last_mouse_dragging;
    if (this.node_widget) {
      const [node22, widget] = this.node_widget;
      if (widget?.mouse) {
        const relativeX = x2 - node22.pos[0];
        const relativeY = y - node22.pos[1];
        const result = widget.mouse(e2, [relativeX, relativeY], node22);
        if (result != null) this.dirty_canvas = result;
      }
    }
    const node2 = graph.getNodeOnPos(x2, y, this.visible_nodes);
    const dragRect = this.dragging_rectangle;
    if (dragRect) {
      dragRect[2] = x2 - dragRect[0];
      dragRect[3] = y - dragRect[1];
      this.dirty_canvas = true;
    } else if (resizingGroup) {
      underPointer |= CanvasItem.Group;
      pointer.resizeDirection = "SE";
    } else if (this.dragging_canvas) {
      this.ds.offset[0] += delta2[0] / this.ds.scale;
      this.ds.offset[1] += delta2[1] / this.ds.scale;
      this.#dirty();
    } else if ((this.allow_interaction || node2?.flags.allow_interaction) && !this.read_only) {
      if (linkConnector.isConnecting) this.dirty_canvas = true;
      this.updateMouseOverNodes(node2, e2);
      if (node2) {
        underPointer |= CanvasItem.Node;
        if (node2.redraw_on_mouse) this.dirty_canvas = true;
        const pos = [0, 0];
        let inputId = -1;
        let outputId = -1;
        const slotLayout = layoutStore.querySlotAtPoint({ x: x2, y });
        if (slotLayout && slotLayout.nodeId === String(node2.id)) {
          if (slotLayout.type === "input") {
            inputId = slotLayout.index;
            pos[0] = slotLayout.position.x;
            pos[1] = slotLayout.position.y;
          } else {
            outputId = slotLayout.index;
            pos[0] = slotLayout.position.x;
            pos[1] = slotLayout.position.y;
          }
        } else {
          inputId = isOverNodeInput(node2, x2, y, pos);
          outputId = isOverNodeOutput(node2, x2, y, pos);
        }
        const overWidget = node2.getWidgetOnPos(x2, y, true) ?? void 0;
        if (!node2.mouseOver) {
          node2.mouseOver = {};
          this.node_over = node2;
          this.dirty_canvas = true;
          for (const reroute of this.#visibleReroutes) {
            reroute.hideSlots();
            this.dirty_bgcanvas = true;
          }
          node2.onMouseEnter?.(e2);
        }
        node2.onMouseMove?.(e2, [x2 - node2.pos[0], y - node2.pos[1]], this);
        const { mouseOver } = node2;
        if (mouseOver.inputId !== inputId || mouseOver.outputId !== outputId || mouseOver.overWidget !== overWidget) {
          mouseOver.inputId = inputId;
          mouseOver.outputId = outputId;
          mouseOver.overWidget = overWidget;
          linkConnector.overWidget = void 0;
          if (linkConnector.isConnecting) {
            const firstLink = linkConnector.renderLinks.at(0);
            let highlightPos;
            let highlightInput;
            if (!firstLink || !linkConnector.isNodeValidDrop(node2)) ;
            else if (linkConnector.state.connectingTo === "input") {
              if (overWidget) {
                const slot = node2.getSlotFromWidget(overWidget);
                if (slot && linkConnector.isInputValidDrop(node2, slot)) {
                  highlightInput = slot;
                  if (LiteGraph.vueNodesMode) {
                    const idx = node2.inputs.indexOf(slot);
                    highlightPos = idx !== -1 ? getSlotPosition(node2, idx, true) : node2.getInputSlotPos(slot);
                  } else {
                    highlightPos = node2.getInputSlotPos(slot);
                  }
                  linkConnector.overWidget = overWidget;
                }
              }
              if (!linkConnector.overWidget) {
                if (inputId === -1 && outputId === -1) {
                  const result = node2.findInputByType(firstLink.fromSlot.type);
                  if (result) {
                    highlightInput = result.slot;
                    highlightPos = LiteGraph.vueNodesMode ? getSlotPosition(node2, result.index, true) : node2.getInputSlotPos(result.slot);
                  }
                } else if (inputId != -1 && node2.inputs[inputId] && LiteGraph.isValidConnection(
                  firstLink.fromSlot.type,
                  node2.inputs[inputId].type
                )) {
                  highlightPos = pos;
                  highlightInput = node2.inputs[inputId];
                }
                if (highlightInput) {
                  const widget = node2.getWidgetFromSlot(highlightInput);
                  if (widget) linkConnector.overWidget = widget;
                }
              }
            } else if (linkConnector.state.connectingTo === "output") {
              if (inputId === -1 && outputId === -1) {
                const result = node2.findOutputByType(firstLink.fromSlot.type);
                if (result) {
                  highlightPos = LiteGraph.vueNodesMode ? getSlotPosition(node2, result.index, false) : node2.getOutputPos(result.index);
                }
              } else {
                if (outputId != -1 && node2.outputs[outputId] && LiteGraph.isValidConnection(
                  firstLink.fromSlot.type,
                  node2.outputs[outputId].type
                )) {
                  highlightPos = pos;
                }
              }
            }
            this._highlight_pos = highlightPos;
            this._highlight_input = highlightInput;
          }
          this.dirty_canvas = true;
        }
        if (!pointer.eDown) {
          if (inputId === -1 && outputId === -1 && !overWidget) {
            pointer.resizeDirection = node2.findResizeDirection(x2, y);
          } else {
            pointer.resizeDirection &&= void 0;
          }
        }
      } else {
        underPointer = this.#updateReroutes(underPointer);
        const segment = this.#getLinkCentreOnPos(e2);
        if (this.over_link_center !== segment) {
          underPointer |= CanvasItem.Link;
          this.over_link_center = segment;
          this.dirty_bgcanvas = true;
        }
        if (this.canvas) {
          const group = graph.getGroupOnPos(x2, y);
          if (group && !e2.ctrlKey && !this.read_only && group.isInResize(x2, y)) {
            pointer.resizeDirection = "SE";
          } else {
            pointer.resizeDirection &&= void 0;
          }
        }
      }
      if (this.node_capturing_input && this.node_capturing_input != node2) {
        this.node_capturing_input.onMouseMove?.(
          e2,
          [
            x2 - this.node_capturing_input.pos[0],
            y - this.node_capturing_input.pos[1]
          ],
          this
        );
      }
      if (this.isDragging) {
        const selected = this.selectedItems;
        const allItems = e2.ctrlKey ? selected : getAllNestedItems(selected);
        const deltaX = delta2[0] / this.ds.scale;
        const deltaY = delta2[1] / this.ds.scale;
        if (LiteGraph.vueNodesMode) {
          this.moveChildNodesInGroupVueMode(allItems, deltaX, deltaY);
        } else {
          for (const item of allItems) {
            item.move(deltaX, deltaY, true);
          }
        }
        this.#dirty();
      }
    }
    this.hoveringOver = underPointer;
    e2.preventDefault();
    return;
  }
  /**
   * Updates the hover / snap state of all visible reroutes.
   * @returns The original value of {@link underPointer}, with any found reroute items added.
   */
  #updateReroutes(underPointer) {
    const { graph, pointer, linkConnector } = this;
    if (!graph) throw new NullGraphError();
    if (!pointer.isDown) {
      let anyChanges = false;
      for (const reroute of this.#visibleReroutes) {
        anyChanges ||= reroute.updateVisibility(this.graph_mouse);
        if (reroute.isSlotHovered) underPointer |= CanvasItem.RerouteSlot;
      }
      if (anyChanges) this.dirty_bgcanvas = true;
    } else if (linkConnector.isConnecting) {
      for (const reroute of this.#visibleReroutes) {
        if (reroute.containsPoint(this.graph_mouse)) {
          if (linkConnector.isRerouteValidDrop(reroute)) {
            linkConnector.overReroute = reroute;
            this._highlight_pos = reroute.pos;
          }
          return underPointer |= CanvasItem.RerouteSlot;
        }
      }
    }
    this._highlight_pos &&= void 0;
    linkConnector.overReroute &&= void 0;
    return underPointer;
  }
  /**
   * Start dragging an item, optionally including all other selected items.
   *
   * ** This function sets the {@link CanvasPointer.finally}() callback. **
   * @param item The item that the drag event started on
   * @param pointer The pointer event that initiated the drag, e.g. pointerdown
   * @param sticky If `true`, the item is added to the selection - see {@link processSelect}
   */
  #startDraggingItems(item, pointer, sticky = false) {
    this.emitBeforeChange();
    this.graph?.beforeChange();
    pointer.finally = () => {
      this.isDragging = false;
      this.graph?.afterChange();
      this.emitAfterChange();
    };
    this.processSelect(item, pointer.eDown, sticky);
    this.isDragging = true;
  }
  /**
   * Handles shared clean up and placement after items have been dragged.
   * @param e The event that completed the drag, e.g. pointerup, pointermove
   */
  #processDraggedItems(e2) {
    const { graph } = this;
    if (e2.shiftKey || LiteGraph.alwaysSnapToGrid)
      graph?.snapToGrid(this.selectedItems);
    this.dirty_canvas = true;
    this.dirty_bgcanvas = true;
    this.onNodeMoved?.(findFirstNode(this.selectedItems));
  }
  /**
   * Called when a mouse up event has to be processed
   */
  processMouseUp(e2) {
    if (e2.isPrimary === false) return;
    const { graph, pointer } = this;
    if (!graph) return;
    this.#finishDragZoom();
    LGraphCanvas.active_canvas = this;
    this.adjustMouseEvent(e2);
    const now = LiteGraph.getTime();
    e2.click_time = now - this.last_mouseclick;
    const isClick = pointer.up(e2);
    if (isClick === true) {
      pointer.isDown = false;
      pointer.isDouble = false;
      this.connecting_links = null;
      this.dragging_canvas = false;
      graph.change();
      e2.stopPropagation();
      e2.preventDefault();
      return;
    }
    this.last_mouse_dragging = false;
    this.last_click_position = null;
    this.block_click &&= false;
    if (e2.button === 0) {
      this.selected_group = null;
      this.isDragging = false;
      const x2 = e2.canvasX;
      const y = e2.canvasY;
      if (!this.linkConnector.isConnecting) {
        this.dirty_canvas = true;
        this.node_over?.onMouseUp?.(
          e2,
          [x2 - this.node_over.pos[0], y - this.node_over.pos[1]],
          // @ts-expect-error - extra parameter
          this
        );
        this.node_capturing_input?.onMouseUp?.(e2, [
          x2 - this.node_capturing_input.pos[0],
          y - this.node_capturing_input.pos[1]
        ]);
      }
    } else if (e2.button === 1) {
      this.dirty_canvas = true;
      this.dragging_canvas = false;
    } else if (e2.button === 2) {
      this.dirty_canvas = true;
    }
    pointer.isDown = false;
    pointer.isDouble = false;
    graph.change();
    e2.stopPropagation();
    e2.preventDefault();
    return;
  }
  /**
   * Called when the mouse moves off the canvas.  Clears all node hover states.
   * @param e
   */
  processMouseOut(e2) {
    this.adjustMouseEvent(e2);
    this.updateMouseOverNodes(null, e2);
  }
  processMouseCancel() {
    console.warn("Pointer cancel!");
    this.pointer.reset();
  }
  /**
   * Called when a mouse wheel event has to be processed
   */
  processMouseWheel(e2) {
    if (!this.graph || !this.allow_dragcanvas) return;
    this.adjustMouseEvent(e2);
    const pos = [e2.clientX, e2.clientY];
    if (this.viewport && !isPointInRect(pos, this.viewport)) return;
    let { scale } = this.ds;
    const isTrackpad = this.pointer.isTrackpadGesture(e2);
    const isCtrlOrMacMeta = e2.ctrlKey || e2.metaKey && navigator.platform.includes("Mac");
    const isZoomModifier = isCtrlOrMacMeta && !e2.altKey && !e2.shiftKey;
    if (isZoomModifier || LiteGraph.mouseWheelScroll === "zoom") {
      if (isTrackpad) {
        scale *= 1 + e2.deltaY * (1 - this.zoom_speed) * 0.18;
        this.ds.changeScale(scale, [e2.clientX, e2.clientY], false);
      } else {
        if (e2.deltaY < 0) {
          scale *= this.zoom_speed;
        } else if (e2.deltaY > 0) {
          scale *= 1 / this.zoom_speed;
        }
        this.ds.changeScale(scale, [e2.clientX, e2.clientY]);
      }
    } else {
      const factor = isTrackpad ? 0.18 : 8333e-6;
      if (!isTrackpad && e2.shiftKey && e2.deltaX === 0) {
        this.ds.offset[0] -= e2.deltaY * (1 + factor) * (1 / scale);
      } else {
        this.ds.offset[0] -= e2.deltaX * (1 + factor) * (1 / scale);
        this.ds.offset[1] -= e2.deltaY * (1 + factor) * (1 / scale);
      }
    }
    this.graph.change();
    e2.preventDefault();
    return;
  }
  #noItemsSelected() {
    const event = new CustomEvent("litegraph:no-items-selected", {
      bubbles: true
    });
    this.canvas.dispatchEvent(event);
  }
  /**
   * process a key event
   */
  processKey(e2) {
    this.#shiftDown = e2.shiftKey;
    const { graph } = this;
    if (!graph) return;
    let block_default = false;
    if (e2.target.localName == "input") return;
    if (e2.type == "keydown") {
      if (e2.key === " ") {
        this.read_only = true;
        if (this._previously_dragging_canvas === null) {
          this._previously_dragging_canvas = this.dragging_canvas;
        }
        this.dragging_canvas = this.pointer.isDown;
        block_default = true;
      } else if (e2.key === "Escape") {
        if (this.linkConnector.isConnecting) {
          this.linkConnector.reset();
          e2.preventDefault();
          return;
        }
        this.node_panel?.close();
        this.options_panel?.close();
        if (this.node_panel || this.options_panel) block_default = true;
      } else if (e2.keyCode === 65 && e2.ctrlKey) {
        this.selectItems();
        block_default = true;
      } else if (e2.keyCode === 67 && (e2.metaKey || e2.ctrlKey) && !e2.shiftKey) {
        if (this.selected_nodes) {
          this.copyToClipboard();
          block_default = true;
        }
      } else if (e2.keyCode === 86 && (e2.metaKey || e2.ctrlKey)) {
        this.pasteFromClipboard({ connectInputs: e2.shiftKey });
      } else if (e2.key === "Delete" || e2.key === "Backspace") {
        if (e2.target.localName != "input" && e2.target.localName != "textarea") {
          if (this.selectedItems.size === 0) {
            this.#noItemsSelected();
            return;
          }
          this.deleteSelected();
          block_default = true;
        }
      }
      for (const node2 of Object.values(this.selected_nodes)) {
        node2.onKeyDown?.(e2);
      }
    } else if (e2.type == "keyup") {
      if (e2.key === " ") {
        this.read_only = false;
        this.dragging_canvas = (this._previously_dragging_canvas ?? false) && this.pointer.isDown;
        this._previously_dragging_canvas = null;
      }
      for (const node2 of Object.values(this.selected_nodes)) {
        node2.onKeyUp?.(e2);
      }
    }
    graph.change();
    if (block_default) {
      e2.preventDefault();
      e2.stopImmediatePropagation();
    }
  }
  _serializeItems(items) {
    const serialisable = {
      nodes: [],
      groups: [],
      reroutes: [],
      links: [],
      subgraphs: []
    };
    const subgraphs = /* @__PURE__ */ new Set();
    for (const item of items ?? this.selectedItems) {
      if (item instanceof LGraphNode) {
        if (item.clonable === false) continue;
        const cloned = item.clone()?.serialize();
        if (!cloned) continue;
        cloned.id = item.id;
        serialisable.nodes.push(cloned);
        if (item.inputs) {
          for (const { link: linkId } of item.inputs) {
            if (linkId == null) continue;
            const link = this.graph?._links.get(linkId)?.asSerialisable();
            if (link) serialisable.links.push(link);
          }
        }
        if (item instanceof SubgraphNode) {
          subgraphs.add(item.subgraph);
        }
      } else if (item instanceof LGraphGroup) {
        serialisable.groups.push(item.serialize());
      } else if (item instanceof Reroute) {
        serialisable.reroutes.push(item.asSerialisable());
      }
    }
    for (const subgraph of subgraphs) {
      for (const node2 of subgraph.nodes) {
        if (node2 instanceof SubgraphNode) {
          subgraphs.add(node2.subgraph);
        }
      }
      const cloned = subgraph.clone(true).asSerialisable();
      serialisable.subgraphs.push(cloned);
    }
    return serialisable;
  }
  /**
   * Copies canvas items to an internal, app-specific clipboard backed by local storage.
   * When called without parameters, it copies {@link selectedItems}.
   * @param items The items to copy.  If nullish, all selected items are copied.
   */
  copyToClipboard(items) {
    const serializedData = JSON.stringify(this._serializeItems(items));
    localStorage.setItem("litegrapheditor_clipboard", serializedData);
    return serializedData;
  }
  emitEvent(detail) {
    this.canvas.dispatchEvent(
      new CustomEvent("litegraph:canvas", {
        bubbles: true,
        detail
      })
    );
  }
  /** @todo Refactor to where it belongs - e.g. Deleting / creating nodes is not actually canvas event. */
  emitBeforeChange() {
    this.emitEvent({
      subType: "before-change"
    });
  }
  /** @todo See {@link emitBeforeChange} */
  emitAfterChange() {
    this.emitEvent({
      subType: "after-change"
    });
  }
  /**
   * Pastes the items from the canvas "clipbaord" - a local storage variable.
   */
  _pasteFromClipboard(options = {}) {
    const data = localStorage.getItem("litegrapheditor_clipboard");
    if (!data) return;
    return this._deserializeItems(JSON.parse(data), options);
  }
  _deserializeItems(parsed, options) {
    const { connectInputs = false, position = this.graph_mouse } = options;
    if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && connectInputs)
      return;
    const { graph } = this;
    if (!graph) throw new NullGraphError();
    graph.beforeChange();
    this.emitBeforeChange();
    parsed.nodes ??= [];
    parsed.groups ??= [];
    parsed.reroutes ??= [];
    parsed.links ??= [];
    parsed.subgraphs ??= [];
    let offsetX = Infinity;
    let offsetY = Infinity;
    for (const item of [...parsed.nodes, ...parsed.reroutes]) {
      if (item.pos == null)
        throw new TypeError(
          "Invalid node encountered on paste.  `pos` was null."
        );
      if (item.pos[0] < offsetX) offsetX = item.pos[0];
      if (item.pos[1] < offsetY) offsetY = item.pos[1];
    }
    if (parsed.groups) {
      for (const group of parsed.groups) {
        if (group.bounding[0] < offsetX) offsetX = group.bounding[0];
        if (group.bounding[1] < offsetY) offsetY = group.bounding[1];
      }
    }
    const results = {
      created: [],
      nodes: /* @__PURE__ */ new Map(),
      links: /* @__PURE__ */ new Map(),
      reroutes: /* @__PURE__ */ new Map(),
      subgraphs: /* @__PURE__ */ new Map()
    };
    const { created, nodes, links, reroutes } = results;
    const subgraphIdMap = {};
    for (const subgraphInfo of parsed.subgraphs)
      subgraphInfo.id = subgraphIdMap[subgraphInfo.id] = createUuidv4();
    const allNodeInfo = [
      parsed.nodes ? [parsed.nodes] : [],
      parsed.subgraphs ? parsed.subgraphs.map((s) => s.nodes ?? []) : []
    ].flat(2);
    for (const nodeInfo of allNodeInfo)
      if (nodeInfo.type in subgraphIdMap)
        nodeInfo.type = subgraphIdMap[nodeInfo.type];
    for (const info of parsed.subgraphs) {
      const subgraph = graph.createSubgraph(info);
      results.subgraphs.set(info.id, subgraph);
    }
    for (const info of parsed.subgraphs)
      results.subgraphs.get(info.id)?.configure(info);
    for (const info of parsed.groups) {
      info.id = -1;
      const group = new LGraphGroup();
      group.configure(info);
      graph.add(group);
      created.push(group);
    }
    for (const info of parsed.nodes) {
      const node2 = info.type == null ? null : LiteGraph.createNode(info.type);
      if (!node2) {
        continue;
      }
      nodes.set(info.id, node2);
      info.id = -1;
      node2.configure(info);
      graph.add(node2);
      created.push(node2);
    }
    for (const info of parsed.reroutes) {
      const { id: id2, ...rerouteInfo } = info;
      const reroute = graph.setReroute(rerouteInfo);
      created.push(reroute);
      reroutes.set(id2, reroute);
    }
    for (const reroute of reroutes.values()) {
      if (reroute.parentId == null) continue;
      const mapped = reroutes.get(reroute.parentId);
      if (mapped) reroute.parentId = mapped.id;
    }
    for (const info of parsed.links) {
      let outNode = nodes.get(info.origin_id);
      let afterRerouteId;
      if (info.parentId != null)
        afterRerouteId = reroutes.get(info.parentId)?.id;
      if (connectInputs && LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs) {
        outNode ??= graph.getNodeById(info.origin_id);
        afterRerouteId ??= info.parentId;
      }
      const inNode = nodes.get(info.target_id);
      if (inNode) {
        const link = outNode?.connect(
          info.origin_slot,
          inNode,
          info.target_slot,
          afterRerouteId
        );
        if (link) links.set(info.id, link);
      }
    }
    for (const reroute of reroutes.values()) {
      const ids = [...reroute.linkIds].map((x2) => links.get(x2)?.id ?? x2);
      reroute.update(reroute.parentId, void 0, ids, reroute.floating);
      if (!reroute.validateLinks(graph.links, graph.floatingLinks)) {
        graph.removeReroute(reroute.id);
      }
    }
    for (const item of created) {
      item.pos[0] += position[0] - offsetX;
      item.pos[1] += position[1] - offsetY;
    }
    const newPositions = created.filter((item) => item instanceof LGraphNode).map((node2) => {
      const fullHeight = node2.size?.[1] ?? 200;
      const layoutHeight = LiteGraph.vueNodesMode ? removeNodeTitleHeight(fullHeight) : fullHeight;
      return {
        nodeId: String(node2.id),
        bounds: {
          x: node2.pos[0],
          y: node2.pos[1],
          width: node2.size?.[0] ?? 100,
          height: layoutHeight
        }
      };
    });
    if (newPositions.length) layoutStore.setSource(LayoutSource.Canvas);
    layoutStore.batchUpdateNodeBounds(newPositions);
    this.selectItems(created);
    graph.afterChange();
    this.emitAfterChange();
    return results;
  }
  pasteFromClipboard(options = {}) {
    this.emitBeforeChange();
    try {
      this._pasteFromClipboard(options);
    } finally {
      this.emitAfterChange();
    }
  }
  processNodeDblClicked(n) {
    this.onShowNodePanel?.(n);
    this.onNodeDblClicked?.(n);
    this.setDirty(true);
  }
  /**
   * Normalizes a drag rectangle to have positive width and height.
   * @param dragRect The drag rectangle to normalize (modified in place)
   * @returns The normalized rectangle
   */
  #normalizeDragRect(dragRect) {
    const w = Math.abs(dragRect[2]);
    const h2 = Math.abs(dragRect[3]);
    if (dragRect[2] < 0) dragRect[0] -= w;
    if (dragRect[3] < 0) dragRect[1] -= h2;
    dragRect[2] = w;
    dragRect[3] = h2;
    return dragRect;
  }
  /**
   * Gets all positionable items that overlap with the given rectangle.
   * @param rect The rectangle to check against
   * @returns Set of positionable items that overlap with the rectangle
   */
  #getItemsInRect(rect) {
    const { graph, subgraph } = this;
    if (!graph) throw new NullGraphError();
    const items = /* @__PURE__ */ new Set();
    if (subgraph) {
      const { inputNode, outputNode } = subgraph;
      if (overlapBounding(rect, inputNode.boundingRect)) items.add(inputNode);
      if (overlapBounding(rect, outputNode.boundingRect)) items.add(outputNode);
    }
    for (const node2 of graph._nodes) {
      if (overlapBounding(rect, node2.boundingRect)) items.add(node2);
    }
    for (const group of graph.groups) {
      if (containsRect(rect, group._bounding)) {
        group.recomputeInsideNodes();
        items.add(group);
      }
    }
    for (const reroute of graph.reroutes.values()) {
      if (isPointInRect(reroute.pos, rect)) items.add(reroute);
    }
    return items;
  }
  /**
   * Handles live selection updates during drag. Called on each pointer move.
   * @param e The pointer move event
   * @param dragRect The current drag rectangle
   * @param initialSelection The selection state before the drag started
   */
  handleLiveSelect(e2, dragRect, initialSelection) {
    dragRect[2] = e2.canvasX - dragRect[0];
    dragRect[3] = e2.canvasY - dragRect[1];
    const normalizedRect = [
      dragRect[0],
      dragRect[1],
      dragRect[2],
      dragRect[3]
    ];
    this.#normalizeDragRect(normalizedRect);
    const itemsInRect = this.#getItemsInRect(normalizedRect);
    const desired = /* @__PURE__ */ new Set();
    if (e2.shiftKey && !e2.altKey) {
      for (const item of initialSelection) desired.add(item);
      for (const item of itemsInRect) desired.add(item);
    } else if (e2.altKey && !e2.shiftKey) {
      for (const item of initialSelection)
        if (!itemsInRect.has(item)) desired.add(item);
    } else {
      for (const item of itemsInRect) desired.add(item);
    }
    let changed = false;
    for (const item of [...this.selectedItems]) {
      if (!desired.has(item)) {
        this.deselect(item);
        changed = true;
      }
    }
    for (const item of desired) {
      if (!this.selectedItems.has(item)) {
        this.select(item);
        changed = true;
      }
    }
    if (changed) {
      this.onSelectionChange?.(this.selected_nodes);
      this.setDirty(true);
    }
  }
  /**
   * Finalizes the live selection when drag ends.
   */
  finalizeLiveSelect() {
    this.onSelectionChange?.(this.selected_nodes);
  }
  /**
   * Handles multi-select when drag ends (classic mode).
   * @param e The pointer up event
   * @param dragRect The drag rectangle
   */
  #handleMultiSelect(e2, dragRect) {
    const normalizedRect = [
      dragRect[0],
      dragRect[1],
      dragRect[2],
      dragRect[3]
    ];
    this.#normalizeDragRect(normalizedRect);
    const itemsInRect = this.#getItemsInRect(normalizedRect);
    const { selectedItems } = this;
    if (e2.shiftKey) {
      for (const item of itemsInRect) this.select(item);
    } else if (e2.altKey) {
      for (const item of itemsInRect) this.deselect(item);
    } else {
      for (const item of selectedItems.values()) {
        if (!itemsInRect.has(item)) this.deselect(item);
      }
      for (const item of itemsInRect) this.select(item);
    }
    this.onSelectionChange?.(this.selected_nodes);
  }
  /**
   * Determines whether to select or deselect an item that has received a pointer event.  Will deselect other nodes if
   * @param item Canvas item to select/deselect
   * @param e The MouseEvent to handle
   * @param sticky Prevents deselecting individual nodes (as used by aux/right-click)
   * @remarks
   * Accessibility: anyone using {@link mutli_select} always deselects when clicking empty space.
   */
  processSelect(item, e2, sticky = false) {
    const addModifier = e2?.shiftKey;
    const subtractModifier = e2 != null && (e2.metaKey || e2.ctrlKey);
    const eitherModifier = addModifier || subtractModifier;
    const modifySelection = eitherModifier || this.multi_select;
    if (!item) {
      if (!eitherModifier || this.multi_select) this.deselectAll();
    } else if (!item.selected || !this.selectedItems.has(item)) {
      if (!modifySelection) this.deselectAll(item);
      this.select(item);
    } else if (modifySelection && !sticky) {
      this.deselect(item);
    } else if (!sticky) {
      this.deselectAll(item);
    } else {
      return;
    }
    this.onSelectionChange?.(this.selected_nodes);
    this.setDirty(true);
  }
  /**
   * Selects a {@link Positionable} item.
   * @param item The canvas item to add to the selection.
   */
  select(item) {
    if (item.selected && this.selectedItems.has(item)) return;
    item.selected = true;
    this.selectedItems.add(item);
    this.state.selectionChanged = true;
    if (!(item instanceof LGraphNode)) return;
    item.onSelected?.();
    this.selected_nodes[item.id] = item;
    this.onNodeSelected?.(item);
    if (item.inputs) {
      for (const input of item.inputs) {
        if (input.link == null) continue;
        this.highlighted_links[input.link] = true;
      }
    }
    if (item.outputs) {
      for (const id2 of item.outputs.flatMap((x2) => x2.links)) {
        if (id2 == null) continue;
        this.highlighted_links[id2] = true;
      }
    }
  }
  /**
   * Deselects a {@link Positionable} item.
   * @param item The canvas item to remove from the selection.
   */
  deselect(item) {
    if (!item.selected && !this.selectedItems.has(item)) return;
    item.selected = false;
    this.selectedItems.delete(item);
    this.state.selectionChanged = true;
    if (!(item instanceof LGraphNode)) return;
    item.onDeselected?.();
    delete this.selected_nodes[item.id];
    this.onNodeDeselected?.(item);
    const { graph } = this;
    if (!graph) return;
    if (item.inputs) {
      for (const input of item.inputs) {
        if (input.link == null) continue;
        const node2 = LLink.getOriginNode(graph, input.link);
        if (node2 && this.selectedItems.has(node2)) continue;
        delete this.highlighted_links[input.link];
      }
    }
    if (item.outputs) {
      for (const id2 of item.outputs.flatMap((x2) => x2.links)) {
        if (id2 == null) continue;
        const node2 = LLink.getTargetNode(graph, id2);
        if (node2 && this.selectedItems.has(node2)) continue;
        delete this.highlighted_links[id2];
      }
    }
  }
  /** @deprecated See {@link LGraphCanvas.processSelect} */
  processNodeSelected(item, e2) {
    this.processSelect(
      item,
      e2,
      e2 && (e2.shiftKey || e2.metaKey || e2.ctrlKey || this.multi_select)
    );
  }
  /** @deprecated See {@link LGraphCanvas.select} */
  selectNode(node2, add_to_current_selection) {
    if (node2 == null) {
      this.deselectAll();
    } else {
      this.selectNodes([node2], add_to_current_selection);
    }
  }
  get empty() {
    if (!this.graph) throw new NullGraphError();
    return this.graph.empty;
  }
  get positionableItems() {
    if (!this.graph) throw new NullGraphError();
    return this.graph.positionableItems();
  }
  /**
   * Selects several items.
   * @param items Items to select - if falsy, all items on the canvas will be selected
   * @param add_to_current_selection If set, the items will be added to the current selection instead of replacing it
   */
  selectItems(items, add_to_current_selection) {
    const itemsToSelect = items ?? this.positionableItems;
    if (!add_to_current_selection) this.deselectAll();
    for (const item of itemsToSelect) this.select(item);
    this.onSelectionChange?.(this.selected_nodes);
    this.setDirty(true);
  }
  /**
   * selects several nodes (or adds them to the current selection)
   * @deprecated See {@link LGraphCanvas.selectItems}
   */
  selectNodes(nodes, add_to_current_selection) {
    this.selectItems(nodes, add_to_current_selection);
  }
  /** @deprecated See {@link LGraphCanvas.deselect} */
  deselectNode(node2) {
    this.deselect(node2);
  }
  /**
   * Deselects all items on the canvas.
   * @param keepSelected If set, this item will not be removed from the selection.
   */
  deselectAll(keepSelected) {
    if (!this.graph) return;
    const selected = this.selectedItems;
    if (!selected.size) return;
    const initialSelectionSize = selected.size;
    let wasSelected;
    for (const sel of selected) {
      if (sel === keepSelected) {
        wasSelected = sel;
        continue;
      }
      sel.onDeselected?.();
      sel.selected = false;
    }
    selected.clear();
    if (wasSelected) selected.add(wasSelected);
    this.setDirty(true);
    const oldNode = keepSelected?.id == null ? null : this.selected_nodes[keepSelected.id];
    this.selected_nodes = {};
    this.current_node = null;
    this.highlighted_links = {};
    if (keepSelected instanceof LGraphNode) {
      if (oldNode) this.selected_nodes[oldNode.id] = oldNode;
      if (keepSelected.inputs) {
        for (const input of keepSelected.inputs) {
          if (input.link == null) continue;
          this.highlighted_links[input.link] = true;
        }
      }
      if (keepSelected.outputs) {
        for (const id2 of keepSelected.outputs.flatMap((x2) => x2.links)) {
          if (id2 == null) continue;
          this.highlighted_links[id2] = true;
        }
      }
    }
    const finalSelectionSize = selected.size;
    if (initialSelectionSize !== finalSelectionSize) {
      this.state.selectionChanged = true;
      this.onSelectionChange?.(this.selected_nodes);
    }
  }
  /** @deprecated See {@link LGraphCanvas.deselectAll} */
  deselectAllNodes() {
    this.deselectAll();
  }
  /**
   * Deletes all selected items from the graph.
   * @todo Refactor deletion task to LGraph.  Selection is a canvas property, delete is a graph action.
   */
  deleteSelected() {
    const { graph } = this;
    if (!graph) throw new NullGraphError();
    this.emitBeforeChange();
    graph.beforeChange();
    for (const item of this.selectedItems) {
      if (item instanceof LGraphNode) {
        const node2 = item;
        if (node2.block_delete) continue;
        node2.connectInputToOutput();
        graph.remove(node2);
        this.onNodeDeselected?.(node2);
      } else if (item instanceof LGraphGroup) {
        graph.remove(item);
      } else if (item instanceof Reroute) {
        graph.removeReroute(item.id);
      }
    }
    this.selected_nodes = {};
    this.selectedItems.clear();
    this.current_node = null;
    this.highlighted_links = {};
    this.state.selectionChanged = true;
    this.onSelectionChange?.(this.selected_nodes);
    this.setDirty(true);
    graph.afterChange();
    this.emitAfterChange();
  }
  /**
   * deletes all nodes in the current selection from the graph
   * @deprecated See {@link LGraphCanvas.deleteSelected}
   */
  deleteSelectedNodes() {
    this.deleteSelected();
  }
  /**
   * centers the camera on a given node
   */
  centerOnNode(node2) {
    const dpi = window?.devicePixelRatio || 1;
    this.ds.offset[0] = -node2.pos[0] - node2.size[0] * 0.5 + this.canvas.width * 0.5 / (this.ds.scale * dpi);
    this.ds.offset[1] = -node2.pos[1] - node2.size[1] * 0.5 + this.canvas.height * 0.5 / (this.ds.scale * dpi);
    this.setDirty(true, true);
  }
  /**
   * adds some useful properties to a mouse event, like the position in graph coordinates
   */
  adjustMouseEvent(e2) {
    let clientX_rel = e2.clientX;
    let clientY_rel = e2.clientY;
    if (this.canvas) {
      const b = this.canvas.getBoundingClientRect();
      clientX_rel -= b.left;
      clientY_rel -= b.top;
    }
    e2.safeOffsetX = clientX_rel;
    e2.safeOffsetY = clientY_rel;
    if (e2.deltaX === void 0)
      e2.deltaX = clientX_rel - this.last_mouse_position[0];
    if (e2.deltaY === void 0)
      e2.deltaY = clientY_rel - this.last_mouse_position[1];
    this.last_mouse_position[0] = clientX_rel;
    this.last_mouse_position[1] = clientY_rel;
    e2.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0];
    e2.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1];
  }
  /**
   * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom
   */
  setZoom(value, zooming_center) {
    this.ds.changeScale(value, zooming_center);
    this.#dirty();
  }
  /**
   * converts a coordinate from graph coordinates to canvas2D coordinates
   */
  convertOffsetToCanvas(pos, out) {
    return this.ds.convertOffsetToCanvas(pos, out);
  }
  /**
   * converts a coordinate from Canvas2D coordinates to graph space
   */
  convertCanvasToOffset(pos, out) {
    return this.ds.convertCanvasToOffset(pos, out);
  }
  // converts event coordinates from canvas2D to graph coordinates
  convertEventToCanvasOffset(e2) {
    const rect = this.canvas.getBoundingClientRect();
    return this.convertCanvasToOffset([
      e2.clientX - rect.left,
      e2.clientY - rect.top
    ]);
  }
  /**
   * brings a node to front (above all other nodes)
   */
  bringToFront(node2) {
    const { graph } = this;
    if (!graph) throw new NullGraphError();
    const i = graph._nodes.indexOf(node2);
    if (i == -1) return;
    graph._nodes.splice(i, 1);
    graph._nodes.push(node2);
  }
  /**
   * sends a node to the back (below all other nodes)
   */
  sendToBack(node2) {
    const { graph } = this;
    if (!graph) throw new NullGraphError();
    const i = graph._nodes.indexOf(node2);
    if (i == -1) return;
    graph._nodes.splice(i, 1);
    graph._nodes.unshift(node2);
  }
  /**
   * Determines which nodes are visible and populates {@link out} with the results.
   * @param nodes The list of nodes to check - if falsy, all nodes in the graph will be checked
   * @param out Array to write visible nodes into - if falsy, a new array is created instead
   * @returns Array passed ({@link out}), or a new array containing all visible nodes
   */
  computeVisibleNodes(nodes, out) {
    const visible_nodes = out || [];
    visible_nodes.length = 0;
    if (!this.graph) throw new NullGraphError();
    const _nodes = nodes || this.graph._nodes;
    for (const node2 of _nodes) {
      node2.updateArea(this.ctx);
      if (!overlapBounding(this.visible_area, node2.renderArea)) continue;
      visible_nodes.push(node2);
    }
    return visible_nodes;
  }
  /**
   * Checks if a node is visible on the canvas.
   * @param node The node to check
   * @returns `true` if the node is visible, otherwise `false`
   */
  isNodeVisible(node2) {
    return this.#visible_node_ids.has(node2.id);
  }
  /**
   * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes)
   */
  draw(force_canvas, force_bgcanvas) {
    if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0)
      return;
    const now = LiteGraph.getTime();
    this.render_time = (now - this.last_draw_time) * 1e-3;
    this.last_draw_time = now;
    if (this.graph) this.ds.computeVisibleArea(this.viewport);
    if (this.dirty_canvas || force_canvas) {
      this.computeVisibleNodes(void 0, this.visible_nodes);
      this.#visible_node_ids = new Set(
        this.visible_nodes.map((node2) => node2.id)
      );
      const { subgraph } = this;
      if (subgraph) {
        subgraph.inputNode.arrange();
        subgraph.outputNode.arrange();
      }
    }
    if (this.dirty_bgcanvas || force_bgcanvas || this.always_render_background || this.graph?._last_trigger_time && now - this.graph._last_trigger_time < 1e3) {
      this.drawBackCanvas();
    }
    if (this.dirty_canvas || force_canvas) this.drawFrontCanvas();
    this.fps = this.render_time ? 1 / this.render_time : 0;
    this.frame++;
  }
  /**
   * draws the front canvas (the one containing all the nodes)
   */
  drawFrontCanvas() {
    this.dirty_canvas = false;
    const { ctx, canvas: canvas2, graph, linkConnector } = this;
    if (ctx.start2D && !this.viewport) {
      ctx.start2D();
      ctx.restore();
      ctx.setTransform(1, 0, 0, 1, 0, 0);
    }
    const area = this.viewport || this.dirty_area;
    if (area) {
      ctx.save();
      ctx.beginPath();
      ctx.rect(area[0], area[1], area[2], area[3]);
      ctx.clip();
    }
    this.#snapToGrid = this.#shiftDown || LiteGraph.alwaysSnapToGrid ? this.graph?.getSnapToGridSize() : void 0;
    if (this.clear_background) {
      if (area) ctx.clearRect(area[0], area[1], area[2], area[3]);
      else ctx.clearRect(0, 0, canvas2.width, canvas2.height);
    }
    if (this.bgcanvas == this.canvas) {
      this.drawBackCanvas();
    } else {
      const scale = window.devicePixelRatio;
      ctx.drawImage(
        this.bgcanvas,
        0,
        0,
        this.bgcanvas.width / scale,
        this.bgcanvas.height / scale
      );
    }
    this.onRender?.(canvas2, ctx);
    if (this.show_info) {
      const pos = this.fpsInfoLocation ?? area;
      this.renderInfo(ctx, pos?.[0] ?? 0, pos?.[1] ?? 0);
    }
    if (graph) {
      ctx.save();
      this.ds.toCanvasContext(ctx);
      const { visible_nodes } = this;
      const drawSnapGuides = this.#snapToGrid && (this.isDragging || layoutStore.isDraggingVueNodes.value);
      for (const node2 of visible_nodes) {
        ctx.save();
        if (drawSnapGuides && this.selectedItems.has(node2))
          this.drawSnapGuide(ctx, node2);
        ctx.translate(node2.pos[0], node2.pos[1]);
        this.drawNode(node2, ctx);
        ctx.restore();
      }
      this.subgraph?.draw(
        ctx,
        this.colourGetter,
        this.linkConnector.renderLinks[0]?.fromSlot,
        this.editor_alpha
      );
      if (this.render_execution_order) {
        this.drawExecutionOrder(ctx);
      }
      if (graph.config.links_ontop) {
        this.drawConnections(ctx);
      }
      if (linkConnector.isConnecting) {
        const { renderLinks } = linkConnector;
        const highlightPos = this.#getHighlightPosition();
        ctx.lineWidth = this.connections_width;
        for (const renderLink of renderLinks) {
          const {
            fromSlot,
            fromPos: pos,
            fromDirection,
            dragDirection
          } = renderLink;
          const connShape = fromSlot.shape;
          const connType = fromSlot.type;
          const colour = resolveConnectingLinkColor(connType);
          if (this.linkRenderer) {
            this.linkRenderer.renderDraggingLink(
              ctx,
              pos,
              highlightPos,
              colour,
              fromDirection,
              dragDirection,
              {
                ...this.buildLinkRenderContext(),
                linkMarkerShape: LinkMarkerShape.None
              }
            );
          }
          ctx.fillStyle = colour;
          ctx.beginPath();
          if (connType === LiteGraph.EVENT || connShape === RenderShape.BOX) {
            ctx.rect(pos[0] - 6 + 0.5, pos[1] - 5 + 0.5, 14, 10);
            ctx.rect(
              highlightPos[0] - 6 + 0.5,
              highlightPos[1] - 5 + 0.5,
              14,
              10
            );
          } else if (connShape === RenderShape.ARROW) {
            ctx.moveTo(pos[0] + 8, pos[1] + 0.5);
            ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5);
            ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5);
            ctx.closePath();
          } else {
            ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2);
            ctx.arc(highlightPos[0], highlightPos[1], 4, 0, Math.PI * 2);
          }
          ctx.fill();
        }
        this.#renderSnapHighlight(ctx, highlightPos);
      }
      if (!this.isDragging && this.over_link_center && this.render_link_tooltip) {
        this.drawLinkTooltip(ctx, this.over_link_center);
      } else {
        this.onDrawLinkTooltip?.(ctx, null);
      }
      this.onDrawForeground?.(ctx, this.visible_area);
      ctx.restore();
    }
    this.onDrawOverlay?.(ctx);
    if (area) ctx.restore();
  }
  /** @returns If the pointer is over a link centre marker, the link segment it belongs to.  Otherwise, `undefined`.  */
  #getLinkCentreOnPos(e2) {
    if (this.linkMarkerShape === LinkMarkerShape.None) {
      return void 0;
    }
    for (const linkSegment of this.renderedPaths) {
      const centre = linkSegment._pos;
      if (!centre) continue;
      if (isInRectangle(e2.canvasX, e2.canvasY, centre[0] - 4, centre[1] - 4, 8, 8)) {
        return linkSegment;
      }
    }
  }
  /** Get the target snap / highlight point in graph space */
  #getHighlightPosition() {
    return LiteGraph.snaps_for_comfy ? this.linkConnector.state.snapLinksPos ?? this._highlight_pos ?? this.graph_mouse : this.graph_mouse;
  }
  /**
   * Renders indicators showing where a link will connect if released.
   * Partial border over target node and a highlight over the slot itself.
   * @param ctx Canvas 2D context
   */
  #renderSnapHighlight(ctx, highlightPos) {
    const linkConnectorSnap = !!this.linkConnector.state.snapLinksPos;
    if (!this._highlight_pos && !linkConnectorSnap) return;
    ctx.fillStyle = "#ffcc00";
    ctx.beginPath();
    const shape2 = this._highlight_input?.shape;
    if (shape2 === RenderShape.ARROW) {
      ctx.moveTo(highlightPos[0] + 8, highlightPos[1] + 0.5);
      ctx.lineTo(highlightPos[0] - 4, highlightPos[1] + 6 + 0.5);
      ctx.lineTo(highlightPos[0] - 4, highlightPos[1] - 6 + 0.5);
      ctx.closePath();
    } else {
      ctx.arc(highlightPos[0], highlightPos[1], 6, 0, Math.PI * 2);
    }
    ctx.fill();
    const { linkConnector } = this;
    const { overReroute, overWidget } = linkConnector;
    if (!LiteGraph.snap_highlights_node || !linkConnector.isConnecting || linkConnectorSnap)
      return;
    overReroute?.drawHighlight(ctx, "#ffcc00aa");
    const node2 = this.node_over;
    if (!node2) return;
    const { strokeStyle, lineWidth } = ctx;
    const area = node2.boundingRect;
    const gap = 3;
    const radius = LiteGraph.ROUND_RADIUS + gap;
    const x2 = area[0] - gap;
    const y = area[1] - gap;
    const width2 = area[2] + gap * 2;
    const height = area[3] + gap * 2;
    ctx.beginPath();
    ctx.roundRect(x2, y, width2, height, radius);
    const start = linkConnector.state.connectingTo === "output" ? 0 : 1;
    const inverter = start ? -1 : 1;
    const hx = highlightPos[0];
    const hy = highlightPos[1];
    const gRadius = width2 < height ? width2 : width2 * Math.max(height / width2, 0.5);
    const gradient = ctx.createRadialGradient(hx, hy, 0, hx, hy, gRadius);
    gradient.addColorStop(1, "#00000000");
    gradient.addColorStop(0, "#ffcc00aa");
    const linearGradient = ctx.createLinearGradient(x2, y, x2 + width2, y);
    linearGradient.addColorStop(0.5, "#00000000");
    linearGradient.addColorStop(start + 0.67 * inverter, "#ddeeff33");
    linearGradient.addColorStop(start + inverter, "#ffcc0055");
    ctx.setLineDash([radius, radius * 1e-3]);
    ctx.lineWidth = 1;
    ctx.strokeStyle = linearGradient;
    ctx.stroke();
    if (overWidget) {
      const { computedHeight } = overWidget;
      ctx.beginPath();
      const {
        pos: [nodeX, nodeY]
      } = node2;
      const height2 = LiteGraph.NODE_WIDGET_HEIGHT;
      if (overWidget.type.startsWith("custom") && computedHeight != null && computedHeight > height2 * 2) {
        ctx.rect(
          nodeX + 9,
          nodeY + overWidget.y + 9,
          (overWidget.width ?? area[2]) - 18,
          computedHeight - 18
        );
      } else {
        ctx.roundRect(
          nodeX + BaseWidget.margin,
          nodeY + overWidget.y,
          overWidget.width ?? area[2],
          height2,
          height2 * 0.5
        );
      }
      ctx.stroke();
    }
    ctx.strokeStyle = gradient;
    ctx.stroke();
    ctx.setLineDash([]);
    ctx.lineWidth = lineWidth;
    ctx.strokeStyle = strokeStyle;
  }
  /**
   * draws some useful stats in the corner of the canvas
   */
  renderInfo(ctx, x2, y) {
    x2 = x2 || 10;
    y = y || this.canvas.offsetHeight - 80;
    ctx.save();
    ctx.translate(x2, y);
    ctx.font = `10px ${LiteGraph.DEFAULT_FONT}`;
    ctx.fillStyle = "#888";
    ctx.textAlign = "left";
    if (this.graph) {
      ctx.fillText(`T: ${this.graph.globaltime.toFixed(2)}s`, 5, 13 * 1);
      ctx.fillText(`I: ${this.graph.iteration}`, 5, 13 * 2);
      ctx.fillText(
        `N: ${this.graph._nodes.length} [${this.visible_nodes.length}]`,
        5,
        13 * 3
      );
      ctx.fillText(`V: ${this.graph._version}`, 5, 13 * 4);
      ctx.fillText(`FPS:${this.fps.toFixed(2)}`, 5, 13 * 5);
    } else {
      ctx.fillText("No graph selected", 5, 13 * 1);
    }
    ctx.restore();
  }
  /**
   * draws the back canvas (the one containing the background and the connections)
   */
  drawBackCanvas() {
    const canvas2 = this.bgcanvas;
    if (canvas2.width != this.canvas.width || canvas2.height != this.canvas.height) {
      canvas2.width = this.canvas.width;
      canvas2.height = this.canvas.height;
    }
    if (!this.bgctx) {
      this.bgctx = this.bgcanvas.getContext("2d");
    }
    const ctx = this.bgctx;
    if (!ctx) throw new TypeError("Background canvas context was null.");
    const viewport = this.viewport || [
      0,
      0,
      ctx.canvas.width,
      ctx.canvas.height
    ];
    if (this.clear_background) {
      ctx.clearRect(viewport[0], viewport[1], viewport[2], viewport[3]);
    }
    const bg_already_painted = this.onRenderBackground ? this.onRenderBackground(canvas2, ctx) : false;
    if (!this.viewport) {
      const scale = window.devicePixelRatio;
      ctx.restore();
      ctx.setTransform(scale, 0, 0, scale, 0, 0);
    }
    if (this.graph) {
      ctx.save();
      this.ds.toCanvasContext(ctx);
      if (this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color) {
        ctx.fillStyle = this.clear_background_color;
        ctx.fillRect(
          this.visible_area[0],
          this.visible_area[1],
          this.visible_area[2],
          this.visible_area[3]
        );
      }
      if (this.background_image && this.ds.scale > 0.5 && !bg_already_painted) {
        if (this.zoom_modify_alpha) {
          ctx.globalAlpha = (1 - 0.5 / this.ds.scale) * this.editor_alpha;
        } else {
          ctx.globalAlpha = this.editor_alpha;
        }
        ctx.imageSmoothingEnabled = false;
        if (!this._bg_img || this._bg_img.name != this.background_image) {
          this._bg_img = new Image();
          this._bg_img.name = this.background_image;
          this._bg_img.src = this.background_image;
          this._bg_img.addEventListener("load", () => {
            this.draw(true, true);
          });
        }
        let pattern = this._pattern;
        if (pattern == null && this._bg_img.width > 0) {
          pattern = ctx.createPattern(this._bg_img, "repeat") ?? void 0;
          this._pattern_img = this._bg_img;
          this._pattern = pattern;
        }
        if (pattern) {
          ctx.fillStyle = pattern;
          ctx.fillRect(
            this.visible_area[0],
            this.visible_area[1],
            this.visible_area[2],
            this.visible_area[3]
          );
          ctx.fillStyle = "transparent";
        }
        ctx.globalAlpha = 1;
        ctx.imageSmoothingEnabled = true;
      }
      if (this.bg_tint) {
        ctx.fillStyle = this.bg_tint;
        ctx.fillRect(
          this.visible_area[0],
          this.visible_area[1],
          this.visible_area[2],
          this.visible_area[3]
        );
        ctx.fillStyle = "transparent";
      }
      if (this.graph._groups.length) {
        this.drawGroups(canvas2, ctx);
      }
      this.onDrawBackground?.(ctx, this.visible_area);
      if (this.render_canvas_border) {
        ctx.strokeStyle = "#235";
        ctx.strokeRect(0, 0, canvas2.width, canvas2.height);
      }
      if (this.render_connections_shadows) {
        ctx.shadowColor = "#000";
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 0;
        ctx.shadowBlur = 6;
      } else {
        ctx.shadowColor = "rgba(0,0,0,0)";
      }
      this.drawConnections(ctx);
      ctx.shadowColor = "rgba(0,0,0,0)";
      ctx.restore();
    }
    this.dirty_bgcanvas = false;
    this.dirty_canvas = true;
  }
  /**
   * draws the given node inside the canvas
   */
  drawNode(node2, ctx) {
    this.current_node = node2;
    if (LiteGraph.vueNodesMode) {
      node2._setConcreteSlots();
      if (!node2.collapsed) {
        node2.arrange();
      }
      return;
    }
    const color2 = node2.renderingColor;
    const bgcolor = node2.renderingBgColor;
    ctx.globalAlpha = this.getNodeModeAlpha(node2);
    if (this.render_shadows && !this.low_quality) {
      ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;
      ctx.shadowOffsetX = 2 * this.ds.scale;
      ctx.shadowOffsetY = 2 * this.ds.scale;
      ctx.shadowBlur = 3 * this.ds.scale;
    } else {
      ctx.shadowColor = "transparent";
    }
    if (node2.flags.collapsed && node2.onDrawCollapsed?.(ctx, this) == true)
      return;
    const shape2 = node2._shape || RenderShape.BOX;
    const size = temp_vec2;
    size[0] = node2.renderingSize[0];
    size[1] = node2.renderingSize[1];
    if (node2.collapsed) {
      ctx.font = this.inner_text_font;
    }
    if (node2.clip_area) {
      ctx.save();
      ctx.beginPath();
      if (shape2 == RenderShape.BOX) {
        ctx.rect(0, 0, size[0], size[1]);
      } else if (shape2 == RenderShape.ROUND) {
        ctx.roundRect(0, 0, size[0], size[1], [10]);
      } else if (shape2 == RenderShape.CIRCLE) {
        ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI * 2);
      }
      ctx.clip();
    }
    this.drawNodeShape(node2, ctx, size, color2, bgcolor, !!node2.selected);
    if (node2.title_buttons && !node2.flags.collapsed) {
      const title_height = LiteGraph.NODE_TITLE_HEIGHT;
      let current_x = size[0];
      for (let i = 0; i < node2.title_buttons.length; i++) {
        const button = node2.title_buttons[i];
        if (!button.visible) {
          continue;
        }
        const button_width = button.getWidth(ctx);
        current_x -= button_width;
        const button_y = -title_height + (title_height - button.height) / 2;
        button.draw(ctx, current_x, button_y);
        current_x -= 2;
      }
    }
    if (!this.low_quality) {
      node2.drawBadges(ctx);
    }
    ctx.shadowColor = "transparent";
    ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR;
    node2.onDrawForeground?.(ctx, this, this.canvas);
    ctx.font = this.inner_text_font;
    node2._setConcreteSlots();
    if (!node2.collapsed) {
      node2.arrange();
      node2.drawSlots(ctx, {
        fromSlot: this.linkConnector.renderLinks[0]?.fromSlot,
        colorContext: this.colourGetter,
        editorAlpha: this.editor_alpha,
        lowQuality: this.low_quality
      });
      ctx.textAlign = "left";
      ctx.globalAlpha = 1;
      this.drawNodeWidgets(node2, null, ctx);
    } else if (this.render_collapsed_slots) {
      node2.drawCollapsedSlots(ctx);
    }
    if (node2.clip_area) {
      ctx.restore();
    }
    ctx.globalAlpha = 1;
  }
  /**
   * Draws the link mouseover effect and tooltip.
   * @param ctx Canvas 2D context to draw on
   * @param link The link to render the mouseover effect for
   * @remarks
   * Called against {@link LGraphCanvas.over_link_center}.
   * @todo Split tooltip from hover, so it can be drawn / eased separately
   */
  drawLinkTooltip(ctx, link) {
    const pos = link._pos;
    ctx.fillStyle = "black";
    ctx.beginPath();
    if (this.linkMarkerShape === LinkMarkerShape.Arrow) {
      const transform = ctx.getTransform();
      ctx.translate(pos[0], pos[1]);
      if (Number.isFinite(link._centreAngle))
        ctx.rotate(link._centreAngle);
      ctx.moveTo(-2, -3);
      ctx.lineTo(4, 0);
      ctx.lineTo(-2, 3);
      ctx.setTransform(transform);
    } else if (this.linkMarkerShape == null || this.linkMarkerShape === LinkMarkerShape.Circle) {
      ctx.arc(pos[0], pos[1], 3, 0, Math.PI * 2);
    }
    ctx.fill();
    const { data } = link;
    if (data == null) return;
    if (this.onDrawLinkTooltip?.(ctx, link, this) == true) return;
    let text = null;
    if (typeof data === "number") text = data.toFixed(2);
    else if (typeof data === "string") text = `"${data}"`;
    else if (typeof data === "boolean") text = String(data);
    else if (data.toToolTip) text = data.toToolTip();
    else text = `[${data.constructor.name}]`;
    if (text == null) return;
    text = text.substring(0, 30);
    ctx.font = "14px Courier New";
    const info = ctx.measureText(text);
    const w = info.width + 20;
    const h2 = 24;
    ctx.shadowColor = "black";
    ctx.shadowOffsetX = 2;
    ctx.shadowOffsetY = 2;
    ctx.shadowBlur = 3;
    ctx.fillStyle = "#454";
    ctx.beginPath();
    ctx.roundRect(pos[0] - w * 0.5, pos[1] - 15 - h2, w, h2, [3]);
    ctx.moveTo(pos[0] - 10, pos[1] - 15);
    ctx.lineTo(pos[0] + 10, pos[1] - 15);
    ctx.lineTo(pos[0], pos[1] - 5);
    ctx.fill();
    ctx.shadowColor = "transparent";
    ctx.textAlign = "center";
    ctx.fillStyle = "#CEC";
    ctx.fillText(text, pos[0], pos[1] - 15 - h2 * 0.3);
  }
  /**
   * Draws the shape of the given node on the canvas
   * @param node The node to draw
   * @param ctx 2D canvas rendering context used to draw
   * @param size Size of the background to draw, in graph units.  Differs from node size if collapsed, etc.
   * @param fgcolor Foreground colour - used for text
   * @param bgcolor Background colour of the node
   * @param _selected Whether to render the node as selected.  Likely to be removed in future, as current usage is simply the selected property of the node.
   */
  drawNodeShape(node2, ctx, size, fgcolor, bgcolor, _selected) {
    ctx.strokeStyle = fgcolor;
    ctx.fillStyle = bgcolor;
    const title_height = LiteGraph.NODE_TITLE_HEIGHT;
    const { low_quality } = this;
    const { collapsed } = node2.flags;
    const shape2 = node2.renderingShape;
    const { title_mode } = node2;
    const render_title = title_mode == TitleMode.TRANSPARENT_TITLE || title_mode == TitleMode.NO_TITLE ? false : true;
    const area = tmp_area;
    area.set(node2.boundingRect);
    area[0] -= node2.pos[0];
    area[1] -= node2.pos[1];
    const old_alpha = ctx.globalAlpha;
    ctx.beginPath();
    if (shape2 == RenderShape.BOX || low_quality) {
      ctx.rect(area[0], area[1], area[2], area[3]);
    } else if (shape2 == RenderShape.ROUND || shape2 == RenderShape.CARD) {
      ctx.roundRect(
        area[0],
        area[1],
        area[2],
        area[3],
        shape2 == RenderShape.CARD ? [LiteGraph.ROUND_RADIUS, LiteGraph.ROUND_RADIUS, 0, 0] : [LiteGraph.ROUND_RADIUS]
      );
    } else if (shape2 == RenderShape.CIRCLE) {
      ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI * 2);
    }
    ctx.fill();
    if (!collapsed && render_title) {
      ctx.shadowColor = "transparent";
      ctx.fillStyle = "rgba(0,0,0,0.2)";
      ctx.fillRect(0, -1, area[2], 2);
    }
    ctx.shadowColor = "transparent";
    node2.onDrawBackground?.(ctx);
    if (render_title || title_mode == TitleMode.TRANSPARENT_TITLE) {
      node2.drawTitleBarBackground(ctx, {
        scale: this.ds.scale,
        low_quality
      });
      node2.drawTitleBox(ctx, {
        scale: this.ds.scale,
        low_quality,
        box_size: 10
      });
      ctx.globalAlpha = old_alpha;
      node2.drawTitleText(ctx, {
        scale: this.ds.scale,
        default_title_color: this.node_title_color,
        low_quality
      });
      node2.onDrawTitle?.(ctx);
    }
    for (const getStyle of Object.values(node2.strokeStyles)) {
      const strokeStyle = getStyle.call(node2);
      if (strokeStyle) {
        strokeShape(ctx, area, {
          shape: shape2,
          title_height,
          title_mode,
          collapsed,
          ...strokeStyle
        });
      }
    }
    node2.drawProgressBar(ctx);
    if (node2.execute_triggered != null && node2.execute_triggered > 0)
      node2.execute_triggered--;
    if (node2.action_triggered != null && node2.action_triggered > 0)
      node2.action_triggered--;
  }
  /**
   * Draws a snap guide for a {@link Positionable} item.
   *
   * Initial design was a simple white rectangle representing the location the
   * item would land if dropped.
   * @param ctx The 2D canvas context to draw on
   * @param item The item to draw a snap guide for
   * @param shape The shape of the snap guide to draw
   * @todo Update to align snapping with boundingRect
   * @todo Shapes
   */
  drawSnapGuide(ctx, item, shape2 = RenderShape.ROUND) {
    const snapGuide = temp;
    snapGuide.set(item.boundingRect);
    const { pos } = item;
    const offsetX = pos[0] - snapGuide[0];
    const offsetY = pos[1] - snapGuide[1];
    snapGuide[0] += offsetX;
    snapGuide[1] += offsetY;
    if (this.#snapToGrid) snapPoint(snapGuide, this.#snapToGrid);
    snapGuide[0] -= offsetX;
    snapGuide[1] -= offsetY;
    const { globalAlpha } = ctx;
    ctx.globalAlpha = 1;
    ctx.beginPath();
    const [x2, y, w, h2] = snapGuide;
    if (shape2 === RenderShape.CIRCLE) {
      const midX = x2 + w * 0.5;
      const midY = y + h2 * 0.5;
      const radius = Math.min(w * 0.5, h2 * 0.5);
      ctx.arc(midX, midY, radius, 0, Math.PI * 2);
    } else {
      ctx.rect(x2, y, w, h2);
    }
    ctx.lineWidth = 0.5;
    ctx.strokeStyle = "#FFFFFF66";
    ctx.fillStyle = "#FFFFFF22";
    ctx.fill();
    ctx.stroke();
    ctx.globalAlpha = globalAlpha;
  }
  drawConnections(ctx) {
    this.renderedPaths.clear();
    if (this.links_render_mode === LinkRenderType.HIDDEN_LINK) return;
    const { graph, subgraph } = this;
    if (!graph) throw new NullGraphError();
    const visibleReroutes = [];
    const now = LiteGraph.getTime();
    const { visible_area } = this;
    margin_area[0] = visible_area[0] - 20;
    margin_area[1] = visible_area[1] - 20;
    margin_area[2] = visible_area[2] + 40;
    margin_area[3] = visible_area[3] + 40;
    ctx.lineWidth = this.connections_width;
    ctx.fillStyle = "#AAA";
    ctx.strokeStyle = "#AAA";
    ctx.globalAlpha = this.editor_alpha;
    const nodes = graph._nodes;
    for (const node2 of nodes) {
      const { inputs } = node2;
      if (!inputs?.length) continue;
      for (const [i, input] of inputs.entries()) {
        if (!input || input.link == null) continue;
        const link_id = input.link;
        const link = graph._links.get(link_id);
        if (!link) continue;
        const endPos = LiteGraph.vueNodesMode ? getSlotPosition(node2, i, true) : node2.getInputPos(i);
        const start_node = graph.getNodeById(link.origin_id);
        if (start_node == null) continue;
        const outputId = link.origin_slot;
        const startPos = outputId === -1 ? [start_node.pos[0] + 10, start_node.pos[1] + 10] : LiteGraph.vueNodesMode ? getSlotPosition(start_node, outputId, false) : start_node.getOutputPos(outputId);
        const output = start_node.outputs[outputId];
        if (!output) continue;
        this.#renderAllLinkSegments(
          ctx,
          link,
          startPos,
          endPos,
          visibleReroutes,
          now,
          output.dir,
          input.dir
        );
      }
    }
    if (subgraph) {
      for (const output of subgraph.inputNode.slots) {
        if (!output.linkIds.length) continue;
        for (const linkId of output.linkIds) {
          const resolved = LLink.resolve(linkId, graph);
          if (!resolved) continue;
          const { link, inputNode, input } = resolved;
          if (!inputNode || !input) continue;
          const endPos = LiteGraph.vueNodesMode ? getSlotPosition(inputNode, link.target_slot, true) : inputNode.getInputPos(link.target_slot);
          this.#renderAllLinkSegments(
            ctx,
            link,
            output.pos,
            endPos,
            visibleReroutes,
            now,
            input.dir,
            input.dir
          );
        }
      }
      for (const input of subgraph.outputNode.slots) {
        if (!input.linkIds.length) continue;
        const resolved = LLink.resolve(input.linkIds[0], graph);
        if (!resolved) continue;
        const { link, outputNode, output } = resolved;
        if (!outputNode || !output) continue;
        const startPos = LiteGraph.vueNodesMode ? getSlotPosition(outputNode, link.origin_slot, false) : outputNode.getOutputPos(link.origin_slot);
        this.#renderAllLinkSegments(
          ctx,
          link,
          startPos,
          input.pos,
          visibleReroutes,
          now,
          output.dir,
          input.dir
        );
      }
    }
    if (graph.floatingLinks.size > 0) {
      this.#renderFloatingLinks(ctx, graph, visibleReroutes, now);
    }
    const rerouteSet = this.#visibleReroutes;
    rerouteSet.clear();
    visibleReroutes.sort((a, b) => a.linkIds.size - b.linkIds.size);
    for (const reroute of visibleReroutes) {
      rerouteSet.add(reroute);
      if (this.#snapToGrid && this.isDragging && this.selectedItems.has(reroute)) {
        this.drawSnapGuide(ctx, reroute, RenderShape.CIRCLE);
      }
      reroute.draw(ctx, this._pattern);
      if (!this.pointer.isDown) reroute.drawSlots(ctx);
    }
    ctx.globalAlpha = 1;
  }
  getNodeModeAlpha(node2) {
    return node2.mode === LGraphEventMode.BYPASS ? 0.2 : node2.mode === LGraphEventMode.NEVER ? 0.4 : this.editor_alpha;
  }
  #renderFloatingLinks(ctx, graph, visibleReroutes, now) {
    const { globalAlpha } = ctx;
    ctx.globalAlpha = globalAlpha * 0.33;
    for (const link of graph.floatingLinks.values()) {
      const reroutes = LLink.getReroutes(graph, link);
      const firstReroute = reroutes[0];
      const reroute = reroutes.at(-1);
      if (!firstReroute || !reroute?.floating) continue;
      if (reroute.floating.slotType === "input") {
        const node2 = graph.getNodeById(link.target_id);
        if (!node2) continue;
        const startPos = firstReroute.pos;
        const endPos = LiteGraph.vueNodesMode ? getSlotPosition(node2, link.target_slot, true) : node2.getInputPos(link.target_slot);
        const endDirection = node2.inputs[link.target_slot]?.dir;
        firstReroute._dragging = true;
        this.#renderAllLinkSegments(
          ctx,
          link,
          startPos,
          endPos,
          visibleReroutes,
          now,
          LinkDirection.CENTER,
          endDirection,
          true
        );
      } else {
        const node2 = graph.getNodeById(link.origin_id);
        if (!node2) continue;
        const startPos = LiteGraph.vueNodesMode ? getSlotPosition(node2, link.origin_slot, false) : node2.getOutputPos(link.origin_slot);
        const endPos = reroute.pos;
        const startDirection = node2.outputs[link.origin_slot]?.dir;
        link._dragging = true;
        this.#renderAllLinkSegments(
          ctx,
          link,
          startPos,
          endPos,
          visibleReroutes,
          now,
          startDirection,
          LinkDirection.CENTER,
          true
        );
      }
    }
    ctx.globalAlpha = globalAlpha;
  }
  #renderAllLinkSegments(ctx, link, startPos, endPos, visibleReroutes, now, startDirection, endDirection, disabled = false) {
    const { graph, renderedPaths } = this;
    if (!graph) return;
    const reroutes = LLink.getReroutes(graph, link);
    const points = [
      startPos,
      ...reroutes.map((x2) => x2.pos),
      endPos
    ];
    const pointsX = points.map((x2) => x2[0]);
    const pointsY = points.map((x2) => x2[1]);
    link_bounding[0] = Math.min(...pointsX);
    link_bounding[1] = Math.min(...pointsY);
    link_bounding[2] = Math.max(...pointsX) - link_bounding[0];
    link_bounding[3] = Math.max(...pointsY) - link_bounding[1];
    if (!overlapBounding(link_bounding, margin_area)) return;
    const start_dir = startDirection || LinkDirection.RIGHT;
    const end_dir = endDirection || LinkDirection.LEFT;
    if (reroutes.length) {
      let startControl;
      const l = reroutes.length;
      for (let j = 0; j < l; j++) {
        const reroute = reroutes[j];
        if (!renderedPaths.has(reroute)) {
          renderedPaths.add(reroute);
          visibleReroutes.push(reroute);
          reroute._colour = link.color || LGraphCanvas.link_type_colors[link.type] || this.default_link_color;
          const prevReroute = graph.getReroute(reroute.parentId);
          const rerouteStartPos = prevReroute?.pos ?? startPos;
          reroute.calculateAngle(this.last_draw_time, graph, rerouteStartPos);
          if (!reroute._dragging) {
            this.renderLink(
              ctx,
              rerouteStartPos,
              reroute.pos,
              link,
              false,
              0,
              null,
              startControl === void 0 ? start_dir : LinkDirection.CENTER,
              LinkDirection.CENTER,
              {
                startControl,
                endControl: reroute.controlPoint,
                reroute,
                disabled
              }
            );
          }
        }
        if (!startControl && reroutes.at(-1)?.floating?.slotType === "input") {
          startControl = [0, 0];
        } else {
          const nextPos = reroutes[j + 1]?.pos ?? endPos;
          const dist = Math.min(
            Reroute.maxSplineOffset,
            distance(reroute.pos, nextPos) * 0.25
          );
          startControl = [dist * reroute.cos, dist * reroute.sin];
        }
      }
      if (link._dragging) return;
      const segmentStartPos = points.at(-2) ?? startPos;
      this.renderLink(
        ctx,
        segmentStartPos,
        endPos,
        link,
        false,
        0,
        null,
        LinkDirection.CENTER,
        end_dir,
        { startControl, disabled }
      );
    } else if (!link._dragging) {
      this.renderLink(
        ctx,
        startPos,
        endPos,
        link,
        false,
        0,
        null,
        start_dir,
        end_dir
      );
    }
    renderedPaths.add(link);
    if (link?._last_time && now - link._last_time < 1e3) {
      const f = 2 - (now - link._last_time) * 2e-3;
      const tmp = ctx.globalAlpha;
      ctx.globalAlpha = tmp * f;
      this.renderLink(
        ctx,
        startPos,
        endPos,
        link,
        true,
        f,
        "white",
        start_dir,
        end_dir
      );
      ctx.globalAlpha = tmp;
    }
  }
  /**
   * Build LinkRenderContext from canvas properties
   * Helper method for using LitegraphLinkAdapter
   */
  buildLinkRenderContext() {
    return {
      // Canvas settings
      renderMode: this.links_render_mode,
      connectionWidth: this.connections_width,
      renderBorder: this.render_connections_border,
      lowQuality: this.low_quality,
      highQualityRender: this.highquality_render,
      scale: this.ds.scale,
      linkMarkerShape: this.linkMarkerShape,
      renderConnectionArrows: this.render_connection_arrows,
      // State
      highlightedLinks: new Set(Object.keys(this.highlighted_links)),
      // Colors
      defaultLinkColor: this.default_link_color,
      linkTypeColors: LGraphCanvas.link_type_colors,
      // Pattern for disabled links
      disabledPattern: this._pattern
    };
  }
  /**
   * draws a link between two points
   * @param ctx Canvas 2D rendering context
   * @param a start pos
   * @param b end pos
   * @param link the link object with all the link info
   * @param skip_border ignore the shadow of the link
   * @param flow show flow animation (for events)
   * @param color the color for the link
   * @param start_dir the direction enum
   * @param end_dir the direction enum
   */
  renderLink(ctx, a, b, link, skip_border, flow, color2, start_dir, end_dir, {
    startControl,
    endControl,
    reroute,
    num_sublines = 1,
    disabled = false
  } = {}) {
    if (this.linkRenderer) {
      const context = this.buildLinkRenderContext();
      this.linkRenderer.renderLinkDirect(
        ctx,
        a,
        b,
        link,
        skip_border,
        flow,
        color2,
        start_dir,
        end_dir,
        context,
        {
          reroute,
          startControl,
          endControl,
          num_sublines,
          disabled
        }
      );
    }
  }
  drawExecutionOrder(ctx) {
    ctx.shadowColor = "transparent";
    ctx.globalAlpha = 0.25;
    ctx.textAlign = "center";
    ctx.strokeStyle = "white";
    ctx.globalAlpha = 0.75;
    const { visible_nodes } = this;
    for (const node2 of visible_nodes) {
      ctx.fillStyle = "black";
      ctx.fillRect(
        node2.pos[0] - LiteGraph.NODE_TITLE_HEIGHT,
        node2.pos[1] - LiteGraph.NODE_TITLE_HEIGHT,
        LiteGraph.NODE_TITLE_HEIGHT,
        LiteGraph.NODE_TITLE_HEIGHT
      );
      if (node2.order == 0) {
        ctx.strokeRect(
          node2.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,
          node2.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,
          LiteGraph.NODE_TITLE_HEIGHT,
          LiteGraph.NODE_TITLE_HEIGHT
        );
      }
      ctx.fillStyle = "#FFF";
      ctx.fillText(
        toString(node2.order),
        node2.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5,
        node2.pos[1] - 6
      );
    }
    ctx.globalAlpha = 1;
  }
  /**
   * draws the widgets stored inside a node
   * @deprecated Use {@link LGraphNode.drawWidgets} instead.
   * @remarks Currently there are extensions hijacking this function, so we cannot remove it.
   */
  drawNodeWidgets(node2, _posY, ctx) {
    node2.drawWidgets(ctx, {
      lowQuality: this.low_quality,
      editorAlpha: this.getNodeModeAlpha(node2)
    });
  }
  /**
   * draws every group area in the background
   */
  drawGroups(canvas2, ctx) {
    if (!this.graph) return;
    const groups = this.graph._groups;
    ctx.save();
    ctx.globalAlpha = 0.5 * this.editor_alpha;
    const drawSnapGuides = this.#snapToGrid && (this.isDragging || layoutStore.isDraggingVueNodes.value);
    for (const group of groups) {
      if (!overlapBounding(this.visible_area, group._bounding)) {
        continue;
      }
      if (drawSnapGuides && this.selectedItems.has(group))
        this.drawSnapGuide(ctx, group);
      group.draw(this, ctx);
    }
    ctx.restore();
  }
  /**
   * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode
   * @todo Remove or rewrite
   */
  resize(width2, height) {
    if (!width2 && !height) {
      const parent = this.canvas.parentElement;
      if (!parent)
        throw new TypeError(
          "Attempted to resize canvas, but parent element was null."
        );
      width2 = parent.offsetWidth;
      height = parent.offsetHeight;
    }
    if (this.canvas.width == width2 && this.canvas.height == height) return;
    this.canvas.width = width2 ?? 0;
    this.canvas.height = height ?? 0;
    this.bgcanvas.width = this.canvas.width;
    this.bgcanvas.height = this.canvas.height;
    this.setDirty(true, true);
  }
  onNodeSelectionChange() {
  }
  /**
   * Determines the furthest nodes in each direction for the currently selected nodes
   */
  boundaryNodesForSelection() {
    return LGraphCanvas.getBoundaryNodes(this.selected_nodes);
  }
  showLinkMenu(segment, e2) {
    const { graph } = this;
    if (!graph) throw new NullGraphError();
    const title = "data" in segment && segment.data != null ? segment.data.constructor.name : void 0;
    const { origin_id, origin_slot } = segment;
    if (origin_id == null || origin_slot == null) {
      new LiteGraph.ContextMenu(["Link has no origin"], {
        event: e2,
        title
      });
      return false;
    }
    const node_left = graph.getNodeById(origin_id);
    const fromType = node_left?.outputs?.[origin_slot]?.type;
    const options = ["Add Node", "Add Reroute", null, "Delete", null];
    const menu2 = new LiteGraph.ContextMenu(options, {
      event: e2,
      title,
      callback: inner_clicked.bind(this)
    });
    return false;
    function inner_clicked(v2, options2, e22) {
      if (!graph) throw new NullGraphError();
      switch (v2) {
        case "Add Node":
          LGraphCanvas.onMenuAdd(null, null, e22, menu2, (node2) => {
            if (!node2?.inputs?.length || !node2?.outputs?.length || origin_slot == null)
              return;
            const options3 = { afterRerouteId: segment.parentId };
            if (node_left?.connectByType(
              origin_slot,
              node2,
              fromType ?? "*",
              options3
            )) {
              node2.pos[0] -= node2.size[0] * 0.5;
            }
          });
          break;
        case "Add Reroute": {
          try {
            this.emitBeforeChange();
            this.adjustMouseEvent(e22);
            graph.createReroute(segment._pos, segment);
            this.setDirty(false, true);
          } catch (error) {
            console.error(error);
          } finally {
            this.emitAfterChange();
          }
          break;
        }
        case "Delete": {
          const linkId = segment instanceof Reroute ? segment.linkIds.values().next().value : segment.id;
          if (linkId !== void 0) {
            graph.removeLink(linkId);
            layoutStore.deleteLinkLayout(linkId);
          }
          break;
        }
      }
    }
    __name(inner_clicked, "inner_clicked");
  }
  createDefaultNodeForSlot(optPass) {
    const opts = Object.assign(
      {
        nodeFrom: null,
        slotFrom: null,
        nodeTo: null,
        slotTo: null,
        position: [0, 0],
        nodeType: void 0,
        posAdd: [0, 0],
        posSizeFix: [0, 0]
      },
      optPass
    );
    const { afterRerouteId } = opts;
    const isFrom = opts.nodeFrom && opts.slotFrom !== null;
    const isTo = !isFrom && opts.nodeTo && opts.slotTo !== null;
    if (!isFrom && !isTo) {
      console.warn(
        `No data passed to createDefaultNodeForSlot`,
        opts.nodeFrom,
        opts.slotFrom,
        opts.nodeTo,
        opts.slotTo
      );
      return false;
    }
    if (!opts.nodeType) {
      console.warn("No type to createDefaultNodeForSlot");
      return false;
    }
    const nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;
    if (!nodeX)
      throw new TypeError("nodeX was null when creating default node for slot.");
    let slotX = isFrom ? opts.slotFrom : opts.slotTo;
    let iSlotConn = false;
    if (nodeX instanceof SubgraphIONodeBase) {
      if (typeof slotX !== "object" || !slotX) {
        console.warn("Cant get slot information", slotX);
        return false;
      }
      const { name: name2 } = slotX;
      iSlotConn = nodeX.slots.findIndex((s) => s.name === name2);
      slotX = nodeX.slots[iSlotConn];
      if (!slotX) {
        console.warn("Cant get slot information", slotX);
        return false;
      }
    } else {
      switch (typeof slotX) {
        case "string":
          iSlotConn = isFrom ? nodeX.findOutputSlot(slotX, false) : nodeX.findInputSlot(slotX, false);
          slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
          break;
        case "object":
          if (slotX === null) {
            console.warn("Cant get slot information", slotX);
            return false;
          }
          iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);
          break;
        case "number":
          iSlotConn = slotX;
          slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
          break;
        case "undefined":
        default:
          console.warn("Cant get slot information", slotX);
          return false;
      }
    }
    const fromSlotType = slotX.type == LiteGraph.EVENT ? "_event_" : slotX.type;
    const slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;
    if (slotTypesDefault?.[fromSlotType]) {
      let nodeNewType = false;
      if (typeof slotTypesDefault[fromSlotType] == "object") {
        for (const typeX in slotTypesDefault[fromSlotType]) {
          if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO") {
            nodeNewType = slotTypesDefault[fromSlotType][typeX];
            break;
          }
        }
      } else if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") {
        nodeNewType = slotTypesDefault[fromSlotType];
      }
      if (nodeNewType) {
        let nodeNewOpts = false;
        if (typeof nodeNewType == "object" && nodeNewType.node) {
          nodeNewOpts = nodeNewType;
          nodeNewType = nodeNewType.node;
        }
        const xSizeFix = opts.posSizeFix[0] * LiteGraph.NODE_WIDTH;
        const ySizeFix = opts.posSizeFix[1] * LiteGraph.NODE_SLOT_HEIGHT;
        const nodeX2 = opts.position[0] + opts.posAdd[0] + xSizeFix;
        const nodeY = opts.position[1] + opts.posAdd[1] + ySizeFix;
        const pos = [nodeX2, nodeY];
        const newNode = LiteGraph.createNode(nodeNewType, nodeNewOpts.title, {
          pos
        });
        if (newNode) {
          if (nodeNewOpts) {
            if (nodeNewOpts.properties) {
              for (const i in nodeNewOpts.properties) {
                newNode.addProperty(i, nodeNewOpts.properties[i]);
              }
            }
            if (nodeNewOpts.inputs) {
              newNode.inputs = [];
              for (const i in nodeNewOpts.inputs) {
                newNode.addOutput(
                  nodeNewOpts.inputs[i][0],
                  nodeNewOpts.inputs[i][1]
                );
              }
            }
            if (nodeNewOpts.outputs) {
              newNode.outputs = [];
              for (const i in nodeNewOpts.outputs) {
                newNode.addOutput(
                  nodeNewOpts.outputs[i][0],
                  nodeNewOpts.outputs[i][1]
                );
              }
            }
            if (nodeNewOpts.json) {
              newNode.configure(nodeNewOpts.json);
            }
          }
          if (!this.graph) throw new NullGraphError();
          this.graph.add(newNode);
          const detail = { node: newNode, opts };
          const mayConnectLinks = this.canvas.dispatchEvent(
            new CustomEvent("connect-new-default-node", {
              detail,
              cancelable: true
            })
          );
          if (!mayConnectLinks) return true;
          if (isFrom) {
            if (!opts.nodeFrom)
              throw new TypeError(
                "createDefaultNodeForSlot - nodeFrom was null"
              );
            opts.nodeFrom.connectByType(iSlotConn, newNode, fromSlotType, {
              afterRerouteId
            });
          } else {
            if (!opts.nodeTo)
              throw new TypeError("createDefaultNodeForSlot - nodeTo was null");
            opts.nodeTo.connectByTypeOutput(iSlotConn, newNode, fromSlotType, {
              afterRerouteId
            });
          }
          return true;
        }
        console.error(`failed creating ${nodeNewType}`);
      }
    }
    return false;
  }
  showConnectionMenu(optPass) {
    const opts = Object.assign(
      {
        nodeFrom: null,
        slotFrom: null,
        nodeTo: null,
        slotTo: null,
        e: void 0,
        allow_searchbox: this.allow_searchbox,
        showSearchBox: this.showSearchBox
      },
      optPass || {}
    );
    const dirty = /* @__PURE__ */ __name(() => this.#dirty(), "dirty");
    const that = this;
    const { graph } = this;
    const { afterRerouteId } = opts;
    const isFrom = opts.nodeFrom && opts.slotFrom;
    const isTo = !isFrom && opts.nodeTo && opts.slotTo;
    if (!isFrom && !isTo) {
      console.warn("No data passed to showConnectionMenu");
      return;
    }
    const nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;
    if (!nodeX)
      throw new TypeError("nodeX was null when creating default node for slot.");
    let slotX = isFrom ? opts.slotFrom : opts.slotTo;
    let iSlotConn;
    if (nodeX instanceof SubgraphIONodeBase) {
      if (typeof slotX !== "object" || !slotX) {
        console.warn("Cant get slot information", slotX);
        return;
      }
      const { name: name2 } = slotX;
      iSlotConn = nodeX.slots.findIndex((s) => s.name === name2);
      if (iSlotConn !== -1) {
        slotX = nodeX.slots[iSlotConn];
      }
      if (!slotX) {
        console.warn("Cant get slot information", slotX);
        return;
      }
    } else {
      switch (typeof slotX) {
        case "string":
          iSlotConn = isFrom ? nodeX.findOutputSlot(slotX, false) : nodeX.findInputSlot(slotX, false);
          slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
          break;
        case "object":
          if (slotX === null) {
            console.warn("Cant get slot information", slotX);
            return;
          }
          iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);
          break;
        case "number":
          iSlotConn = slotX;
          slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
          break;
        default:
          console.warn("Cant get slot information", slotX);
          return;
      }
    }
    const options = ["Add Node", "Add Reroute", null];
    if (opts.allow_searchbox) {
      options.push("Search", null);
    }
    const fromSlotType = slotX.type == LiteGraph.EVENT ? "_event_" : slotX.type;
    const slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;
    if (slotTypesDefault?.[fromSlotType]) {
      if (typeof slotTypesDefault[fromSlotType] == "object") {
        for (const typeX in slotTypesDefault[fromSlotType]) {
          options.push(slotTypesDefault[fromSlotType][typeX]);
        }
      } else {
        options.push(slotTypesDefault[fromSlotType]);
      }
    }
    const menu2 = new LiteGraph.ContextMenu(options, {
      event: opts.e,
      extra: slotX,
      title: (slotX && slotX.name != "" ? slotX.name + (fromSlotType ? " | " : "") : "") + (slotX && fromSlotType ? fromSlotType : ""),
      callback: inner_clicked
    });
    return menu2;
    function inner_clicked(v2, options2, e2) {
      switch (v2) {
        case "Add Node":
          LGraphCanvas.onMenuAdd(null, null, e2, menu2, function(node2) {
            if (!node2) return;
            if (isFrom) {
              if (!opts.nodeFrom)
                throw new TypeError(
                  "Cannot add node to SubgraphInputNode: nodeFrom was null"
                );
              const slot = opts.nodeFrom.connectByType(
                iSlotConn,
                node2,
                fromSlotType,
                { afterRerouteId }
              );
              if (!slot) console.warn("Failed to make new connection.");
            } else {
              if (!opts.nodeTo)
                throw new TypeError(
                  "Cannot add node to SubgraphInputNode: nodeTo was null"
                );
              opts.nodeTo.connectByTypeOutput(iSlotConn, node2, fromSlotType, {
                afterRerouteId
              });
            }
          });
          break;
        case "Add Reroute": {
          const node2 = isFrom ? opts.nodeFrom : opts.nodeTo;
          const slot = options2.extra;
          if (!graph) throw new NullGraphError();
          if (!node2) throw new TypeError("Cannot add reroute: node was null");
          if (!slot) throw new TypeError("Cannot add reroute: slot was null");
          if (!opts.e)
            throw new TypeError(
              "Cannot add reroute: CanvasPointerEvent was null"
            );
          if (node2 instanceof SubgraphIONodeBase) {
            throw new TypeError(
              "Cannot add floating reroute to Subgraph IO Nodes"
            );
          } else {
            const reroute = node2.connectFloatingReroute(
              [opts.e.canvasX, opts.e.canvasY],
              slot,
              afterRerouteId
            );
            if (!reroute) throw new Error("Failed to create reroute");
          }
          dirty();
          break;
        }
        case "Search":
          if (isFrom) {
            opts.showSearchBox(e2, {
              // @ts-expect-error - Subgraph types
              node_from: opts.nodeFrom,
              // @ts-expect-error - Subgraph types
              slot_from: slotX,
              type_filter_in: fromSlotType
            });
          } else {
            opts.showSearchBox(e2, {
              // @ts-expect-error - Subgraph types
              node_to: opts.nodeTo,
              // @ts-expect-error - Subgraph types
              slot_from: slotX,
              type_filter_out: fromSlotType
            });
          }
          break;
        default: {
          const customProps = {
            position: [opts.e?.canvasX ?? 0, opts.e?.canvasY ?? 0],
            nodeType: v2,
            afterRerouteId
          };
          const options3 = Object.assign(opts, customProps);
          if (!that.createDefaultNodeForSlot(options3)) break;
        }
      }
    }
    __name(inner_clicked, "inner_clicked");
  }
  // refactor: there are different dialogs, some uses createDialog some dont
  prompt(title, value, callback, event, multiline) {
    const that = this;
    title = title || "";
    const customProperties = {
      is_modified: false,
      className: "graphdialog rounded",
      innerHTML: multiline ? "<span class='name'></span> <textarea autofocus class='value'></textarea><button class='rounded'>OK</button>" : "<span class='name'></span> <input autofocus type='text' class='value'/><button class='rounded'>OK</button>",
      close() {
        that.prompt_box = null;
        if (dialog.parentNode) {
          dialog.remove();
        }
      }
    };
    const div = document.createElement("div");
    const dialog = Object.assign(div, customProperties);
    const graphcanvas = LGraphCanvas.active_canvas;
    const { canvas: canvas2 } = graphcanvas;
    if (!canvas2.parentNode)
      throw new TypeError(
        "canvas element parentNode was null when opening a prompt."
      );
    canvas2.parentNode.append(dialog);
    if (this.ds.scale > 1) dialog.style.transform = `scale(${this.ds.scale})`;
    let dialogCloseTimer;
    let prevent_timeout = 0;
    LiteGraph.pointerListenerAdd(dialog, "leave", function() {
      if (prevent_timeout) return;
      if (LiteGraph.dialog_close_on_mouse_leave) {
        if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) {
          dialogCloseTimer = setTimeout(
            dialog.close,
            LiteGraph.dialog_close_on_mouse_leave_delay
          );
        }
      }
    });
    LiteGraph.pointerListenerAdd(dialog, "enter", function() {
      if (LiteGraph.dialog_close_on_mouse_leave && dialogCloseTimer)
        clearTimeout(dialogCloseTimer);
    });
    const selInDia = dialog.querySelectorAll("select");
    if (selInDia) {
      for (const selIn of selInDia) {
        selIn.addEventListener("click", function() {
          prevent_timeout++;
        });
        selIn.addEventListener("blur", function() {
          prevent_timeout = 0;
        });
        selIn.addEventListener("change", function() {
          prevent_timeout = -1;
        });
      }
    }
    this.prompt_box?.close();
    this.prompt_box = dialog;
    const name_element = dialog.querySelector(".name");
    if (!name_element) throw new TypeError("name_element was null");
    name_element.textContent = title;
    const value_element = dialog.querySelector(".value");
    if (!value_element) throw new TypeError("value_element was null");
    value_element.value = value;
    value_element.select();
    const input = value_element;
    input.addEventListener("keydown", function(e2) {
      dialog.is_modified = true;
      if (e2.key == "Escape") {
        dialog.close();
      } else if (e2.key == "Enter" && e2.target.localName != "textarea") {
        if (callback) {
          callback(this.value);
        }
        dialog.close();
      } else {
        return;
      }
      e2.preventDefault();
      e2.stopPropagation();
    });
    const button = dialog.querySelector("button");
    if (!button) throw new TypeError("button was null when opening prompt");
    button.addEventListener("click", function() {
      callback?.(input.value);
      that.setDirty(true);
      dialog.close();
    });
    const rect = canvas2.getBoundingClientRect();
    let offsetx = -20;
    let offsety = -20;
    if (rect) {
      offsetx -= rect.left;
      offsety -= rect.top;
    }
    if (event) {
      dialog.style.left = `${event.clientX + offsetx}px`;
      dialog.style.top = `${event.clientY + offsety}px`;
    } else {
      dialog.style.left = `${canvas2.width * 0.5 + offsetx}px`;
      dialog.style.top = `${canvas2.height * 0.5 + offsety}px`;
    }
    setTimeout(function() {
      input.focus();
      const clickTime = Date.now();
      function handleOutsideClick(e2) {
        if (e2.target === canvas2 && Date.now() - clickTime > 256) {
          dialog.close();
          canvas2.parentElement?.removeEventListener("click", handleOutsideClick);
          canvas2.parentElement?.removeEventListener(
            "touchend",
            handleOutsideClick
          );
        }
      }
      __name(handleOutsideClick, "handleOutsideClick");
      canvas2.parentElement?.addEventListener("click", handleOutsideClick);
      canvas2.parentElement?.addEventListener("touchend", handleOutsideClick);
    }, 10);
    return dialog;
  }
  showSearchBox(event, searchOptions) {
    const options = {
      slot_from: null,
      node_from: null,
      node_to: null,
      // TODO check for registered_slot_[in/out]_types not empty
      // this will be checked for functionality enabled : filter on slot type, in and out
      do_type_filter: LiteGraph.search_filter_enabled,
      // these are default: pass to set initially set values
      // @ts-expect-error Property missing from interface definition
      type_filter_in: false,
      type_filter_out: false,
      show_general_if_none_on_typefilter: true,
      show_general_after_typefiltered: true,
      hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave,
      show_all_if_empty: true,
      show_all_on_open: LiteGraph.search_show_all_on_open
    };
    Object.assign(options, searchOptions);
    const that = this;
    const graphcanvas = LGraphCanvas.active_canvas;
    const { canvas: canvas2 } = graphcanvas;
    const root_document = canvas2.ownerDocument || document;
    const div = document.createElement("div");
    const dialog = Object.assign(div, {
      close() {
        that.search_box = void 0;
        this.blur();
        canvas2.focus();
        root_document.body.style.overflow = "";
        setTimeout(() => canvas2.focus(), 20);
        dialog.remove();
      }
    });
    dialog.className = "litegraph litesearchbox graphdialog rounded";
    dialog.innerHTML = "<span class='name'>Search</span> <input autofocus type='text' class='value rounded'/>";
    if (options.do_type_filter) {
      dialog.innerHTML += "<select class='slot_in_type_filter'><option value=''></option></select>";
      dialog.innerHTML += "<select class='slot_out_type_filter'><option value=''></option></select>";
    }
    const helper = document.createElement("div");
    helper.className = "helper";
    dialog.append(helper);
    if (root_document.fullscreenElement) {
      root_document.fullscreenElement.append(dialog);
    } else {
      root_document.body.append(dialog);
      root_document.body.style.overflow = "hidden";
    }
    let selIn;
    let selOut;
    if (options.do_type_filter) {
      selIn = dialog.querySelector(".slot_in_type_filter");
      selOut = dialog.querySelector(".slot_out_type_filter");
    }
    if (this.ds.scale > 1) {
      dialog.style.transform = `scale(${this.ds.scale})`;
    }
    if (options.hide_on_mouse_leave) {
      let prevent_timeout = false;
      let timeout_close = null;
      LiteGraph.pointerListenerAdd(dialog, "enter", function() {
        if (timeout_close) {
          clearTimeout(timeout_close);
          timeout_close = null;
        }
      });
      dialog.addEventListener("pointerleave", function() {
        if (prevent_timeout) return;
        const hideDelay = options.hide_on_mouse_leave;
        const delay = typeof hideDelay === "number" ? hideDelay : 500;
        timeout_close = setTimeout(dialog.close, delay);
      });
      if (options.do_type_filter) {
        if (!selIn)
          throw new TypeError("selIn was null when showing search box");
        if (!selOut)
          throw new TypeError("selOut was null when showing search box");
        selIn.addEventListener("click", function() {
          prevent_timeout++;
        });
        selIn.addEventListener("blur", function() {
          prevent_timeout = 0;
        });
        selIn.addEventListener("change", function() {
          prevent_timeout = -1;
        });
        selOut.addEventListener("click", function() {
          prevent_timeout++;
        });
        selOut.addEventListener("blur", function() {
          prevent_timeout = 0;
        });
        selOut.addEventListener("change", function() {
          prevent_timeout = -1;
        });
      }
    }
    that.search_box?.close();
    that.search_box = dialog;
    let first = null;
    let timeout = null;
    let selected = null;
    const maybeInput = dialog.querySelector("input");
    if (!maybeInput) throw new TypeError("Could not create search input box.");
    const input = maybeInput;
    if (input) {
      input.addEventListener("blur", function() {
        this.focus();
      });
      input.addEventListener("keydown", function(e2) {
        if (e2.key == "ArrowUp") {
          changeSelection(false);
        } else if (e2.key == "ArrowDown") {
          changeSelection(true);
        } else if (e2.key == "Escape") {
          dialog.close();
        } else if (e2.key == "Enter") {
          if (selected instanceof HTMLElement) {
            select(unescape(String(selected.dataset["type"])));
          } else if (first) {
            select(first);
          } else {
            dialog.close();
          }
        } else {
          if (timeout) {
            clearInterval(timeout);
          }
          timeout = setTimeout(refreshHelper, 10);
          return;
        }
        e2.preventDefault();
        e2.stopPropagation();
        e2.stopImmediatePropagation();
        return true;
      });
    }
    if (options.do_type_filter) {
      if (selIn) {
        const aSlots = LiteGraph.slot_types_in;
        const nSlots = aSlots.length;
        if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION) {
          options.type_filter_in = "_event_";
        }
        for (let iK = 0; iK < nSlots; iK++) {
          const opt = document.createElement("option");
          opt.value = aSlots[iK];
          opt.innerHTML = aSlots[iK];
          selIn.append(opt);
          if (
            // @ts-expect-error Property missing from interface definition
            options.type_filter_in !== false && String(options.type_filter_in).toLowerCase() == String(aSlots[iK]).toLowerCase()
          ) {
            opt.selected = true;
          }
        }
        selIn.addEventListener("change", function() {
          refreshHelper();
        });
      }
      if (selOut) {
        const aSlots = LiteGraph.slot_types_out;
        if (options.type_filter_out == LiteGraph.EVENT || options.type_filter_out == LiteGraph.ACTION) {
          options.type_filter_out = "_event_";
        }
        for (const aSlot of aSlots) {
          const opt = document.createElement("option");
          opt.value = aSlot;
          opt.innerHTML = aSlot;
          selOut.append(opt);
          if (options.type_filter_out !== false && String(options.type_filter_out).toLowerCase() == String(aSlot).toLowerCase()) {
            opt.selected = true;
          }
        }
        selOut.addEventListener("change", function() {
          refreshHelper();
        });
      }
    }
    const rect = canvas2.getBoundingClientRect();
    const safeEvent = event ?? new MouseEvent("click", {
      clientX: rect.left + rect.width * 0.5,
      clientY: rect.top + rect.height * 0.5,
      // @ts-expect-error layerY is a nonstandard property
      layerY: rect.top + rect.height * 0.5
    });
    const left = safeEvent.clientX - 80;
    const top = safeEvent.clientY - 20;
    dialog.style.left = `${left}px`;
    dialog.style.top = `${top}px`;
    if (safeEvent.layerY > rect.height - 200) {
      helper.style.maxHeight = `${rect.height - safeEvent.layerY - 20}px`;
    }
    requestAnimationFrame(function() {
      input.focus();
    });
    if (options.show_all_on_open) refreshHelper();
    function select(name2) {
      if (name2) {
        if (that.onSearchBoxSelection) {
          that.onSearchBoxSelection(name2, safeEvent, graphcanvas);
        } else {
          if (!graphcanvas.graph) throw new NullGraphError();
          graphcanvas.graph.beforeChange();
          const node2 = LiteGraph.createNode(name2);
          if (node2) {
            node2.pos = graphcanvas.convertEventToCanvasOffset(safeEvent);
            graphcanvas.graph.add(node2, false);
          }
          if (options.node_from) {
            let iS = false;
            switch (typeof options.slot_from) {
              case "string":
                iS = options.node_from.findOutputSlot(options.slot_from);
                break;
              case "object":
                if (options.slot_from == null)
                  throw new TypeError(
                    "options.slot_from was null when showing search box"
                  );
                iS = options.slot_from.name ? options.node_from.findOutputSlot(options.slot_from.name) : -1;
                if (iS == -1 && options.slot_from.slot_index !== void 0)
                  iS = options.slot_from.slot_index;
                break;
              case "number":
                iS = options.slot_from;
                break;
              default:
                iS = 0;
            }
            if (options.node_from.outputs[iS] !== void 0) {
              if (iS !== false && iS > -1) {
                if (node2 == null)
                  throw new TypeError(
                    "options.slot_from was null when showing search box"
                  );
                options.node_from.connectByType(
                  iS,
                  node2,
                  options.node_from.outputs[iS].type
                );
              }
            }
          }
          if (options.node_to) {
            let iS = false;
            switch (typeof options.slot_from) {
              case "string":
                iS = options.node_to.findInputSlot(options.slot_from);
                break;
              case "object":
                if (options.slot_from == null)
                  throw new TypeError(
                    "options.slot_from was null when showing search box"
                  );
                iS = options.slot_from.name ? options.node_to.findInputSlot(options.slot_from.name) : -1;
                if (iS == -1 && options.slot_from.slot_index !== void 0)
                  iS = options.slot_from.slot_index;
                break;
              case "number":
                iS = options.slot_from;
                break;
              default:
                iS = 0;
            }
            if (options.node_to.inputs[iS] !== void 0) {
              if (iS !== false && iS > -1) {
                if (node2 == null)
                  throw new TypeError(
                    "options.slot_from was null when showing search box"
                  );
                options.node_to.connectByTypeOutput(
                  iS,
                  node2,
                  options.node_to.inputs[iS].type
                );
              }
            }
          }
          graphcanvas.graph.afterChange();
        }
      }
      dialog.close();
    }
    __name(select, "select");
    function changeSelection(forward) {
      const prev = selected;
      if (!selected) {
        selected = forward ? helper.childNodes[0] : helper.childNodes[helper.childNodes.length];
      } else if (selected instanceof Element) {
        selected.classList.remove("selected");
        selected = forward ? selected.nextSibling : selected.previousSibling;
        selected ||= prev;
      }
      if (selected instanceof Element) {
        selected.classList.add("selected");
        selected.scrollIntoView({ block: "end", behavior: "smooth" });
      }
    }
    __name(changeSelection, "changeSelection");
    function refreshHelper() {
      timeout = null;
      let str = input.value;
      first = null;
      helper.innerHTML = "";
      if (!str && !options.show_all_if_empty) return;
      if (that.onSearchBox) {
        const list = that.onSearchBox(helper, str, graphcanvas);
        if (list) {
          for (const item of list) {
            addResult(item);
          }
        }
      } else {
        let inner_test_filter = /* @__PURE__ */ __name(function(type, optsIn) {
          optsIn = optsIn || {};
          const optsDef = {
            skipFilter: false,
            inTypeOverride: false,
            outTypeOverride: false
          };
          const opts = Object.assign(optsDef, optsIn);
          const ctor = LiteGraph.registered_node_types[type];
          if (filter2 && ctor.filter != filter2) return false;
          if ((!options.show_all_if_empty || str) && !type.toLowerCase().includes(str) && (!ctor.title || !ctor.title.toLowerCase().includes(str))) {
            return false;
          }
          if (options.do_type_filter && !opts.skipFilter) {
            const sType = type;
            let sV = opts.inTypeOverride !== false ? opts.inTypeOverride : sIn.value;
            if (sIn && sV && LiteGraph.registered_slot_in_types[sV]?.nodes) {
              const doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType);
              if (doesInc === false) return false;
            }
            sV = sOut.value;
            if (opts.outTypeOverride !== false) sV = opts.outTypeOverride;
            if (sOut && sV && LiteGraph.registered_slot_out_types[sV]?.nodes) {
              const doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType);
              if (doesInc === false) return false;
            }
          }
          return true;
        }, "inner_test_filter");
        let c2 = 0;
        str = str.toLowerCase();
        if (!graphcanvas.graph) throw new NullGraphError();
        const filter2 = graphcanvas.filter || graphcanvas.graph.filter;
        let sIn = false;
        let sOut = false;
        if (options.do_type_filter && that.search_box) {
          sIn = that.search_box.querySelector(".slot_in_type_filter");
          sOut = that.search_box.querySelector(".slot_out_type_filter");
        }
        const keys = Object.keys(LiteGraph.registered_node_types);
        const filtered = keys.filter((x2) => inner_test_filter(x2));
        for (const item of filtered) {
          addResult(item);
          if (LGraphCanvas.search_limit !== -1 && c2++ > LGraphCanvas.search_limit)
            break;
        }
        if (options.show_general_after_typefiltered && (sIn.value || sOut.value)) {
          filtered_extra = [];
          for (const i in LiteGraph.registered_node_types) {
            if (inner_test_filter(i, {
              inTypeOverride: sIn && sIn.value ? "*" : false,
              outTypeOverride: sOut && sOut.value ? "*" : false
            })) {
              filtered_extra.push(i);
            }
          }
          for (const extraItem of filtered_extra) {
            addResult(extraItem, "generic_type");
            if (LGraphCanvas.search_limit !== -1 && c2++ > LGraphCanvas.search_limit)
              break;
          }
        }
        if ((sIn.value || sOut.value) && helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) {
          filtered_extra = [];
          for (const i in LiteGraph.registered_node_types) {
            if (inner_test_filter(i, { skipFilter: true }))
              filtered_extra.push(i);
          }
          for (const extraItem of filtered_extra) {
            addResult(extraItem, "not_in_filter");
            if (LGraphCanvas.search_limit !== -1 && c2++ > LGraphCanvas.search_limit)
              break;
          }
        }
      }
      function addResult(type, className) {
        const help2 = document.createElement("div");
        first ||= type;
        const nodeType = LiteGraph.registered_node_types[type];
        if (nodeType?.title) {
          help2.textContent = nodeType?.title;
          const typeEl = document.createElement("span");
          typeEl.className = "litegraph lite-search-item-type";
          typeEl.textContent = type;
          help2.append(typeEl);
        } else {
          help2.textContent = type;
        }
        help2.dataset["type"] = escape(type);
        help2.className = "litegraph lite-search-item";
        if (className) {
          help2.className += ` ${className}`;
        }
        help2.addEventListener("click", function() {
          select(unescape(String(this.dataset["type"])));
        });
        helper.append(help2);
      }
      __name(addResult, "addResult");
    }
    __name(refreshHelper, "refreshHelper");
    return dialog;
  }
  showEditPropertyValue(node2, property, options) {
    if (!node2 || node2.properties[property] === void 0) return;
    options = options || {};
    const info = node2.getPropertyInfo(property);
    const { type } = info;
    let input_html = "";
    if (type == "string" || type == "number" || type == "array" || type == "object") {
      input_html = "<input autofocus type='text' class='value'/>";
    } else if ((type == "enum" || type == "combo") && info.values) {
      input_html = "<select autofocus type='text' class='value'>";
      for (const i in info.values) {
        const v2 = Array.isArray(info.values) ? info.values[i] : i;
        const selected = v2 == node2.properties[property] ? "selected" : "";
        input_html += `<option value='${v2}' ${selected}>${info.values[i]}</option>`;
      }
      input_html += "</select>";
    } else if (type == "boolean" || type == "toggle") {
      const checked = node2.properties[property] ? "checked" : "";
      input_html = `<input autofocus type='checkbox' class='value' ${checked}/>`;
    } else {
      console.warn(`unknown type: ${type}`);
      return;
    }
    const dialog = this.createDialog(
      `<span class='name'>${info.label || property}</span>${input_html}<button>OK</button>`,
      options
    );
    let input;
    if ((type == "enum" || type == "combo") && info.values) {
      input = dialog.querySelector("select");
      input?.addEventListener("change", function(e2) {
        dialog.modified();
        setValue(e2.target?.value);
      });
    } else if (type == "boolean" || type == "toggle") {
      input = dialog.querySelector("input");
      input?.addEventListener("click", function() {
        dialog.modified();
        setValue(!!input.checked);
      });
    } else {
      input = dialog.querySelector("input");
      if (input) {
        input.addEventListener("blur", function() {
          this.focus();
        });
        let v2 = node2.properties[property] !== void 0 ? node2.properties[property] : "";
        if (type !== "string") {
          v2 = JSON.stringify(v2);
        }
        input.value = v2;
        input.addEventListener("keydown", function(e2) {
          if (e2.key == "Escape") {
            dialog.close();
          } else if (e2.key == "Enter") {
            inner();
          } else {
            dialog.modified();
            return;
          }
          e2.preventDefault();
          e2.stopPropagation();
        });
      }
    }
    input?.focus();
    const button = dialog.querySelector("button");
    if (!button)
      throw new TypeError("Show edit property value button was null.");
    button.addEventListener("click", inner);
    function inner() {
      setValue(input?.value);
    }
    __name(inner, "inner");
    const dirty = /* @__PURE__ */ __name(() => this.#dirty(), "dirty");
    function setValue(value) {
      if (info?.values && typeof info.values === "object" && info.values[value] != void 0) {
        value = info.values[value];
      }
      if (typeof node2.properties[property] == "number") {
        value = Number(value);
      }
      if (type == "array" || type == "object") {
        value = JSON.parse(value);
      }
      node2.properties[property] = value;
      if (node2.graph) {
        node2.graph._version++;
      }
      node2.onPropertyChanged?.(property, value);
      options.onclose?.();
      dialog.close();
      dirty();
    }
    __name(setValue, "setValue");
    return dialog;
  }
  // TODO refactor, there are different dialog, some uses createDialog, some dont
  createDialog(html, options) {
    const def_options = {
      checkForInput: false,
      closeOnLeave: true,
      closeOnLeave_checkModified: true
    };
    options = Object.assign(def_options, options || {});
    const customProperties = {
      className: "graphdialog",
      innerHTML: html,
      is_modified: false,
      modified() {
        this.is_modified = true;
      },
      close() {
        this.remove();
      }
    };
    const div = document.createElement("div");
    const dialog = Object.assign(div, customProperties);
    const rect = this.canvas.getBoundingClientRect();
    let offsetx = -20;
    let offsety = -20;
    if (rect) {
      offsetx -= rect.left;
      offsety -= rect.top;
    }
    if (options.position) {
      offsetx += options.position[0];
      offsety += options.position[1];
    } else if (options.event) {
      offsetx += options.event.clientX;
      offsety += options.event.clientY;
    } else {
      offsetx += this.canvas.width * 0.5;
      offsety += this.canvas.height * 0.5;
    }
    dialog.style.left = `${offsetx}px`;
    dialog.style.top = `${offsety}px`;
    if (!this.canvas.parentNode)
      throw new TypeError("Canvas parent element was null.");
    this.canvas.parentNode.append(dialog);
    if (options.checkForInput) {
      const aI = dialog.querySelectorAll("input");
      if (aI) {
        for (const iX of aI) {
          iX.addEventListener("keydown", function(e2) {
            dialog.modified();
            if (e2.key == "Escape") {
              dialog.close();
            } else if (e2.key != "Enter") {
              return;
            }
            e2.preventDefault();
            e2.stopPropagation();
          });
          iX.focus();
        }
      }
    }
    let dialogCloseTimer;
    let prevent_timeout = 0;
    dialog.addEventListener("mouseleave", function() {
      if (prevent_timeout) return;
      if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) {
        dialogCloseTimer = setTimeout(
          dialog.close,
          LiteGraph.dialog_close_on_mouse_leave_delay
        );
      }
    });
    dialog.addEventListener("mouseenter", function() {
      if (options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) {
        if (dialogCloseTimer) clearTimeout(dialogCloseTimer);
      }
    });
    const selInDia = dialog.querySelectorAll("select");
    if (selInDia) {
      for (const selIn of selInDia) {
        selIn.addEventListener("click", function() {
          prevent_timeout++;
        });
        selIn.addEventListener("blur", function() {
          prevent_timeout = 0;
        });
        selIn.addEventListener("change", function() {
          prevent_timeout = -1;
        });
      }
    }
    return dialog;
  }
  createPanel(title, options) {
    options = options || {};
    const ref_window = options.window || window;
    const root = document.createElement("div");
    root.className = "litegraph dialog";
    root.innerHTML = "<div class='dialog-header'><span class='dialog-title'></span></div><div class='dialog-content'></div><div style='display:none;' class='dialog-alt-content'></div><div class='dialog-footer'></div>";
    root.header = root.querySelector(".dialog-header");
    if (options.width)
      root.style.width = options.width + (typeof options.width === "number" ? "px" : "");
    if (options.height)
      root.style.height = options.height + (typeof options.height === "number" ? "px" : "");
    if (options.closable) {
      const close = document.createElement("span");
      close.innerHTML = "&#10005;";
      close.classList.add("close");
      close.addEventListener("click", function() {
        root.close();
      });
      root.header.append(close);
    }
    root.title_element = root.querySelector(".dialog-title");
    root.title_element.textContent = title;
    root.content = root.querySelector(".dialog-content");
    root.alt_content = root.querySelector(".dialog-alt-content");
    root.footer = root.querySelector(".dialog-footer");
    root.footer.style.marginTop = "-96px";
    root.close = function() {
      if (typeof root.onClose == "function") root.onClose();
      root.remove();
      this.remove();
    };
    root.toggleAltContent = function(force) {
      let vTo;
      let vAlt;
      if (force !== void 0) {
        vTo = force ? "block" : "none";
        vAlt = force ? "none" : "block";
      } else {
        vTo = root.alt_content.style.display != "block" ? "block" : "none";
        vAlt = root.alt_content.style.display != "block" ? "none" : "block";
      }
      root.alt_content.style.display = vTo;
      root.content.style.display = vAlt;
    };
    root.toggleFooterVisibility = function(force) {
      let vTo;
      if (force !== void 0) {
        vTo = force ? "block" : "none";
      } else {
        vTo = root.footer.style.display != "block" ? "block" : "none";
      }
      root.footer.style.display = vTo;
    };
    root.clear = function() {
      this.content.innerHTML = "";
    };
    root.addHTML = function(code, classname, on_footer) {
      const elem = document.createElement("div");
      if (classname) elem.className = classname;
      elem.innerHTML = code;
      if (on_footer) root.footer.append(elem);
      else root.content.append(elem);
      return elem;
    };
    root.addButton = function(name2, callback, options2) {
      const elem = document.createElement("button");
      elem.textContent = name2;
      elem.options = options2;
      elem.classList.add("btn");
      elem.addEventListener("click", callback);
      root.footer.append(elem);
      return elem;
    };
    root.addSeparator = function() {
      const elem = document.createElement("div");
      elem.className = "separator";
      root.content.append(elem);
    };
    root.addWidget = function(type, name2, value, options2, callback) {
      options2 = options2 || {};
      let str_value = String(value);
      type = type.toLowerCase();
      if (type == "number" && typeof value === "number")
        str_value = value.toFixed(3);
      const elem = document.createElement("div");
      elem.className = "property";
      elem.innerHTML = "<span class='property_name'></span><span class='property_value'></span>";
      const nameSpan = elem.querySelector(".property_name");
      if (!nameSpan) throw new TypeError("Property name element was null.");
      nameSpan.textContent = options2.label || name2;
      const value_element = elem.querySelector(".property_value");
      if (!value_element) throw new TypeError("Property name element was null.");
      value_element.textContent = str_value;
      elem.dataset["property"] = name2;
      elem.dataset["type"] = options2.type || type;
      elem.options = options2;
      elem.value = value;
      if (type == "code") {
        elem.addEventListener("click", function() {
          root.inner_showCodePad(this.dataset["property"]);
        });
      } else if (type == "boolean") {
        elem.classList.add("boolean");
        if (value) elem.classList.add("bool-on");
        elem.addEventListener("click", () => {
          const propname = elem.dataset["property"];
          elem.value = !elem.value;
          elem.classList.toggle("bool-on");
          if (!value_element)
            throw new TypeError("Property name element was null.");
          value_element.textContent = elem.value ? "true" : "false";
          innerChange(propname, elem.value);
        });
      } else if (type == "string" || type == "number") {
        if (!value_element)
          throw new TypeError("Property name element was null.");
        value_element.setAttribute("contenteditable", "true");
        value_element.addEventListener("keydown", function(e2) {
          if (e2.code == "Enter" && (type != "string" || !e2.shiftKey)) {
            e2.preventDefault();
            this.blur();
          }
        });
        value_element.addEventListener("blur", function() {
          let v2 = this.textContent;
          const propname = this.parentElement?.dataset["property"];
          const proptype = this.parentElement?.dataset["type"];
          if (proptype == "number") v2 = Number(v2);
          innerChange(propname, v2);
        });
      } else if (type == "enum" || type == "combo") {
        const str_value2 = LGraphCanvas.getPropertyPrintableValue(
          value,
          options2.values
        );
        if (!value_element)
          throw new TypeError("Property name element was null.");
        value_element.textContent = str_value2 ?? "";
        value_element.addEventListener("click", function(event) {
          const values = options2.values || [];
          const propname = this.parentElement?.dataset["property"];
          const inner_clicked = /* @__PURE__ */ __name((v2) => {
            this.textContent = v2;
            innerChange(propname, v2);
            return false;
          }, "inner_clicked");
          new LiteGraph.ContextMenu(
            values,
            {
              event,
              className: "dark",
              callback: inner_clicked
            },
            // @ts-expect-error ref_window parameter unused in ContextMenu constructor
            ref_window
          );
        });
      }
      root.content.append(elem);
      function innerChange(name22, value2) {
        options2.callback?.(name22, value2, options2);
        callback?.(name22, value2, options2);
      }
      __name(innerChange, "innerChange");
      return elem;
    };
    if (typeof root.onOpen == "function") root.onOpen();
    return root;
  }
  closePanels() {
    document.querySelector("#node-panel")?.close?.();
    document.querySelector("#option-panel")?.close?.();
  }
  showShowNodePanel(node2) {
    this.SELECTED_NODE = node2;
    this.closePanels();
    const ref_window = this.getCanvasWindow();
    const panel = this.createPanel(node2.title || "", {
      closable: true,
      window: ref_window,
      onOpen: /* @__PURE__ */ __name(() => {
        this.NODEPANEL_IS_OPEN = true;
      }, "onOpen"),
      onClose: /* @__PURE__ */ __name(() => {
        this.NODEPANEL_IS_OPEN = false;
        this.node_panel = null;
      }, "onClose")
    });
    this.node_panel = panel;
    panel.id = "node-panel";
    panel.node = node2;
    panel.classList.add("settings");
    panel.style.position = "absolute";
    panel.style.top = "96px";
    panel.style.left = "65px";
    const inner_refresh = /* @__PURE__ */ __name(() => {
      panel.content.innerHTML = "";
      panel.addHTML(
        // @ts-expect-error - desc property
        `<span class='node_type'>${node2.type}</span><span class='node_desc'>${node2.constructor.desc || ""}</span><span class='separator'></span>`
      );
      panel.addHTML("<h3>Properties</h3>");
      const fUpdate = /* @__PURE__ */ __name((name2, value) => {
        if (!this.graph) throw new NullGraphError();
        this.graph.beforeChange(node2);
        switch (name2) {
          case "Title":
            if (typeof value !== "string")
              throw new TypeError(
                "Attempting to set title to non-string value."
              );
            node2.title = value;
            break;
          case "Mode": {
            if (typeof value !== "string")
              throw new TypeError("Attempting to set mode to non-string value.");
            const kV = Object.values(LiteGraph.NODE_MODES).indexOf(value);
            if (kV !== -1 && LiteGraph.NODE_MODES[kV]) {
              node2.changeMode(kV);
            } else {
              console.warn(`unexpected mode: ${value}`);
            }
            break;
          }
          case "Color":
            if (typeof value !== "string")
              throw new TypeError(
                "Attempting to set colour to non-string value."
              );
            if (LGraphCanvas.node_colors[value]) {
              node2.color = LGraphCanvas.node_colors[value].color;
              node2.bgcolor = LGraphCanvas.node_colors[value].bgcolor;
            } else {
              console.warn(`unexpected color: ${value}`);
            }
            break;
          default:
            node2.setProperty(name2, value);
            break;
        }
        this.graph.afterChange();
        this.dirty_canvas = true;
      }, "fUpdate");
      panel.addWidget("string", "Title", node2.title, {}, fUpdate);
      const mode = node2.mode == null ? void 0 : LiteGraph.NODE_MODES[node2.mode];
      panel.addWidget(
        "combo",
        "Mode",
        mode,
        { values: LiteGraph.NODE_MODES },
        fUpdate
      );
      const nodeCol = node2.color !== void 0 ? Object.keys(LGraphCanvas.node_colors).filter(function(nK) {
        return LGraphCanvas.node_colors[nK].color == node2.color;
      }) : "";
      panel.addWidget(
        "combo",
        "Color",
        nodeCol,
        { values: Object.keys(LGraphCanvas.node_colors) },
        fUpdate
      );
      for (const pName in node2.properties) {
        const value = node2.properties[pName];
        const info = node2.getPropertyInfo(pName);
        if (node2.onAddPropertyToPanel?.(pName, panel)) continue;
        panel.addWidget(info.widget || info.type, pName, value, info, fUpdate);
      }
      panel.addSeparator();
      node2.onShowCustomPanelInfo?.(panel);
      panel.footer.innerHTML = "";
      panel.addButton("Delete", function() {
        if (node2.block_delete) return;
        if (!node2.graph) throw new NullGraphError();
        node2.graph.remove(node2);
        panel.close();
      }).classList.add("delete");
    }, "inner_refresh");
    panel.inner_showCodePad = function(propname) {
      panel.classList.remove("settings");
      panel.classList.add("centered");
      panel.alt_content.innerHTML = "<textarea class='code'></textarea>";
      const textarea = panel.alt_content.querySelector("textarea");
      const fDoneWith = /* @__PURE__ */ __name(function() {
        panel.toggleAltContent(false);
        panel.toggleFooterVisibility(true);
        textarea.remove();
        panel.classList.add("settings");
        panel.classList.remove("centered");
        inner_refresh();
      }, "fDoneWith");
      textarea.value = String(node2.properties[propname]);
      textarea.addEventListener("keydown", function(e2) {
        if (e2.code == "Enter" && e2.ctrlKey) {
          node2.setProperty(propname, textarea.value);
          fDoneWith();
        }
      });
      panel.toggleAltContent(true);
      panel.toggleFooterVisibility(false);
      textarea.style.height = "calc(100% - 40px)";
      const assign = panel.addButton("Assign", function() {
        node2.setProperty(propname, textarea.value);
        fDoneWith();
      });
      panel.alt_content.append(assign);
      const button = panel.addButton("Close", fDoneWith);
      button.style.float = "right";
      panel.alt_content.append(button);
    };
    inner_refresh();
    if (!this.canvas.parentNode)
      throw new TypeError("showNodePanel - this.canvas.parentNode was null");
    this.canvas.parentNode.append(panel);
  }
  checkPanels() {
    if (!this.canvas) return;
    if (!this.canvas.parentNode)
      throw new TypeError("checkPanels - this.canvas.parentNode was null");
    const panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog");
    for (const panel of panels) {
      if (!panel.node) continue;
      if (!panel.node.graph || panel.graph != this.graph) panel.close();
    }
  }
  getCanvasMenuOptions() {
    let options;
    if (this.getMenuOptions) {
      options = this.getMenuOptions();
    } else {
      options = [
        {
          content: "Add Node",
          has_submenu: true,
          callback: LGraphCanvas.onMenuAdd
        },
        { content: "Add Group", callback: LGraphCanvas.onGroupAdd },
        {
          content: "Paste",
          callback: /* @__PURE__ */ __name(() => {
            this.pasteFromClipboard();
          }, "callback")
        }
        // { content: "Arrange", callback: that.graph.arrange },
        // {content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll }
      ];
      if (Object.keys(this.selected_nodes).length > 1) {
        options.push(
          {
            content: "Convert to Subgraph",
            callback: /* @__PURE__ */ __name(() => {
              if (!this.selectedItems.size)
                throw new Error("Convert to Subgraph: Nothing selected.");
              this._graph.convertToSubgraph(this.selectedItems);
            }, "callback")
          },
          {
            content: "Align",
            has_submenu: true,
            callback: LGraphCanvas.onGroupAlign
          }
        );
      }
    }
    const extra = this.getExtraMenuOptions?.(this, options);
    return Array.isArray(extra) ? options.concat(extra) : options;
  }
  // called by processContextMenu to extract the menu list
  getNodeMenuOptions(node2) {
    let options;
    if (node2.getMenuOptions) {
      options = node2.getMenuOptions(this);
    } else {
      options = [
        {
          content: "Convert to Subgraph",
          callback: /* @__PURE__ */ __name(() => {
            if (this.selectedItems.size) {
              let hasGroups = false;
              for (const item of this.selectedItems) {
                const node22 = item;
                const isGroup = typeof node22.type === "string" && node22.type.startsWith(`${PREFIX}${SEPARATOR}`);
                if (isGroup && node22.convertToNodes) {
                  hasGroups = true;
                  const nodes = node22.convertToNodes();
                  requestAnimationFrame(() => {
                    this.selectItems(nodes, true);
                    if (!this.selectedItems.size)
                      throw new Error("Convert to Subgraph: Nothing selected.");
                    this._graph.convertToSubgraph(this.selectedItems);
                  });
                  return;
                }
              }
              if (!hasGroups) {
                if (!this.selectedItems.size)
                  throw new Error("Convert to Subgraph: Nothing selected.");
                this._graph.convertToSubgraph(this.selectedItems);
              }
            } else {
              throw new Error("Convert to Subgraph: Nothing selected.");
            }
          }, "callback")
        },
        {
          content: "Properties",
          has_submenu: true,
          callback: LGraphCanvas.onShowMenuNodeProperties
        },
        {
          content: "Properties Panel",
          callback: /* @__PURE__ */ __name(function(item, options2, e2, menu2, node22) {
            LGraphCanvas.active_canvas.showShowNodePanel(node22);
          }, "callback")
        },
        null,
        {
          content: "Title",
          callback: LGraphCanvas.onShowPropertyEditor
        },
        {
          content: "Mode",
          has_submenu: true,
          callback: LGraphCanvas.onMenuNodeMode
        }
      ];
      if (node2.resizable !== false) {
        options.push({
          content: "Resize",
          callback: LGraphCanvas.onMenuResizeNode
        });
      }
      if (node2.collapsible) {
        options.push({
          content: node2.collapsed ? "Expand" : "Collapse",
          callback: LGraphCanvas.onMenuNodeCollapse
        });
      }
      if (node2.widgets?.some((w) => w.advanced)) {
        options.push({
          content: node2.showAdvanced ? "Hide Advanced" : "Show Advanced",
          callback: LGraphCanvas.onMenuToggleAdvanced
        });
      }
      options.push(
        {
          content: node2.pinned ? "Unpin" : "Pin",
          callback: /* @__PURE__ */ __name(() => {
            for (const i in this.selected_nodes) {
              const node22 = this.selected_nodes[i];
              node22.pin();
            }
            this.setDirty(true, true);
          }, "callback")
        },
        {
          content: "Colors",
          has_submenu: true,
          callback: LGraphCanvas.onMenuNodeColors
        },
        {
          content: "Shapes",
          has_submenu: true,
          callback: LGraphCanvas.onMenuNodeShapes
        },
        null
      );
    }
    const extra = node2.getExtraMenuOptions?.(this, options);
    if (Array.isArray(extra) && extra.length > 0) {
      extra.push(null);
      options = extra.concat(options);
    }
    if (node2.clonable !== false) {
      options.push({
        content: "Clone",
        callback: LGraphCanvas.onMenuNodeClone
      });
    }
    if (Object.keys(this.selected_nodes).length > 1) {
      options.push(
        {
          content: "Align Selected To",
          has_submenu: true,
          callback: LGraphCanvas.onNodeAlign
        },
        {
          content: "Distribute Nodes",
          has_submenu: true,
          callback: LGraphCanvas.createDistributeMenu
        }
      );
    }
    options.push(null, {
      content: "Remove",
      disabled: !(node2.removable !== false && !node2.block_delete),
      callback: LGraphCanvas.onMenuNodeRemove
    });
    node2.graph?.onGetNodeMenuOptions?.(options, node2);
    return options;
  }
  /** @deprecated */
  getGroupMenuOptions(group) {
    console.warn(
      "LGraphCanvas.getGroupMenuOptions is deprecated, use LGraphGroup.getMenuOptions instead"
    );
    return group.getMenuOptions();
  }
  processContextMenu(node2, event) {
    const canvas2 = LGraphCanvas.active_canvas;
    const ref_window = canvas2.getCanvasWindow();
    let menu_info;
    const options = {
      event,
      callback: inner_option_clicked,
      extra: node2
    };
    if (node2) {
      options.title = node2.displayType ?? node2.type ?? void 0;
      LGraphCanvas.active_node = node2;
      const slot = node2.getSlotInPosition(event.canvasX, event.canvasY);
      if (slot) {
        menu_info = [];
        if (node2.getSlotMenuOptions) {
          menu_info = node2.getSlotMenuOptions(slot);
        } else {
          if (slot.output?.links?.length || slot.input?.link != null) {
            menu_info.push({ content: "Disconnect Links", slot });
          }
          const _slot = slot.input || slot.output;
          if (!_slot)
            throw new TypeError(
              "Both in put and output slots were null when processing context menu."
            );
          if (!_slot.nameLocked && !("link" in _slot && _slot.widget)) {
            menu_info.push({ content: "Rename Slot", slot });
          }
          if (_slot.removable) {
            menu_info.push(null);
            menu_info.push(
              _slot.locked ? "Cannot remove" : { content: "Remove Slot", slot, className: "danger" }
            );
          }
          if (node2.getExtraSlotMenuOptions) {
            menu_info.push(...node2.getExtraSlotMenuOptions(slot));
          }
        }
        options.title = (slot.input ? slot.input.type : slot.output.type) || "*";
        if (slot.input && slot.input.type == LiteGraph.ACTION)
          options.title = "Action";
        if (slot.output && slot.output.type == LiteGraph.EVENT)
          options.title = "Event";
      } else {
        menu_info = this.getNodeMenuOptions(node2);
      }
    } else {
      menu_info = this.getCanvasMenuOptions();
      if (!this.graph) throw new NullGraphError();
      if (this.links_render_mode !== LinkRenderType.HIDDEN_LINK) {
        const rerouteLayout = layoutStore.queryRerouteAtPoint({
          x: event.canvasX,
          y: event.canvasY
        });
        let reroute;
        if (rerouteLayout) {
          reroute = this.graph.getReroute(rerouteLayout.id);
        } else {
          reroute = this.graph.getRerouteOnPos(
            event.canvasX,
            event.canvasY,
            this.#visibleReroutes
          );
        }
        if (reroute) {
          menu_info.unshift(
            {
              content: "Delete Reroute",
              callback: /* @__PURE__ */ __name(() => {
                if (!this.graph) throw new NullGraphError();
                this.graph.removeReroute(reroute.id);
              }, "callback")
            },
            null
          );
        }
      }
      const group = this.graph.getGroupOnPos(event.canvasX, event.canvasY);
      if (group) {
        menu_info.push(null, {
          content: "Edit Group",
          has_submenu: true,
          submenu: {
            title: "Group",
            extra: group,
            options: group.getMenuOptions()
          }
        });
      }
    }
    if (!menu_info) return;
    new LiteGraph.ContextMenu(menu_info, options, ref_window);
    const createDialog = /* @__PURE__ */ __name((options2) => this.createDialog(
      "<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>",
      options2
    ), "createDialog");
    const setDirty = /* @__PURE__ */ __name(() => this.setDirty(true), "setDirty");
    function inner_option_clicked(v2, options2) {
      if (!v2) return;
      if (v2.content == "Remove Slot") {
        if (!node2?.graph) throw new NullGraphError();
        const info = v2.slot;
        if (!info)
          throw new TypeError(
            "Found-slot info was null when processing context menu."
          );
        node2.graph.beforeChange();
        if (info.input) {
          node2.removeInput(info.slot);
        } else if (info.output) {
          node2.removeOutput(info.slot);
        }
        node2.graph.afterChange();
        return;
      } else if (v2.content == "Disconnect Links") {
        if (!node2?.graph) throw new NullGraphError();
        const info = v2.slot;
        if (!info)
          throw new TypeError(
            "Found-slot info was null when processing context menu."
          );
        node2.graph.beforeChange();
        if (info.output) {
          node2.disconnectOutput(info.slot);
        } else if (info.input) {
          node2.disconnectInput(info.slot, true);
        }
        node2.graph.afterChange();
        return;
      } else if (v2.content == "Rename Slot") {
        if (!node2)
          throw new TypeError(
            "`node` was null when processing the context menu."
          );
        const info = v2.slot;
        if (!info)
          throw new TypeError(
            "Found-slot info was null when processing context menu."
          );
        const slot_info = info.input ? node2.getInputInfo(info.slot) : node2.getOutputInfo(info.slot);
        const dialog = createDialog(options2);
        const input = dialog.querySelector("input");
        if (input && slot_info) {
          input.value = slot_info.label || "";
        }
        const inner = /* @__PURE__ */ __name(function() {
          if (!node2.graph) throw new NullGraphError();
          node2.graph.beforeChange();
          if (input?.value) {
            if (slot_info) {
              slot_info.label = input.value;
            }
            setDirty();
          }
          dialog.close();
          node2.graph.afterChange();
        }, "inner");
        dialog.querySelector("button")?.addEventListener("click", inner);
        if (!input)
          throw new TypeError(
            "Input element was null when processing context menu."
          );
        input.addEventListener("keydown", function(e2) {
          dialog.is_modified = true;
          if (e2.key == "Escape") {
            dialog.close();
          } else if (e2.key == "Enter") {
            inner();
          } else if (e2.target.localName != "textarea") {
            return;
          }
          e2.preventDefault();
          e2.stopPropagation();
        });
        input.focus();
      }
    }
    __name(inner_option_clicked, "inner_option_clicked");
  }
  /**
   * Starts an animation to fit the view around the specified selection of nodes.
   * @param bounds The bounds to animate the view to, defined by a rectangle.
   */
  animateToBounds(bounds, options = {}) {
    const setDirty = /* @__PURE__ */ __name(() => this.setDirty(true, true), "setDirty");
    this.ds.animateToBounds(bounds, setDirty, options);
  }
  /**
   * Fits the view to the selected nodes with animation.
   * If nothing is selected, the view is fitted around all items in the graph.
   */
  fitViewToSelectionAnimated(options = {}) {
    const items = this.selectedItems.size ? Array.from(this.selectedItems) : this.positionableItems;
    const bounds = createBounds(items);
    if (!bounds)
      throw new TypeError(
        "Attempted to fit to view but could not calculate bounds."
      );
    const setDirty = /* @__PURE__ */ __name(() => this.setDirty(true, true), "setDirty");
    this.ds.animateToBounds(bounds, setDirty, options);
  }
  /**
   * Calculate new position with delta
   */
  calculateNewPosition(node2, deltaX, deltaY) {
    return {
      x: node2.pos[0] + deltaX,
      y: node2.pos[1] + deltaY
    };
  }
  /**
   * Apply batched node position updates
   */
  applyNodePositionUpdates(nodesToMove, mutations) {
    for (const { node: node2, newPos } of nodesToMove) {
      node2.pos[0] = newPos.x;
      node2.pos[1] = newPos.y;
      mutations.moveNode(node2.id, newPos);
    }
  }
  /**
   * Initialize layout mutations with Canvas source
   */
  initLayoutMutations() {
    const mutations = useLayoutMutations();
    mutations.setSource(LayoutSource.Canvas);
    return mutations;
  }
  /**
   * Collect all nodes that are children of groups in the selection
   */
  collectNodesInGroups(items) {
    const nodesInGroups = /* @__PURE__ */ new Set();
    for (const item of items) {
      if (item instanceof LGraphGroup) {
        for (const child of item._children) {
          if (child instanceof LGraphNode) {
            nodesInGroups.add(child);
          }
        }
      }
    }
    return nodesInGroups;
  }
  /**
   * Move group children (both nodes and non-nodes)
   */
  moveGroupChildren(group, deltaX, deltaY, nodesToMove) {
    for (const child of group._children) {
      if (child instanceof LGraphNode) {
        const node2 = child;
        nodesToMove.push({
          node: node2,
          newPos: this.calculateNewPosition(node2, deltaX, deltaY)
        });
      } else if (!(child instanceof LGraphGroup)) {
        child.move(deltaX, deltaY, true);
      }
    }
  }
  moveChildNodesInGroupVueMode(allItems, deltaX, deltaY) {
    const mutations = this.initLayoutMutations();
    const nodesInMovingGroups = this.collectNodesInGroups(allItems);
    const nodesToMove = [];
    for (const item of allItems) {
      const isNode = item instanceof LGraphNode;
      if (isNode) {
        const node2 = item;
        if (nodesInMovingGroups.has(node2)) {
          continue;
        }
        nodesToMove.push({
          node: node2,
          newPos: this.calculateNewPosition(node2, deltaX, deltaY)
        });
      } else if (item instanceof LGraphGroup) {
        item.move(deltaX, deltaY, true);
        this.moveGroupChildren(item, deltaX, deltaY, nodesToMove);
      } else {
        item.move(deltaX, deltaY, true);
      }
    }
    this.applyNodePositionUpdates(nodesToMove, mutations);
  }
  repositionNodesVueMode(nodesToReposition) {
    const mutations = this.initLayoutMutations();
    this.applyNodePositionUpdates(nodesToReposition, mutations);
  }
}
class MapProxyHandler {
  static {
    __name(this, "MapProxyHandler");
  }
  getOwnPropertyDescriptor(target, p) {
    const value = this.get(target, p);
    if (value) {
      return {
        configurable: true,
        enumerable: true,
        value
      };
    }
  }
  has(target, p) {
    if (typeof p === "symbol") return false;
    const int = parseInt(p, 10);
    return target.has(!isNaN(int) ? int : p);
  }
  ownKeys(target) {
    return [...target.keys()].map(String);
  }
  get(target, p) {
    if (p in target) return Reflect.get(target, p, target);
    if (typeof p === "symbol") return;
    const int = parseInt(p, 10);
    return target.get(!isNaN(int) ? int : p);
  }
  set(target, p, newValue2) {
    if (typeof p === "symbol") return false;
    const int = parseInt(p, 10);
    target.set(!isNaN(int) ? int : p, newValue2);
    return true;
  }
  deleteProperty(target, p) {
    return target.delete(p);
  }
  static bindAllMethods(map2) {
    map2.clear = map2.clear.bind(map2);
    map2.delete = map2.delete.bind(map2);
    map2.forEach = map2.forEach.bind(map2);
    map2.get = map2.get.bind(map2);
    map2.has = map2.has.bind(map2);
    map2.set = map2.set.bind(map2);
    map2.entries = map2.entries.bind(map2);
    map2.keys = map2.keys.bind(map2);
    map2.values = map2.values.bind(map2);
    map2[Symbol.iterator] = map2[Symbol.iterator].bind(map2);
  }
}
class LGraph {
  static {
    __name(this, "LGraph");
  }
  static serialisedSchemaVersion = 1;
  static STATUS_STOPPED = 1;
  static STATUS_RUNNING = 2;
  /** List of LGraph properties that are manually handled by {@link LGraph.configure}. */
  static ConfigureProperties = /* @__PURE__ */ new Set([
    "nodes",
    "groups",
    "links",
    "state",
    "reroutes",
    "floatingLinks",
    "id",
    "subgraphs",
    "definitions",
    "inputs",
    "outputs",
    "widgets",
    "inputNode",
    "outputNode",
    "extra"
  ]);
  id = zeroUuid;
  revision = 0;
  _version = -1;
  /** The backing store for links.  Keys are wrapped in String() */
  _links = /* @__PURE__ */ new Map();
  /**
   * Indexed property access is deprecated.
   * Backwards compatibility with a Proxy has been added, but will eventually be removed.
   *
   * Use {@link Map} methods:
   * ```
   * const linkId = 123
   * const link = graph.links.get(linkId)
   * // Deprecated: const link = graph.links[linkId]
   * ```
   */
  links;
  list_of_graphcanvas;
  status = LGraph.STATUS_STOPPED;
  state = {
    lastGroupId: 0,
    lastNodeId: 0,
    lastLinkId: 0,
    lastRerouteId: 0
  };
  events = new CustomEventTarget();
  _subgraphs = /* @__PURE__ */ new Map();
  _nodes = [];
  _nodes_by_id = {};
  _nodes_in_order = [];
  _nodes_executable = null;
  _groups = [];
  iteration = 0;
  globaltime = 0;
  /** @deprecated Unused */
  runningtime = 0;
  fixedtime = 0;
  fixedtime_lapse = 0.01;
  elapsed_time = 0.01;
  last_update_time = 0;
  starttime = 0;
  catch_errors = true;
  execution_timer_id;
  errors_in_execution;
  /** @deprecated Unused */
  execution_time;
  _last_trigger_time;
  filter;
  /** Must contain serialisable values, e.g. primitive types */
  config = {};
  vars = {};
  nodes_executing = [];
  nodes_actioning = [];
  nodes_executedAction = [];
  extra = {};
  /** @deprecated Deserialising a workflow sets this unused property. */
  version;
  /** @returns Whether the graph has no items */
  get empty() {
    return this._nodes.length + this._groups.length + this.reroutes.size === 0;
  }
  /** @returns All items on the canvas that can be selected */
  *positionableItems() {
    for (const node2 of this._nodes) yield node2;
    for (const group of this._groups) yield group;
    for (const reroute of this.reroutes.values()) yield reroute;
    return;
  }
  /** Internal only.  Not required for serialisation; calculated on deserialise. */
  #lastFloatingLinkId = 0;
  floatingLinksInternal = /* @__PURE__ */ new Map();
  get floatingLinks() {
    return this.floatingLinksInternal;
  }
  reroutesInternal = /* @__PURE__ */ new Map();
  /** All reroutes in this graph. */
  get reroutes() {
    return this.reroutesInternal;
  }
  get rootGraph() {
    return this;
  }
  get isRootGraph() {
    return this.rootGraph === this;
  }
  /** @deprecated See {@link state}.{@link LGraphState.lastNodeId lastNodeId} */
  get last_node_id() {
    return this.state.lastNodeId;
  }
  set last_node_id(value) {
    this.state.lastNodeId = value;
  }
  /** @deprecated See {@link state}.{@link LGraphState.lastLinkId lastLinkId} */
  get last_link_id() {
    return this.state.lastLinkId;
  }
  set last_link_id(value) {
    this.state.lastLinkId = value;
  }
  onTrigger;
  // @ts-expect-error - Private property type needs fixing
  _input_nodes;
  /**
   * See {@link LGraph}
   * @param o data from previous serialization [optional]
   */
  constructor(o) {
    const links = this._links;
    MapProxyHandler.bindAllMethods(links);
    const handler = new MapProxyHandler();
    this.links = new Proxy(links, handler);
    this.list_of_graphcanvas = null;
    this.clear();
    if (o) this.configure(o);
  }
  /**
   * Removes all nodes from this graph
   */
  clear() {
    this.stop();
    this.status = LGraph.STATUS_STOPPED;
    this.id = zeroUuid;
    this.revision = 0;
    this.state = {
      lastGroupId: 0,
      lastNodeId: 0,
      lastLinkId: 0,
      lastRerouteId: 0
    };
    this._version = -1;
    this._subgraphs.clear();
    if (this._nodes) {
      for (const _node of this._nodes) {
        _node.onRemoved?.();
        this.onNodeRemoved?.(_node);
      }
    }
    this._nodes = [];
    this._nodes_by_id = {};
    this._nodes_in_order = [];
    this._nodes_executable = null;
    this._links.clear();
    this.reroutes.clear();
    this.floatingLinksInternal.clear();
    this.#lastFloatingLinkId = 0;
    this._groups = [];
    this.iteration = 0;
    this.config = {};
    this.vars = {};
    this.extra = {};
    this.globaltime = 0;
    this.runningtime = 0;
    this.fixedtime = 0;
    this.fixedtime_lapse = 0.01;
    this.elapsed_time = 0.01;
    this.last_update_time = 0;
    this.starttime = 0;
    this.catch_errors = true;
    this.nodes_executing = [];
    this.nodes_actioning = [];
    this.nodes_executedAction = [];
    this.change();
    this.canvasAction((c2) => c2.clear());
  }
  get subgraphs() {
    return this.rootGraph._subgraphs;
  }
  get nodes() {
    return this._nodes;
  }
  get groups() {
    return this._groups;
  }
  /**
   * Attach Canvas to this graph
   */
  attachCanvas(canvas2) {
    if (!(canvas2 instanceof LGraphCanvas)) {
      throw new TypeError("attachCanvas expects an LGraphCanvas instance");
    }
    this.primaryCanvas = canvas2;
    this.list_of_graphcanvas ??= [];
    if (!this.list_of_graphcanvas.includes(canvas2)) {
      this.list_of_graphcanvas.push(canvas2);
    }
    if (canvas2.graph === this) return;
    canvas2.graph?.detachCanvas(canvas2);
    canvas2.graph = this;
    canvas2.subgraph = void 0;
  }
  /**
   * Detach Canvas from this graph
   */
  detachCanvas(canvas2) {
    canvas2.graph = null;
    const canvases = this.list_of_graphcanvas;
    if (canvases) {
      const pos = canvases.indexOf(canvas2);
      if (pos !== -1) canvases.splice(pos, 1);
    }
  }
  /**
   * @deprecated Will be removed in 0.9
   * Starts running this graph every interval milliseconds.
   * @param interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate
   */
  start(interval) {
    if (this.status == LGraph.STATUS_RUNNING) return;
    this.status = LGraph.STATUS_RUNNING;
    this.onPlayEvent?.();
    this.sendEventToAllNodes("onStart");
    this.starttime = LiteGraph.getTime();
    this.last_update_time = this.starttime;
    interval ||= 0;
    if (interval == 0 && typeof window != "undefined" && window.requestAnimationFrame) {
      const on_frame = /* @__PURE__ */ __name(() => {
        if (this.execution_timer_id != -1) return;
        window.requestAnimationFrame(on_frame);
        this.onBeforeStep?.();
        this.runStep(1, !this.catch_errors);
        this.onAfterStep?.();
      }, "on_frame");
      this.execution_timer_id = -1;
      on_frame();
    } else {
      this.execution_timer_id = setInterval(() => {
        this.onBeforeStep?.();
        this.runStep(1, !this.catch_errors);
        this.onAfterStep?.();
      }, interval);
    }
  }
  /**
   * @deprecated Will be removed in 0.9
   * Stops the execution loop of the graph
   */
  stop() {
    if (this.status == LGraph.STATUS_STOPPED) return;
    this.status = LGraph.STATUS_STOPPED;
    this.onStopEvent?.();
    if (this.execution_timer_id != null) {
      if (this.execution_timer_id != -1) {
        clearInterval(this.execution_timer_id);
      }
      this.execution_timer_id = null;
    }
    this.sendEventToAllNodes("onStop");
  }
  /**
   * Run N steps (cycles) of the graph
   * @param num number of steps to run, default is 1
   * @param do_not_catch_errors [optional] if you want to try/catch errors
   * @param limit max number of nodes to execute (used to execute from start to a node)
   */
  runStep(num, do_not_catch_errors, limit) {
    num = num || 1;
    const start = LiteGraph.getTime();
    this.globaltime = 1e-3 * (start - this.starttime);
    const nodes = this._nodes_executable || this._nodes;
    if (!nodes) return;
    limit = limit || nodes.length;
    if (do_not_catch_errors) {
      for (let i = 0; i < num; i++) {
        for (let j = 0; j < limit; ++j) {
          const node2 = nodes[j];
          if (node2.mode == LGraphEventMode.ALWAYS && node2.onExecute) {
            node2.doExecute?.();
          }
        }
        this.fixedtime += this.fixedtime_lapse;
        this.onExecuteStep?.();
      }
      this.onAfterExecute?.();
    } else {
      try {
        for (let i = 0; i < num; i++) {
          for (let j = 0; j < limit; ++j) {
            const node2 = nodes[j];
            if (node2.mode == LGraphEventMode.ALWAYS) {
              node2.onExecute?.();
            }
          }
          this.fixedtime += this.fixedtime_lapse;
          this.onExecuteStep?.();
        }
        this.onAfterExecute?.();
        this.errors_in_execution = false;
      } catch (error) {
        this.errors_in_execution = true;
        if (LiteGraph.throw_errors) throw error;
        if (LiteGraph.debug) console.error("Error during execution:", error);
        this.stop();
      }
    }
    const now = LiteGraph.getTime();
    let elapsed = now - start;
    if (elapsed == 0) elapsed = 1;
    this.execution_time = 1e-3 * elapsed;
    this.globaltime += 1e-3 * elapsed;
    this.iteration += 1;
    this.elapsed_time = (now - this.last_update_time) * 1e-3;
    this.last_update_time = now;
    this.nodes_executing = [];
    this.nodes_actioning = [];
    this.nodes_executedAction = [];
  }
  /**
   * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than
   * nodes with only inputs.
   */
  updateExecutionOrder() {
    this._nodes_in_order = this.computeExecutionOrder(false);
    this._nodes_executable = [];
    for (const node2 of this._nodes_in_order) {
      if (node2.onExecute) {
        this._nodes_executable.push(node2);
      }
    }
  }
  // This is more internal, it computes the executable nodes in order and returns it
  computeExecutionOrder(only_onExecute, set_level) {
    const L = [];
    const S = [];
    const M = {};
    const visited_links = {};
    const remaining_links = {};
    for (const node2 of this._nodes) {
      if (only_onExecute && !node2.onExecute) {
        continue;
      }
      M[node2.id] = node2;
      let num = 0;
      if (node2.inputs) {
        for (const input of node2.inputs) {
          if (input?.link != null) {
            num += 1;
          }
        }
      }
      if (num == 0) {
        S.push(node2);
        if (set_level) node2._level = 1;
      } else {
        if (set_level) node2._level = 0;
        remaining_links[node2.id] = num;
      }
    }
    while (true) {
      const node2 = S.shift();
      if (node2 === void 0) break;
      L.push(node2);
      delete M[node2.id];
      if (!node2.outputs) continue;
      for (const output of node2.outputs) {
        if (output?.links == null || output.links.length == 0) continue;
        for (const link_id of output.links) {
          const link = this._links.get(link_id);
          if (!link) continue;
          if (visited_links[link.id]) continue;
          const target_node = this.getNodeById(link.target_id);
          if (target_node == null) {
            visited_links[link.id] = true;
            continue;
          }
          if (set_level) {
            node2._level ??= 0;
            if (!target_node._level || target_node._level <= node2._level) {
              target_node._level = node2._level + 1;
            }
          }
          visited_links[link.id] = true;
          remaining_links[target_node.id] -= 1;
          if (remaining_links[target_node.id] == 0) S.push(target_node);
        }
      }
    }
    for (const i in M) {
      L.push(M[i]);
    }
    if (L.length != this._nodes.length && LiteGraph.debug)
      console.warn("something went wrong, nodes missing");
    function setOrder(nodes) {
      const l = nodes.length;
      for (let i = 0; i < l; ++i) {
        nodes[i].order = i;
      }
    }
    __name(setOrder, "setOrder");
    setOrder(L);
    L.sort(function(A, B2) {
      const Ap = A.constructor.priority || A.priority || 0;
      const Bp = B2.constructor.priority || B2.priority || 0;
      return Ap == Bp ? A.order - B2.order : Ap - Bp;
    });
    setOrder(L);
    return L;
  }
  /**
   * Positions every node in a more readable manner
   */
  arrange(margin, layout) {
    margin = margin || 100;
    const nodes = this.computeExecutionOrder(false, true);
    const columns = [];
    for (const node2 of nodes) {
      const col = node2._level || 1;
      columns[col] ||= [];
      columns[col].push(node2);
    }
    let x2 = margin;
    for (const column of columns) {
      if (!column) continue;
      let max_size = 100;
      let y = margin + LiteGraph.NODE_TITLE_HEIGHT;
      for (const node2 of column) {
        node2.pos[0] = layout == LiteGraph.VERTICAL_LAYOUT ? y : x2;
        node2.pos[1] = layout == LiteGraph.VERTICAL_LAYOUT ? x2 : y;
        const max_size_index = layout == LiteGraph.VERTICAL_LAYOUT ? 1 : 0;
        if (node2.size[max_size_index] > max_size) {
          max_size = node2.size[max_size_index];
        }
        const node_size_index = layout == LiteGraph.VERTICAL_LAYOUT ? 0 : 1;
        y += node2.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT;
      }
      x2 += max_size + margin;
    }
    this.setDirtyCanvas(true, true);
  }
  /**
   * Returns the amount of time the graph has been running in milliseconds
   * @returns number of milliseconds the graph has been running
   */
  getTime() {
    return this.globaltime;
  }
  /**
   * Returns the amount of time accumulated using the fixedtime_lapse var.
   * This is used in context where the time increments should be constant
   * @returns number of milliseconds the graph has been running
   */
  getFixedTime() {
    return this.fixedtime;
  }
  /**
   * Returns the amount of time it took to compute the latest iteration.
   * Take into account that this number could be not correct
   * if the nodes are using graphical actions
   * @returns number of milliseconds it took the last cycle
   */
  getElapsedTime() {
    return this.elapsed_time;
  }
  /**
   * @deprecated Will be removed in 0.9
   * Sends an event to all the nodes, useful to trigger stuff
   * @param eventname the name of the event (function to be called)
   * @param params parameters in array format
   */
  sendEventToAllNodes(eventname, params, mode) {
    mode = mode || LGraphEventMode.ALWAYS;
    const nodes = this._nodes_in_order || this._nodes;
    if (!nodes) return;
    for (const node2 of nodes) {
      if (!node2[eventname] || node2.mode != mode) continue;
      if (params === void 0) {
        node2[eventname]();
      } else if (params && params.constructor === Array) {
        node2[eventname].apply(node2, params);
      } else {
        node2[eventname](params);
      }
    }
  }
  /**
   * Runs an action on every canvas registered to this graph.
   * @param action Action to run for every canvas
   */
  canvasAction(action) {
    const canvases = this.list_of_graphcanvas;
    if (!canvases) return;
    for (const canvas2 of canvases) action(canvas2);
  }
  /** @deprecated See {@link LGraph.canvasAction} */
  sendActionToCanvas(action, params) {
    const { list_of_graphcanvas } = this;
    if (!list_of_graphcanvas) return;
    for (const c2 of list_of_graphcanvas) {
      c2[action]?.apply(c2, params);
    }
  }
  /**
   * Adds a new node instance to this graph
   * @param node the instance of the node
   */
  add(node2, skip_compute_order) {
    if (!node2) return;
    const { state: state2 } = this;
    if (LiteGraph.alwaysSnapToGrid) {
      const snapTo = this.getSnapToGridSize();
      if (snapTo) node2.snapToGrid(snapTo);
    }
    if (node2 instanceof LGraphGroup) {
      if (node2.id == null || node2.id === -1) node2.id = ++state2.lastGroupId;
      if (node2.id > state2.lastGroupId) state2.lastGroupId = node2.id;
      this._groups.push(node2);
      this.setDirtyCanvas(true);
      this.change();
      node2.graph = this;
      this._version++;
      return;
    }
    if (node2.id != -1 && this._nodes_by_id[node2.id] != null) {
      console.warn(
        "LiteGraph: there is already a node with this ID, changing it"
      );
      node2.id = LiteGraph.use_uuids ? LiteGraph.uuidv4() : ++state2.lastNodeId;
    }
    if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) {
      throw "LiteGraph: max number of nodes in a graph reached";
    }
    if (LiteGraph.use_uuids) {
      if (node2.id == null || node2.id == -1) node2.id = LiteGraph.uuidv4();
    } else {
      if (node2.id == null || node2.id == -1) {
        node2.id = ++state2.lastNodeId;
      } else if (typeof node2.id === "number" && state2.lastNodeId < node2.id) {
        state2.lastNodeId = node2.id;
      }
    }
    node2.graph = this;
    this._version++;
    this._nodes.push(node2);
    this._nodes_by_id[node2.id] = node2;
    node2.onAdded?.(this);
    if (this.config.align_to_grid) node2.alignToGrid();
    if (!skip_compute_order) this.updateExecutionOrder();
    this.onNodeAdded?.(node2);
    this.setDirtyCanvas(true);
    this.change();
    return node2;
  }
  /**
   * Removes a node from the graph
   * @param node the instance of the node
   */
  remove(node2) {
    if (node2 instanceof LGraphGroup) {
      this.canvasAction((c2) => c2.deselect(node2));
      const index2 = this._groups.indexOf(node2);
      if (index2 != -1) {
        this._groups.splice(index2, 1);
      }
      node2.graph = void 0;
      this._version++;
      this.setDirtyCanvas(true, true);
      this.change();
      return;
    }
    if (this._nodes_by_id[node2.id] == null) {
      console.warn("LiteGraph: node not found", node2);
      return;
    }
    if (node2.ignore_remove) {
      console.warn("LiteGraph: node cannot be removed", node2);
      return;
    }
    this.beforeChange();
    const { inputs, outputs } = node2;
    if (inputs) {
      for (const [i, slot] of inputs.entries()) {
        if (slot.link != null) node2.disconnectInput(i, true);
      }
    }
    if (outputs) {
      for (const [i, slot] of outputs.entries()) {
        if (slot.links?.length) node2.disconnectOutput(i);
      }
    }
    for (const link of this.floatingLinks.values()) {
      if (link.origin_id === node2.id || link.target_id === node2.id) {
        this.removeFloatingLink(link);
      }
    }
    node2.onRemoved?.();
    node2.graph = null;
    this._version++;
    const { list_of_graphcanvas } = this;
    if (list_of_graphcanvas) {
      for (const canvas2 of list_of_graphcanvas) {
        if (canvas2.selected_nodes[node2.id])
          delete canvas2.selected_nodes[node2.id];
        canvas2.deselect(node2);
      }
    }
    const pos = this._nodes.indexOf(node2);
    if (pos != -1) this._nodes.splice(pos, 1);
    delete this._nodes_by_id[node2.id];
    this.onNodeRemoved?.(node2);
    this.canvasAction((c2) => c2.checkPanels());
    this.setDirtyCanvas(true, true);
    this.afterChange();
    this.change();
    this.updateExecutionOrder();
  }
  /**
   * Returns a node by its id.
   */
  getNodeById(id2) {
    return id2 != null ? this._nodes_by_id[id2] : null;
  }
  /**
   * Returns a list of nodes that matches a class
   * @param classObject the class itself (not an string)
   * @returns a list with all the nodes of this type
   */
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
  findNodesByClass(classObject, result) {
    result = result || [];
    result.length = 0;
    const { _nodes } = this;
    for (const node2 of _nodes) {
      if (node2.constructor === classObject) result.push(node2);
    }
    return result;
  }
  /**
   * Returns a list of nodes that matches a type
   * @param type the name of the node type
   * @returns a list with all the nodes of this type
   */
  findNodesByType(type, result) {
    const matchType = type.toLowerCase();
    result = result || [];
    result.length = 0;
    const { _nodes } = this;
    for (const node2 of _nodes) {
      if (node2.type?.toLowerCase() == matchType) result.push(node2);
    }
    return result;
  }
  /**
   * Returns the first node that matches a name in its title
   * @param title the name of the node to search
   * @returns the node or null
   */
  findNodeByTitle(title) {
    const { _nodes } = this;
    for (const node2 of _nodes) {
      if (node2.title == title) return node2;
    }
    return null;
  }
  /**
   * Returns a list of nodes that matches a name
   * @param title the name of the node to search
   * @returns a list with all the nodes with this name
   */
  findNodesByTitle(title) {
    const result = [];
    const { _nodes } = this;
    for (const node2 of _nodes) {
      if (node2.title == title) result.push(node2);
    }
    return result;
  }
  /**
   * Returns the top-most node in this position of the canvas
   * @param x the x coordinate in canvas space
   * @param y the y coordinate in canvas space
   * @param nodeList a list with all the nodes to search from, by default is all the nodes in the graph
   * @returns the node at this position or null
   */
  getNodeOnPos(x2, y, nodeList) {
    const nodes = nodeList || this._nodes;
    let i = nodes.length;
    while (--i >= 0) {
      const node2 = nodes[i];
      if (node2.isPointInside(x2, y)) return node2;
    }
    return null;
  }
  /**
   * Returns the top-most group in that position
   * @param x The x coordinate in canvas space
   * @param y The y coordinate in canvas space
   * @returns The group or null
   */
  getGroupOnPos(x2, y) {
    for (let i = this._groups.length - 1; i >= 0; i--) {
      const group = this._groups[i];
      if (group.isPointInside(x2, y)) {
        return group;
      }
    }
    return void 0;
  }
  /**
   * Returns the top-most group with a titlebar in the provided position.
   * @param x The x coordinate in canvas space
   * @param y The y coordinate in canvas space
   * @returns The group or null
   */
  getGroupTitlebarOnPos(x2, y) {
    for (let i = this._groups.length - 1; i >= 0; i--) {
      const group = this._groups[i];
      if (group.isPointInTitlebar(x2, y)) {
        return group;
      }
    }
    return void 0;
  }
  /**
   * Finds a reroute a the given graph point
   * @param x X co-ordinate in graph space
   * @param y Y co-ordinate in graph space
   * @returns The first reroute under the given co-ordinates, or undefined
   */
  getRerouteOnPos(x2, y, reroutes) {
    for (const reroute of reroutes ?? this.reroutes.values()) {
      if (reroute.containsPoint([x2, y])) return reroute;
    }
  }
  /**
   * Snaps the provided items to a grid.
   *
   * Item positions are rounded to the nearest multiple of {@link LiteGraph.CANVAS_GRID_SIZE}.
   *
   * When {@link LiteGraph.alwaysSnapToGrid} is enabled
   * and the grid size is falsy, a default of 1 is used.
   * @param items The items to be snapped to the grid
   * @todo Currently only snaps nodes.
   */
  snapToGrid(items) {
    const snapTo = this.getSnapToGridSize();
    if (!snapTo) return;
    for (const item of getAllNestedItems(items)) {
      if (!item.pinned) item.snapToGrid(snapTo);
    }
  }
  /**
   * Finds the size of the grid that items should be snapped to when moved.
   * @returns The size of the grid that items should be snapped to
   */
  getSnapToGridSize() {
    return LiteGraph.alwaysSnapToGrid ? LiteGraph.CANVAS_GRID_SIZE || 1 : LiteGraph.CANVAS_GRID_SIZE;
  }
  /**
   * @deprecated Will be removed in 0.9
   * Checks that the node type matches the node type registered,
   * used when replacing a nodetype by a newer version during execution
   * this replaces the ones using the old version with the new version
   */
  checkNodeTypes() {
    const { _nodes } = this;
    for (const [i, node2] of _nodes.entries()) {
      const ctor = LiteGraph.registered_node_types[node2.type];
      if (node2.constructor == ctor) continue;
      console.warn("node being replaced by newer version:", node2.type);
      const newnode = LiteGraph.createNode(node2.type);
      if (!newnode) continue;
      _nodes[i] = newnode;
      newnode.configure(node2.serialize());
      newnode.graph = this;
      this._nodes_by_id[newnode.id] = newnode;
      if (node2.inputs) newnode.inputs = [...node2.inputs];
      if (node2.outputs) newnode.outputs = [...node2.outputs];
    }
    this.updateExecutionOrder();
  }
  trigger(action, param) {
    const validEventTypes = /* @__PURE__ */ new Set([
      "node:slot-links:changed",
      "node:slot-errors:changed",
      "node:property:changed"
    ]);
    if (validEventTypes.has(action) && param && typeof param === "object") {
      this.onTrigger?.({ type: action, ...param });
    }
  }
  /** @todo Clean up - never implemented. */
  triggerInput(name2, value) {
    const nodes = this.findNodesByTitle(name2);
    for (const node2 of nodes) {
      node2.onTrigger(value);
    }
  }
  /** @todo Clean up - never implemented. */
  setCallback(name2, func) {
    const nodes = this.findNodesByTitle(name2);
    for (const node2 of nodes) {
      node2.setTrigger(func);
    }
  }
  // used for undo, called before any change is made to the graph
  beforeChange(info) {
    this.onBeforeChange?.(this, info);
    this.canvasAction((c2) => c2.onBeforeChange?.(this));
  }
  // used to resend actions, called after any change is made to the graph
  afterChange(info) {
    this.onAfterChange?.(this, info);
    this.canvasAction((c2) => c2.onAfterChange?.(this));
  }
  /**
   * clears the triggered slot animation in all links (stop visual animation)
   */
  clearTriggeredSlots() {
    for (const link_info of this._links.values()) {
      if (!link_info) continue;
      if (link_info._last_time) link_info._last_time = 0;
    }
  }
  /* Called when something visually changed (not the graph!) */
  change() {
    this.canvasAction((c2) => c2.setDirty(true, true));
    this.on_change?.(this);
  }
  setDirtyCanvas(fg, bg) {
    this.canvasAction((c2) => c2.setDirty(fg, bg));
  }
  addFloatingLink(link) {
    if (link.id === -1) {
      link.id = ++this.#lastFloatingLinkId;
    }
    this.floatingLinksInternal.set(link.id, link);
    const slot = link.target_id !== -1 ? this.getNodeById(link.target_id)?.inputs?.[link.target_slot] : this.getNodeById(link.origin_id)?.outputs?.[link.origin_slot];
    if (slot) {
      slot._floatingLinks ??= /* @__PURE__ */ new Set();
      slot._floatingLinks.add(link);
    } else {
      console.warn(
        `Adding invalid floating link: target/slot: [${link.target_id}/${link.target_slot}] origin/slot: [${link.origin_id}/${link.origin_slot}]`
      );
    }
    const reroutes = LLink.getReroutes(this, link);
    for (const reroute of reroutes) {
      reroute.floatingLinkIds.add(link.id);
    }
    return link;
  }
  removeFloatingLink(link) {
    this.floatingLinksInternal.delete(link.id);
    const slot = link.target_id !== -1 ? this.getNodeById(link.target_id)?.inputs?.[link.target_slot] : this.getNodeById(link.origin_id)?.outputs?.[link.origin_slot];
    if (slot) {
      slot._floatingLinks?.delete(link);
    }
    const reroutes = LLink.getReroutes(this, link);
    for (const reroute of reroutes) {
      reroute.floatingLinkIds.delete(link.id);
      if (reroute.floatingLinkIds.size === 0) {
        delete reroute.floating;
      }
      if (reroute.totalLinks === 0) this.removeReroute(reroute.id);
    }
  }
  getLink(id2) {
    return id2 == null ? void 0 : this._links.get(id2);
  }
  getReroute(id2) {
    return id2 == null ? void 0 : this.reroutes.get(id2);
  }
  /**
   * Configures a reroute on the graph where ID is already known (probably deserialisation).
   * Creates the object if it does not exist.
   * @param serialisedReroute See {@link SerialisableReroute}
   */
  setReroute({
    id: id2,
    parentId,
    pos,
    linkIds,
    floating
  }) {
    id2 ??= ++this.state.lastRerouteId;
    if (id2 > this.state.lastRerouteId) this.state.lastRerouteId = id2;
    const reroute = this.reroutes.get(id2) ?? new Reroute(id2, this);
    reroute.update(parentId, pos, linkIds, floating);
    this.reroutes.set(id2, reroute);
    return reroute;
  }
  /**
   * Creates a new reroute and adds it to the graph.
   * @param pos Position in graph space
   * @param before The existing link segment (reroute, link) that will be after this reroute,
   * going from the node output to input.
   * @returns The newly created reroute - typically ignored.
   */
  createReroute(pos, before) {
    const layoutMutations2 = useLayoutMutations();
    const rerouteId = ++this.state.lastRerouteId;
    const linkIds = before instanceof Reroute ? before.linkIds : [before.id];
    const floatingLinkIds = before instanceof Reroute ? before.floatingLinkIds : [before.id];
    const reroute = new Reroute(
      rerouteId,
      this,
      pos,
      before.parentId,
      linkIds,
      floatingLinkIds
    );
    this.reroutes.set(rerouteId, reroute);
    layoutMutations2.setSource(LayoutSource.Canvas);
    layoutMutations2.createReroute(
      rerouteId,
      { x: pos[0], y: pos[1] },
      before.parentId,
      Array.from(linkIds)
    );
    for (const linkId of linkIds) {
      const link = this._links.get(linkId);
      if (!link) continue;
      if (link.parentId === before.parentId) link.parentId = rerouteId;
      const reroutes = LLink.getReroutes(this, link);
      for (const x2 of reroutes.filter((x22) => x22.parentId === before.parentId)) {
        x2.parentId = rerouteId;
      }
    }
    for (const linkId of floatingLinkIds) {
      const link = this.floatingLinks.get(linkId);
      if (!link) continue;
      if (link.parentId === before.parentId) link.parentId = rerouteId;
      const reroutes = LLink.getReroutes(this, link);
      for (const x2 of reroutes.filter((x22) => x22.parentId === before.parentId)) {
        x2.parentId = rerouteId;
      }
    }
    return reroute;
  }
  /**
   * Removes a reroute from the graph
   * @param id ID of reroute to remove
   */
  removeReroute(id2) {
    const layoutMutations2 = useLayoutMutations();
    const { reroutes } = this;
    const reroute = reroutes.get(id2);
    if (!reroute) return;
    this.canvasAction((c2) => c2.deselect(reroute));
    const { parentId, linkIds, floatingLinkIds } = reroute;
    for (const reroute2 of reroutes.values()) {
      if (reroute2.parentId === id2) reroute2.parentId = parentId;
    }
    for (const linkId of linkIds) {
      const link = this._links.get(linkId);
      if (link && link.parentId === id2) link.parentId = parentId;
    }
    for (const linkId of floatingLinkIds) {
      const link = this.floatingLinks.get(linkId);
      if (!link) {
        console.warn(
          `Removed reroute had floating link ID that did not exist [${linkId}]`
        );
        continue;
      }
      const floatingReroutes = LLink.getReroutes(this, link);
      const lastReroute = floatingReroutes.at(-1);
      const secondLastReroute = floatingReroutes.at(-2);
      if (reroute !== lastReroute) {
        continue;
      } else if (secondLastReroute?.totalLinks !== 1) {
        this.removeFloatingLink(link);
      } else if (link.parentId === id2) {
        link.parentId = parentId;
        secondLastReroute.floating = reroute.floating;
      }
    }
    reroutes.delete(id2);
    layoutMutations2.setSource(LayoutSource.Canvas);
    layoutMutations2.deleteReroute(id2);
    this.setDirtyCanvas(false, true);
  }
  /**
   * Destroys a link
   */
  removeLink(link_id) {
    const link = this._links.get(link_id);
    if (!link) return;
    const node2 = this.getNodeById(link.target_id);
    node2?.disconnectInput(link.target_slot, false);
    link.disconnect(this);
  }
  /**
   * Creates a new subgraph definition, and adds it to the graph.
   * @param data Exported data (typically serialised) to configure the new subgraph with
   * @returns The newly created subgraph definition.
   */
  createSubgraph(data) {
    const { id: id2 } = data;
    const subgraph = new Subgraph(this.rootGraph, data);
    this.subgraphs.set(id2, subgraph);
    this.rootGraph.events.dispatch("subgraph-created", { subgraph, data });
    return subgraph;
  }
  convertToSubgraph(items) {
    if (items.size === 0)
      throw new Error("Cannot convert to subgraph: nothing to convert");
    const { state: state2, revision, config: config2 } = this;
    const firstChild = [...items][0];
    if (items.size === 1 && firstChild instanceof LGraphGroup) {
      items = /* @__PURE__ */ new Set([firstChild]);
      firstChild.recomputeInsideNodes();
      firstChild.children.forEach((n) => items.add(n));
    }
    const {
      boundaryLinks,
      boundaryFloatingLinks,
      internalLinks,
      boundaryInputLinks,
      boundaryOutputLinks
    } = getBoundaryLinks(this, items);
    const { nodes, reroutes, groups } = splitPositionables(items);
    const boundingRect = createBounds(items);
    if (!boundingRect)
      throw new Error("Failed to create bounding rect for subgraph");
    const resolvedInputLinks = boundaryInputLinks.map((x2) => x2.resolve(this));
    const resolvedOutputLinks = boundaryOutputLinks.map((x2) => x2.resolve(this));
    const clonedNodes = multiClone(nodes);
    const links = internalLinks.map((x2) => x2.asSerialisable());
    const inputs = mapSubgraphInputsAndLinks(resolvedInputLinks, links);
    const outputs = mapSubgraphOutputsAndLinks(resolvedOutputLinks, links);
    const data = {
      id: createUuidv4(),
      name: "New Subgraph",
      inputNode: {
        id: SUBGRAPH_INPUT_ID,
        bounding: [0, 0, 75, 100]
      },
      outputNode: {
        id: SUBGRAPH_OUTPUT_ID,
        bounding: [0, 0, 75, 100]
      },
      inputs,
      outputs,
      widgets: [],
      version: LGraph.serialisedSchemaVersion,
      state: state2,
      revision,
      config: config2,
      links,
      nodes: clonedNodes,
      reroutes: structuredClone(
        [...reroutes].map((reroute) => reroute.asSerialisable())
      ),
      groups: structuredClone([...groups].map((group) => group.serialize()))
    };
    const subgraph = this.createSubgraph(data);
    subgraph.configure(data);
    for (const node2 of subgraph.nodes) node2.onGraphConfigured?.();
    for (const node2 of subgraph.nodes) node2.onAfterGraphConfigured?.();
    subgraph.inputNode.arrange();
    subgraph.outputNode.arrange();
    const { boundingRect: inputRect } = subgraph.inputNode;
    const { boundingRect: outputRect } = subgraph.outputNode;
    alignOutsideContainer(inputRect, Alignment.MidLeft, boundingRect, [50, 0]);
    alignOutsideContainer(outputRect, Alignment.MidRight, boundingRect, [50, 0]);
    for (const resolved of resolvedInputLinks)
      resolved.inputNode?.disconnectInput(
        resolved.inputNode.inputs.indexOf(resolved.input),
        true
      );
    for (const resolved of resolvedOutputLinks)
      resolved.outputNode?.disconnectOutput(
        resolved.outputNode.outputs.indexOf(resolved.output),
        resolved.inputNode
      );
    for (const node2 of nodes) this.remove(node2);
    for (const reroute of reroutes) this.removeReroute(reroute.id);
    for (const group of groups) this.remove(group);
    this.rootGraph.events.dispatch("convert-to-subgraph", {
      subgraph,
      bounds: boundingRect,
      exportedSubgraph: data,
      boundaryLinks,
      resolvedInputLinks,
      resolvedOutputLinks,
      boundaryFloatingLinks,
      internalLinks
    });
    const subgraphNode = LiteGraph.createNode(subgraph.id, subgraph.name, {
      outputs: structuredClone(outputs)
    });
    if (!subgraphNode) throw new Error("Failed to create subgraph node");
    for (let i2 = 0; i2 < inputs.length; i2++) {
      Object.assign(subgraphNode.inputs[i2], inputs[i2]);
    }
    subgraphNode.setSize(subgraphNode.computeSize());
    alignToContainer(
      subgraphNode._posSize,
      Alignment.Centre | Alignment.Middle,
      boundingRect
    );
    subgraphNode.pos[1] += LiteGraph.NODE_TITLE_HEIGHT / 2;
    this.add(subgraphNode);
    const groupedByOutput = groupResolvedByOutput(resolvedInputLinks);
    let i = 0;
    for (const [, connections] of groupedByOutput.entries()) {
      const [firstResolved, ...others] = connections;
      const { output, outputNode, link, subgraphInput } = firstResolved;
      i++;
      if (link.origin_id === SUBGRAPH_INPUT_ID) {
        link.target_id = subgraphNode.id;
        link.target_slot = i - 1;
        if (subgraphInput instanceof SubgraphInput) {
          subgraphInput.connect(
            subgraphNode.findInputSlotByType(link.type, true, true),
            subgraphNode,
            link.parentId
          );
        } else {
          throw new TypeError("Subgraph input node is not a SubgraphInput");
        }
        for (const resolved of others) {
          resolved.link.disconnect(this);
        }
        continue;
      }
      if (!output || !outputNode) {
        console.warn(
          "Convert to Subgraph reconnect: Failed to resolve input link",
          connections[0]
        );
        continue;
      }
      const input = subgraphNode.inputs[i - 1];
      outputNode.connectSlots(output, subgraphNode, input, link.parentId);
    }
    const outputsGroupedByOutput = groupResolvedByOutput(resolvedOutputLinks);
    i = 0;
    for (const [, connections] of outputsGroupedByOutput.entries()) {
      i++;
      for (const connection of connections) {
        const { input, inputNode, link, subgraphOutput } = connection;
        if (link.target_id === SUBGRAPH_OUTPUT_ID) {
          link.origin_id = subgraphNode.id;
          link.origin_slot = i - 1;
          this.links.set(link.id, link);
          if (subgraphOutput instanceof SubgraphOutput) {
            subgraphOutput.connect(
              subgraphNode.findOutputSlotByType(link.type, true, true),
              subgraphNode,
              link.parentId
            );
          } else {
            throw new TypeError("Subgraph input node is not a SubgraphInput");
          }
          continue;
        }
        if (!input || !inputNode) {
          console.warn(
            "Convert to Subgraph reconnect: Failed to resolve output link",
            connection
          );
          continue;
        }
        const output = subgraphNode.outputs[i - 1];
        subgraphNode.connectSlots(output, inputNode, input, link.parentId);
      }
    }
    subgraphNode._setConcreteSlots();
    subgraphNode.arrange();
    this.canvasAction(
      (c2) => c2.canvas.dispatchEvent(
        new CustomEvent("subgraph-converted", {
          bubbles: true,
          detail: { subgraphNode }
        })
      )
    );
    return { subgraph, node: subgraphNode };
  }
  unpackSubgraph(subgraphNode, options) {
    if (!(subgraphNode instanceof SubgraphNode))
      throw new Error("Can only unpack Subgraph Nodes");
    const skipMissingNodes = options?.skipMissingNodes ?? false;
    this.beforeChange();
    const positionables = [
      ...subgraphNode.subgraph.nodes,
      ...subgraphNode.subgraph.reroutes.values(),
      ...subgraphNode.subgraph.groups
    ].map((p) => {
      return {
        boundingRect: [p.pos[0], p.pos[1], p.size?.[0] ?? 0, p.size?.[1] ?? 0]
      };
    });
    const bounds = createBounds(positionables) ?? [0, 0, 0, 0];
    const center = [bounds[0] + bounds[2] / 2, bounds[1] + bounds[3] / 2];
    const toSelect = [];
    const offsetX = subgraphNode.pos[0] - center[0] + subgraphNode.size[0] / 2;
    const offsetY = subgraphNode.pos[1] - center[1] + subgraphNode.size[1] / 2;
    const movedNodes = multiClone(subgraphNode.subgraph.nodes);
    const nodeIdMap = /* @__PURE__ */ new Map();
    for (const n_info of movedNodes) {
      let node2 = LiteGraph.createNode(String(n_info.type), n_info.title);
      if (!node2) {
        if (skipMissingNodes) {
          console.warn(
            `Cannot unpack node of type "${n_info.type}" - node type not found. Creating placeholder node.`
          );
          node2 = new LGraphNode(n_info.title || n_info.type || "Missing Node");
          node2.last_serialization = n_info;
          node2.has_errors = true;
          node2.type = String(n_info.type);
        } else {
          throw new Error(
            `Cannot unpack: node type "${n_info.type}" is not registered`
          );
        }
      }
      nodeIdMap.set(n_info.id, ++this.last_node_id);
      node2.id = this.last_node_id;
      n_info.id = this.last_node_id;
      this.add(node2, true);
      node2.configure(n_info);
      node2.pos[0] += offsetX;
      node2.pos[1] += offsetY;
      for (const input of node2.inputs) {
        input.link = null;
      }
      for (const output of node2.outputs) {
        output.links = [];
      }
      toSelect.push(node2);
    }
    const groups = structuredClone(
      [...subgraphNode.subgraph.groups].map((g2) => g2.serialize())
    );
    for (const g_info of groups) {
      const group = new LGraphGroup(g_info.title, g_info.id);
      this.add(group, true);
      group.configure(g_info);
      group.pos[0] += offsetX;
      group.pos[1] += offsetY;
      toSelect.push(group);
    }
    for (const islot of subgraphNode.inputs) {
      if (!islot.link) continue;
      const link = this.links.get(islot.link);
      if (!link) {
        console.warn("Broken link", islot, islot.link);
        continue;
      }
      for (const reroute of LLink.getReroutes(this, link)) {
        reroute.linkIds.delete(link.id);
      }
    }
    for (const oslot of subgraphNode.outputs) {
      for (const linkId of oslot.links ?? []) {
        const link = this.links.get(linkId);
        if (!link) {
          console.warn("Broken link", oslot, linkId);
          continue;
        }
        for (const reroute of LLink.getReroutes(this, link)) {
          reroute.linkIds.delete(link.id);
        }
      }
    }
    const newLinks = [];
    for (const [, link] of subgraphNode.subgraph._links) {
      let externalParentId;
      if (link.origin_id === SUBGRAPH_INPUT_ID) {
        const outerLinkId = subgraphNode.inputs[link.origin_slot].link;
        if (!outerLinkId) {
          console.error("Missing Link ID when unpacking");
          continue;
        }
        const outerLink = this.links[outerLinkId];
        link.origin_id = outerLink.origin_id;
        link.origin_slot = outerLink.origin_slot;
        externalParentId = outerLink.parentId;
      } else {
        const origin_id = nodeIdMap.get(link.origin_id);
        if (!origin_id) {
          console.error("Missing Link ID when unpacking");
          continue;
        }
        link.origin_id = origin_id;
      }
      if (link.target_id === SUBGRAPH_OUTPUT_ID) {
        for (const linkId of subgraphNode.outputs[link.target_slot].links ?? []) {
          const sublink = this.links[linkId];
          newLinks.push({
            oid: link.origin_id,
            oslot: link.origin_slot,
            tid: sublink.target_id,
            tslot: sublink.target_slot,
            id: link.id,
            iparent: link.parentId,
            eparent: sublink.parentId,
            externalFirst: true
          });
          sublink.parentId = void 0;
        }
        continue;
      } else {
        const target_id = nodeIdMap.get(link.target_id);
        if (!target_id) {
          console.error("Missing Link ID when unpacking");
          continue;
        }
        link.target_id = target_id;
      }
      newLinks.push({
        oid: link.origin_id,
        oslot: link.origin_slot,
        tid: link.target_id,
        tslot: link.target_slot,
        id: link.id,
        iparent: link.parentId,
        eparent: externalParentId,
        externalFirst: false
      });
    }
    this.remove(subgraphNode);
    this.subgraphs.delete(subgraphNode.subgraph.id);
    const linkIdMap = /* @__PURE__ */ new Map();
    for (const newLink of newLinks) {
      let created;
      if (newLink.oid == SUBGRAPH_INPUT_ID) {
        if (!(this instanceof Subgraph)) {
          console.error("Ignoring link to subgraph outside subgraph");
          continue;
        }
        const tnode = this._nodes_by_id[newLink.tid];
        created = this.inputNode.slots[newLink.oslot].connect(
          tnode.inputs[newLink.tslot],
          tnode
        );
      } else if (newLink.tid == SUBGRAPH_OUTPUT_ID) {
        if (!(this instanceof Subgraph)) {
          console.error("Ignoring link to subgraph outside subgraph");
          continue;
        }
        const tnode = this._nodes_by_id[newLink.oid];
        created = this.outputNode.slots[newLink.tslot].connect(
          tnode.outputs[newLink.oslot],
          tnode
        );
      } else {
        created = this._nodes_by_id[newLink.oid].connect(
          newLink.oslot,
          this._nodes_by_id[newLink.tid],
          newLink.tslot
        );
      }
      if (!created) {
        console.error("Failed to create link");
        continue;
      }
      const linkIds = linkIdMap.get(newLink.id) ?? [];
      linkIds.push(created.id);
      if (!linkIdMap.has(newLink.id)) {
        linkIdMap.set(newLink.id, linkIds);
      }
      newLink.id = created.id;
    }
    const rerouteIdMap = /* @__PURE__ */ new Map();
    for (const reroute of subgraphNode.subgraph.reroutes.values()) {
      if (reroute.parentId !== void 0 && rerouteIdMap.get(reroute.parentId) === void 0) {
        console.error("Missing Parent ID");
      }
      const migratedReroute = new Reroute(++this.state.lastRerouteId, this, [
        reroute.pos[0] + offsetX,
        reroute.pos[1] + offsetY
      ]);
      rerouteIdMap.set(reroute.id, migratedReroute.id);
      this.reroutes.set(migratedReroute.id, migratedReroute);
      toSelect.push(migratedReroute);
    }
    for (const newLink of newLinks) {
      const linkInstance = this.links.get(newLink.id);
      if (!linkInstance) {
        continue;
      }
      let instance = linkInstance;
      let parentId = void 0;
      if (newLink.externalFirst) {
        parentId = newLink.eparent;
        while (parentId) {
          instance.parentId = parentId;
          instance = this.reroutes.get(parentId);
          if (!instance) throw new Error("Broken Id link when unpacking");
          if (instance.linkIds.has(linkInstance.id))
            throw new Error("Infinite parentId loop");
          instance.linkIds.add(linkInstance.id);
          parentId = instance.parentId;
        }
      }
      parentId = newLink.iparent;
      while (parentId) {
        const migratedId = rerouteIdMap.get(parentId);
        if (!migratedId) throw new Error("Broken Id link when unpacking");
        instance.parentId = migratedId;
        instance = this.reroutes.get(migratedId);
        if (!instance) throw new Error("Broken Id link when unpacking");
        if (instance.linkIds.has(linkInstance.id))
          throw new Error("Infinite parentId loop");
        instance.linkIds.add(linkInstance.id);
        const oldReroute = subgraphNode.subgraph.reroutes.get(parentId);
        if (!oldReroute) throw new Error("Broken Id link when unpacking");
        parentId = oldReroute.parentId;
      }
      if (!newLink.externalFirst) {
        parentId = newLink.eparent;
        while (parentId) {
          instance.parentId = parentId;
          instance = this.reroutes.get(parentId);
          if (!instance) throw new Error("Broken Id link when unpacking");
          if (instance.linkIds.has(linkInstance.id))
            throw new Error("Infinite parentId loop");
          instance.linkIds.add(linkInstance.id);
          parentId = instance.parentId;
        }
      }
    }
    for (const nodeId of nodeIdMap.values()) {
      const node2 = this._nodes_by_id[nodeId];
      node2._setConcreteSlots();
      node2.arrange();
    }
    this.canvasAction((c2) => c2.selectItems(toSelect));
    this.afterChange();
  }
  /**
   * Resolve a path of subgraph node IDs into a list of subgraph nodes.
   * Not intended to be run from subgraphs.
   * @param nodeIds An ordered list of node IDs, from the root graph to the most nested subgraph node
   * @returns An ordered list of nested subgraph nodes.
   */
  resolveSubgraphIdPath(nodeIds) {
    const result = [];
    let currentGraph = this.rootGraph;
    for (const nodeId of nodeIds) {
      const node2 = currentGraph.getNodeById(nodeId);
      if (!node2)
        throw new Error(
          `Node [${nodeId}] not found.  ID Path: ${nodeIds.join(":")}`
        );
      if (!node2.isSubgraphNode())
        throw new Error(
          `Node [${nodeId}] is not a SubgraphNode.  ID Path: ${nodeIds.join(":")}`
        );
      result.push(node2);
      currentGraph = node2.subgraph;
    }
    return result;
  }
  /**
   * Creates a Object containing all the info about this graph, it can be serialized
   * @deprecated Use {@link asSerialisable}, which returns the newer schema version.
   * @returns value of the node
   */
  serialize(option) {
    const {
      config: config2,
      state: state2,
      groups,
      nodes,
      reroutes,
      extra,
      floatingLinks,
      definitions
    } = this.asSerialisable(option);
    const linkArray = [...this._links.values()];
    const links = linkArray.map((x2) => x2.serialize());
    if (reroutes?.length) {
      extra.linkExtensions = linkArray.filter((x2) => x2.parentId !== void 0).map((x2) => ({ id: x2.id, parentId: x2.parentId }));
    }
    extra.reroutes = reroutes?.length ? reroutes : void 0;
    return {
      id: this.id,
      revision: this.revision,
      last_node_id: state2.lastNodeId,
      last_link_id: state2.lastLinkId,
      nodes,
      links,
      floatingLinks,
      groups,
      definitions,
      config: config2,
      extra,
      version: LiteGraph.VERSION
    };
  }
  /** @returns The drag and scale state of the first attached canvas, otherwise `undefined`. */
  #getDragAndScale() {
    const ds = this.list_of_graphcanvas?.at(0)?.ds;
    if (ds) return { scale: ds.scale, offset: ds.offset };
  }
  /**
   * Prepares a shallow copy of this object for immediate serialisation or structuredCloning.
   * The return value should be discarded immediately.
   * @param options Serialise options = currently `sortNodes: boolean`, whether to sort nodes by ID.
   * @returns A shallow copy of parts of this graph, with shallow copies of its serialisable objects.
   * Mutating the properties of the return object may result in changes to your graph.
   * It is intended for use with {@link structuredClone} or {@link JSON.stringify}.
   */
  asSerialisable(options) {
    const { id: id2, revision, config: config2, state: state2 } = this;
    const nodeList = !LiteGraph.use_uuids && options?.sortNodes ? (
      // @ts-expect-error If LiteGraph.use_uuids is false, ids are numbers.
      [...this._nodes].sort((a, b) => a.id - b.id)
    ) : this._nodes;
    const nodes = nodeList.map((node2) => node2.serialize());
    const groups = this._groups.map((x2) => x2.serialize());
    const links = this._links.size ? [...this._links.values()].map((x2) => x2.asSerialisable()) : void 0;
    const floatingLinks = this.floatingLinks.size ? [...this.floatingLinks.values()].map((x2) => x2.asSerialisable()) : void 0;
    const reroutes = this.reroutes.size ? [...this.reroutes.values()].map((x2) => x2.asSerialisable()) : void 0;
    const extra = { ...this.extra };
    if (LiteGraph.saveViewportWithGraph) extra.ds = this.#getDragAndScale();
    if (!extra.ds) delete extra.ds;
    const data = {
      id: id2,
      revision,
      version: LGraph.serialisedSchemaVersion,
      config: config2,
      state: state2,
      groups,
      nodes,
      links,
      floatingLinks,
      reroutes,
      extra
    };
    if (this.isRootGraph && this._subgraphs.size) {
      const usedSubgraphIds = findUsedSubgraphIds(this, this._subgraphs);
      const usedSubgraphs = [...this._subgraphs.values()].filter((subgraph) => usedSubgraphIds.has(subgraph.id)).map((x2) => x2.asSerialisable());
      if (usedSubgraphs.length > 0) {
        data.definitions = { subgraphs: usedSubgraphs };
      }
    }
    this.onSerialize?.(data);
    return data;
  }
  _configureBase(data) {
    const { id: id2, extra } = data;
    if (id2) {
      this.id = id2;
    } else if (this.id === zeroUuid) {
      this.id = createUuidv4();
    }
    this.extra = extra ? structuredClone(extra) : {};
    delete this.extra.linkExtensions;
  }
  /**
   * Configure a graph from a JSON string
   * @param data The deserialised object to configure this graph from
   * @param keep_old If `true`, the graph will not be cleared prior to
   * adding the configuration.
   */
  configure(data, keep_old) {
    const layoutMutations2 = useLayoutMutations();
    const options = {
      data,
      clearGraph: !keep_old
    };
    const mayContinue = this.events.dispatch("configuring", options);
    if (!mayContinue) return;
    try {
      if (!data) return;
      if (options.clearGraph) this.clear();
      this._configureBase(data);
      let reroutes;
      if (data.version === 0.4) {
        const { extra } = data;
        if (Array.isArray(data.links)) {
          for (const linkData of data.links) {
            const link = LLink.createFromArray(linkData);
            this._links.set(link.id, link);
          }
        }
        if (Array.isArray(extra?.linkExtensions)) {
          for (const linkEx of extra.linkExtensions) {
            const link = this._links.get(linkEx.id);
            if (link) link.parentId = linkEx.parentId;
          }
        }
        reroutes = extra?.reroutes;
      } else {
        if (data.state) {
          const { lastGroupId, lastLinkId, lastNodeId, lastRerouteId } = data.state;
          const { state: state2 } = this;
          if (lastGroupId != null) state2.lastGroupId = lastGroupId;
          if (lastLinkId != null) state2.lastLinkId = lastLinkId;
          if (lastNodeId != null) state2.lastNodeId = lastNodeId;
          if (lastRerouteId != null) state2.lastRerouteId = lastRerouteId;
        }
        if (Array.isArray(data.links)) {
          for (const linkData of data.links) {
            const link = LLink.create(linkData);
            this._links.set(link.id, link);
          }
        }
        reroutes = data.reroutes;
      }
      if (Array.isArray(reroutes)) {
        for (const rerouteData of reroutes) {
          this.setReroute(rerouteData);
        }
      }
      const nodesData = data.nodes;
      for (const i in data) {
        if (LGraph.ConfigureProperties.has(i)) continue;
        this[i] = data[i];
      }
      const subgraphs = data.definitions?.subgraphs;
      if (subgraphs) {
        for (const subgraph of subgraphs) this.createSubgraph(subgraph);
        for (const subgraph of subgraphs)
          this.subgraphs.get(subgraph.id)?.configure(subgraph);
      }
      let error = false;
      const nodeDataMap = /* @__PURE__ */ new Map();
      this._nodes = [];
      if (nodesData) {
        for (const n_info of nodesData) {
          let node2 = LiteGraph.createNode(String(n_info.type), n_info.title);
          if (!node2) {
            if (LiteGraph.debug)
              console.warn("Node not found or has errors:", n_info.type);
            node2 = new LGraphNode("");
            node2.last_serialization = n_info;
            node2.has_errors = true;
            error = true;
          }
          node2.id = n_info.id;
          this.add(node2, true);
          nodeDataMap.set(node2.id, n_info);
        }
        for (const [id2, nodeData] of nodeDataMap) {
          this.getNodeById(id2)?.configure(nodeData);
        }
      }
      if (Array.isArray(data.floatingLinks)) {
        for (const linkData of data.floatingLinks) {
          const floatingLink = LLink.create(linkData);
          this.addFloatingLink(floatingLink);
          if (floatingLink.id > this.#lastFloatingLinkId)
            this.#lastFloatingLinkId = floatingLink.id;
        }
      }
      for (const reroute of this.reroutes.values()) {
        if (!reroute.validateLinks(this._links, this.floatingLinks)) {
          this.reroutes.delete(reroute.id);
          layoutMutations2.setSource(LayoutSource.Canvas);
          layoutMutations2.deleteReroute(reroute.id);
        }
      }
      this._groups.length = 0;
      const groupData = data.groups;
      if (groupData) {
        for (const data2 of groupData) {
          const group = new LiteGraph.LGraphGroup();
          group.configure(data2);
          this.add(group);
        }
      }
      this.updateExecutionOrder();
      this.onConfigure?.(data);
      this._version++;
      const { primaryCanvas } = this;
      const subgraphId = primaryCanvas?.subgraph?.id;
      if (subgraphId) {
        const subgraph = this.subgraphs.get(subgraphId);
        if (subgraph) {
          primaryCanvas.setGraph(subgraph);
        } else {
          primaryCanvas.setGraph(this);
        }
      }
      this.setDirtyCanvas(true, true);
      return error;
    } finally {
      this.events.dispatch("configured");
    }
  }
  #canvas;
  get primaryCanvas() {
    return this.rootGraph.#canvas;
  }
  set primaryCanvas(canvas2) {
    this.rootGraph.#canvas = canvas2;
  }
  load(url, callback) {
    if (url instanceof Blob || url instanceof File) {
      const reader = new FileReader();
      reader.addEventListener("load", (event) => {
        const result = toString(event.target?.result);
        const data = JSON.parse(result);
        this.configure(data);
        callback?.();
      });
      reader.readAsText(url);
      return;
    }
    const req = new XMLHttpRequest();
    req.open("GET", url, true);
    req.send(null);
    req.addEventListener("load", () => {
      if (req.status !== 200) {
        console.error("Error loading graph:", req.status, req.response);
        return;
      }
      const data = JSON.parse(req.response);
      this.configure(data);
      callback?.();
    });
    req.addEventListener("error", (err) => {
      console.error("Error loading graph:", err);
    });
  }
}
class Subgraph extends LGraph {
  static {
    __name(this, "Subgraph");
  }
  events = new CustomEventTarget();
  /** Limits the number of levels / depth that subgraphs may be nested.  Prevents uncontrolled programmatic nesting. */
  static MAX_NESTED_SUBGRAPHS = 1e3;
  /** The display name of the subgraph. */
  name = "Unnamed Subgraph";
  inputNode = new SubgraphInputNode(this);
  outputNode = new SubgraphOutputNode(this);
  /** Ordered list of inputs to the subgraph itself. Similar to a reroute, with the input side in the graph, and the output side in the subgraph. */
  inputs = [];
  /** Ordered list of outputs from the subgraph itself. Similar to a reroute, with the input side in the subgraph, and the output side in the graph. */
  outputs = [];
  /** A list of node widgets displayed in the parent graph, on the subgraph object. */
  widgets = [];
  #rootGraph;
  get rootGraph() {
    return this.#rootGraph;
  }
  constructor(rootGraph, data) {
    if (!rootGraph) throw new Error("Root graph is required");
    super();
    this.#rootGraph = rootGraph;
    const cloned = structuredClone(data);
    this._configureBase(cloned);
    this.#configureSubgraph(cloned);
  }
  getIoNodeOnPos(x2, y) {
    const { inputNode, outputNode } = this;
    if (inputNode.containsPoint([x2, y])) return inputNode;
    if (outputNode.containsPoint([x2, y])) return outputNode;
  }
  #configureSubgraph(data) {
    const { name: name2, inputs, outputs, widgets: widgets2 } = data;
    this.name = name2;
    if (inputs) {
      this.inputs.length = 0;
      for (const input of inputs) {
        const subgraphInput = new SubgraphInput(input, this.inputNode);
        this.inputs.push(subgraphInput);
        this.events.dispatch("input-added", { input: subgraphInput });
      }
    }
    if (outputs) {
      this.outputs.length = 0;
      for (const output of outputs) {
        this.outputs.push(new SubgraphOutput(output, this.outputNode));
      }
    }
    if (widgets2) {
      this.widgets.length = 0;
      for (const widget of widgets2) {
        this.widgets.push(widget);
      }
    }
    this.inputNode.configure(data.inputNode);
    this.outputNode.configure(data.outputNode);
  }
  configure(data, keep_old) {
    const r = super.configure(data, keep_old);
    this.#configureSubgraph(data);
    return r;
  }
  attachCanvas(canvas2) {
    super.attachCanvas(canvas2);
    canvas2.subgraph = this;
  }
  addInput(name2, type) {
    this.events.dispatch("adding-input", { name: name2, type });
    const input = new SubgraphInput(
      {
        id: createUuidv4(),
        name: name2,
        type
      },
      this.inputNode
    );
    this.inputs.push(input);
    this.events.dispatch("input-added", { input });
    return input;
  }
  addOutput(name2, type) {
    this.events.dispatch("adding-output", { name: name2, type });
    const output = new SubgraphOutput(
      {
        id: createUuidv4(),
        name: name2,
        type
      },
      this.outputNode
    );
    this.outputs.push(output);
    this.events.dispatch("output-added", { output });
    return output;
  }
  /**
   * Renames an input slot in the subgraph.
   * @param input The input slot to rename.
   * @param name The new name for the input slot.
   */
  renameInput(input, name2) {
    const index2 = this.inputs.indexOf(input);
    if (index2 === -1) throw new Error("Input not found");
    const oldName = input.displayName;
    this.events.dispatch("renaming-input", {
      input,
      index: index2,
      oldName,
      newName: name2
    });
    input.label = name2;
  }
  /**
   * Renames an output slot in the subgraph.
   * @param output The output slot to rename.
   * @param name The new name for the output slot.
   */
  renameOutput(output, name2) {
    const index2 = this.outputs.indexOf(output);
    if (index2 === -1) throw new Error("Output not found");
    const oldName = output.displayName;
    this.events.dispatch("renaming-output", {
      output,
      index: index2,
      oldName,
      newName: name2
    });
    output.label = name2;
  }
  /**
   * Removes an input slot from the subgraph.
   * @param input The input slot to remove.
   */
  removeInput(input) {
    input.disconnect();
    const index2 = this.inputs.indexOf(input);
    if (index2 === -1) throw new Error("Input not found");
    const mayContinue = this.events.dispatch("removing-input", { input, index: index2 });
    if (!mayContinue) return;
    this.inputs.splice(index2, 1);
    const { length } = this.inputs;
    for (let i = index2; i < length; i++) {
      this.inputs[i].decrementSlots("inputs");
    }
  }
  /**
   * Removes an output slot from the subgraph.
   * @param output The output slot to remove.
   */
  removeOutput(output) {
    output.disconnect();
    const index2 = this.outputs.indexOf(output);
    if (index2 === -1) throw new Error("Output not found");
    const mayContinue = this.events.dispatch("removing-output", {
      output,
      index: index2
    });
    if (!mayContinue) return;
    this.outputs.splice(index2, 1);
    const { length } = this.outputs;
    for (let i = index2; i < length; i++) {
      this.outputs[i].decrementSlots("outputs");
    }
  }
  draw(ctx, colorContext, fromSlot, editorAlpha) {
    this.inputNode.draw(ctx, colorContext, fromSlot, editorAlpha);
    this.outputNode.draw(ctx, colorContext, fromSlot, editorAlpha);
  }
  /**
   * Clones the subgraph, creating an identical copy with a new ID.
   * @returns A new subgraph with the same configuration, but a new ID.
   */
  clone(keepId = false) {
    const exported = this.asSerialisable();
    if (!keepId) exported.id = createUuidv4();
    const subgraph = new Subgraph(this.rootGraph, exported);
    subgraph.configure(exported);
    return subgraph;
  }
  asSerialisable() {
    return {
      id: this.id,
      version: LGraph.serialisedSchemaVersion,
      state: this.state,
      revision: this.revision,
      config: this.config,
      name: this.name,
      inputNode: this.inputNode.asSerialisable(),
      outputNode: this.outputNode.asSerialisable(),
      inputs: this.inputs.map((x2) => x2.asSerialisable()),
      outputs: this.outputs.map((x2) => x2.asSerialisable()),
      widgets: [...this.widgets],
      nodes: this.nodes.map((node2) => node2.serialize()),
      groups: this.groups.map((group) => group.serialize()),
      links: [...this.links.values()].map((x2) => x2.asSerialisable()),
      reroutes: this.reroutes.size ? [...this.reroutes.values()].map((x2) => x2.asSerialisable()) : void 0,
      extra: this.extra
    };
  }
}
class InputIndicators {
  static {
    __name(this, "InputIndicators");
  }
  constructor(canvas2) {
    this.canvas = canvas2;
    this.controller = new AbortController();
    const { signal } = this.controller;
    const element = canvas2.canvas;
    const options = { capture: true, signal };
    element.addEventListener("pointerdown", this.#onPointerDownOrMove, options);
    element.addEventListener("pointermove", this.#onPointerDownOrMove, options);
    element.addEventListener("pointerup", this.#onPointerUp, options);
    element.addEventListener("keydown", this.#onKeyDownOrUp, options);
    document.addEventListener("keyup", this.#onKeyDownOrUp, options);
    const origDrawFrontCanvas = canvas2.drawFrontCanvas.bind(canvas2);
    signal.addEventListener("abort", () => {
      canvas2.drawFrontCanvas = origDrawFrontCanvas;
    });
    canvas2.drawFrontCanvas = () => {
      origDrawFrontCanvas();
      this.draw();
    };
  }
  // #region config
  radius = 8;
  startAngle = 0;
  endAngle = Math.PI * 2;
  inactiveColour = "#ffffff10";
  colour1 = "#ff5f00";
  colour2 = "#00ff7c";
  colour3 = "#dea7ff";
  fontString = "bold 12px Inter, sans-serif";
  // #endregion
  // #region state
  enabled = true;
  shiftDown = false;
  undoDown = false;
  redoDown = false;
  ctrlDown = false;
  altDown = false;
  mouse0Down = false;
  mouse1Down = false;
  mouse2Down = false;
  x = 0;
  y = 0;
  // #endregion
  controller;
  #onPointerDownOrMove = this.onPointerDownOrMove.bind(this);
  onPointerDownOrMove(e2) {
    this.mouse0Down = (e2.buttons & 1) === 1;
    this.mouse1Down = (e2.buttons & 4) === 4;
    this.mouse2Down = (e2.buttons & 2) === 2;
    this.x = e2.clientX;
    this.y = e2.clientY;
    this.canvas.setDirty(true);
  }
  #onPointerUp = this.onPointerUp.bind(this);
  onPointerUp() {
    this.mouse0Down = false;
    this.mouse1Down = false;
    this.mouse2Down = false;
  }
  #onKeyDownOrUp = this.onKeyDownOrUp.bind(this);
  onKeyDownOrUp(e2) {
    this.ctrlDown = e2.ctrlKey;
    this.altDown = e2.altKey;
    this.shiftDown = e2.shiftKey;
    this.undoDown = e2.ctrlKey && e2.code === "KeyZ" && e2.type === "keydown";
    this.redoDown = e2.ctrlKey && e2.code === "KeyY" && e2.type === "keydown";
  }
  draw() {
    const {
      canvas: { ctx },
      radius,
      startAngle,
      endAngle,
      x: x2,
      y,
      inactiveColour,
      colour1,
      colour2,
      colour3,
      fontString
    } = this;
    const { fillStyle, font } = ctx;
    const mouseDotX = x2;
    const mouseDotY = y - 80;
    const textX = mouseDotX;
    const textY = mouseDotY - 15;
    ctx.font = fontString;
    textMarker(
      textX + 0,
      textY,
      "Shift",
      this.shiftDown ? colour1 : inactiveColour
    );
    textMarker(
      textX + 45,
      textY + 20,
      "Alt",
      this.altDown ? colour2 : inactiveColour
    );
    textMarker(
      textX + 30,
      textY,
      "Control",
      this.ctrlDown ? colour3 : inactiveColour
    );
    textMarker(textX - 30, textY, "↩️", this.undoDown ? "#000" : "transparent");
    textMarker(textX + 45, textY, "↪️", this.redoDown ? "#000" : "transparent");
    ctx.beginPath();
    drawDot(mouseDotX, mouseDotY);
    drawDot(mouseDotX + 15, mouseDotY);
    drawDot(mouseDotX + 30, mouseDotY);
    ctx.fillStyle = inactiveColour;
    ctx.fill();
    const leftButtonColour = this.mouse0Down ? colour1 : inactiveColour;
    const middleButtonColour = this.mouse1Down ? colour2 : inactiveColour;
    const rightButtonColour = this.mouse2Down ? colour3 : inactiveColour;
    if (this.mouse0Down) mouseMarker(mouseDotX, mouseDotY, leftButtonColour);
    if (this.mouse1Down)
      mouseMarker(mouseDotX + 15, mouseDotY, middleButtonColour);
    if (this.mouse2Down)
      mouseMarker(mouseDotX + 30, mouseDotY, rightButtonColour);
    ctx.fillStyle = fillStyle;
    ctx.font = font;
    function textMarker(x22, y2, text, colour) {
      ctx.fillStyle = colour;
      ctx.fillText(text, x22, y2);
    }
    __name(textMarker, "textMarker");
    function mouseMarker(x22, y2, colour) {
      ctx.beginPath();
      ctx.fillStyle = colour;
      drawDot(x22, y2);
      ctx.fill();
    }
    __name(mouseMarker, "mouseMarker");
    function drawDot(x22, y2) {
      ctx.arc(x22, y2, radius, startAngle, endAngle);
    }
    __name(drawDot, "drawDot");
  }
  dispose() {
    this.controller?.abort();
    this.controller = void 0;
  }
  [Symbol.dispose]() {
    this.dispose();
  }
}
class LiteGraphGlobal {
  static {
    __name(this, "LiteGraphGlobal");
  }
  // Enums
  SlotShape = SlotShape;
  SlotDirection = SlotDirection;
  SlotType = SlotType;
  LabelPosition = LabelPosition;
  /** Used in serialised graphs at one point. */
  VERSION = 0.4;
  CANVAS_GRID_SIZE = 10;
  NODE_TITLE_HEIGHT = 30;
  NODE_TITLE_TEXT_Y = 20;
  NODE_SLOT_HEIGHT = 20;
  NODE_WIDGET_HEIGHT = 20;
  NODE_WIDTH = 140;
  NODE_MIN_WIDTH = 50;
  NODE_COLLAPSED_RADIUS = 10;
  NODE_COLLAPSED_WIDTH = 80;
  NODE_TITLE_COLOR = "#999";
  NODE_SELECTED_TITLE_COLOR = "#FFF";
  NODE_TEXT_SIZE = 14;
  NODE_TEXT_COLOR = "#AAA";
  NODE_TEXT_HIGHLIGHT_COLOR = "#EEE";
  NODE_SUBTEXT_SIZE = 12;
  NODE_DEFAULT_COLOR = "#333";
  NODE_DEFAULT_BGCOLOR = "#353535";
  NODE_DEFAULT_BOXCOLOR = "#666";
  NODE_DEFAULT_SHAPE = RenderShape.ROUND;
  NODE_BOX_OUTLINE_COLOR = "#FFF";
  NODE_ERROR_COLOUR = "#E00";
  NODE_FONT = "Inter";
  NODE_DEFAULT_BYPASS_COLOR = "#FF00FF";
  NODE_OPACITY = 0.9;
  DEFAULT_FONT = "Inter";
  DEFAULT_SHADOW_COLOR = "rgba(0,0,0,0.5)";
  DEFAULT_GROUP_FONT = 24;
  DEFAULT_GROUP_FONT_SIZE;
  GROUP_FONT = "Inter";
  WIDGET_BGCOLOR = "#222";
  WIDGET_OUTLINE_COLOR = "#666";
  WIDGET_PROMOTED_OUTLINE_COLOR = "#BF00FF";
  WIDGET_ADVANCED_OUTLINE_COLOR = "rgba(56, 139, 253, 0.8)";
  WIDGET_TEXT_COLOR = "#DDD";
  WIDGET_SECONDARY_TEXT_COLOR = "#999";
  WIDGET_DISABLED_TEXT_COLOR = "#666";
  LINK_COLOR = "#9A9";
  EVENT_LINK_COLOR = "#A86";
  CONNECTING_LINK_COLOR = "#AFA";
  /** avoid infinite loops */
  MAX_NUMBER_OF_NODES = 1e4;
  /** default node position */
  DEFAULT_POSITION = [100, 100];
  /** ,"circle" */
  VALID_SHAPES = ["default", "box", "round", "card"];
  ROUND_RADIUS = 8;
  // shapes are used for nodes but also for slots
  BOX_SHAPE = RenderShape.BOX;
  ROUND_SHAPE = RenderShape.ROUND;
  CIRCLE_SHAPE = RenderShape.CIRCLE;
  CARD_SHAPE = RenderShape.CARD;
  ARROW_SHAPE = RenderShape.ARROW;
  /** intended for slot arrays */
  GRID_SHAPE = RenderShape.GRID;
  // enums
  INPUT = NodeSlotType.INPUT;
  OUTPUT = NodeSlotType.OUTPUT;
  // TODO: -1 can lead to ambiguity in JS; these should be updated to a more explicit constant or Symbol.
  /** for outputs */
  EVENT = -1;
  /** for inputs */
  ACTION = -1;
  /** helper, will add "On Request" and more in the future */
  NODE_MODES = ["Always", "On Event", "Never", "On Trigger"];
  /** use with node_box_coloured_by_mode */
  NODE_MODES_COLORS = ["#666", "#422", "#333", "#224", "#626"];
  ALWAYS = LGraphEventMode.ALWAYS;
  ON_EVENT = LGraphEventMode.ON_EVENT;
  NEVER = LGraphEventMode.NEVER;
  ON_TRIGGER = LGraphEventMode.ON_TRIGGER;
  UP = LinkDirection.UP;
  DOWN = LinkDirection.DOWN;
  LEFT = LinkDirection.LEFT;
  RIGHT = LinkDirection.RIGHT;
  CENTER = LinkDirection.CENTER;
  /** helper */
  LINK_RENDER_MODES = ["Straight", "Linear", "Spline"];
  HIDDEN_LINK = LinkRenderType.HIDDEN_LINK;
  STRAIGHT_LINK = LinkRenderType.STRAIGHT_LINK;
  LINEAR_LINK = LinkRenderType.LINEAR_LINK;
  SPLINE_LINK = LinkRenderType.SPLINE_LINK;
  NORMAL_TITLE = TitleMode.NORMAL_TITLE;
  NO_TITLE = TitleMode.NO_TITLE;
  TRANSPARENT_TITLE = TitleMode.TRANSPARENT_TITLE;
  AUTOHIDE_TITLE = TitleMode.AUTOHIDE_TITLE;
  /** arrange nodes vertically */
  VERTICAL_LAYOUT = "vertical";
  /** used to redirect calls */
  proxy = null;
  node_images_path = "";
  debug = false;
  catch_exceptions = true;
  throw_errors = true;
  /** if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits */
  allow_scripts = false;
  /** nodetypes by string */
  registered_node_types = {};
  /** @deprecated used for dropping files in the canvas.  It appears the code that enables this was removed, but the object remains and is references by built-in drag drop. */
  node_types_by_file_extension = {};
  /** node types by classname */
  Nodes = {};
  /** used to store vars between graphs */
  Globals = {};
  /** @deprecated Unused and will be deleted. */
  searchbox_extras = {};
  /** [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback */
  node_box_coloured_when_on = false;
  /** [true!] nodebox based on node mode, visual feedback */
  node_box_coloured_by_mode = false;
  /** [false on mobile] better true if not touch device, TODO add an helper/listener to close if false */
  dialog_close_on_mouse_leave = false;
  dialog_close_on_mouse_leave_delay = 500;
  /** [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys */
  shift_click_do_break_link_from = false;
  /** [false!]prefer false, way too easy to break links */
  click_do_break_link_to = false;
  /** [true!] who accidentally ctrl-alt-clicks on an in/output? nobody! that's who! */
  ctrl_alt_click_do_break_link = true;
  /** [true!] snaps links when dragging connections over valid targets */
  snaps_for_comfy = true;
  /** [true!] renders a partial border to highlight when a dragged link is snapped to a node */
  snap_highlights_node = true;
  /**
   * If `true`, items always snap to the grid - modifier keys are ignored.
   * When {@link snapToGrid} is falsy, a value of `1` is used.
   * Default: `false`
   */
  alwaysSnapToGrid;
  /**
   * When set to a positive number, when nodes are moved their positions will
   * be rounded to the nearest multiple of this value.  Half up.
   * Default: `undefined`
   * @todo Not implemented - see {@link LiteGraph.CANVAS_GRID_SIZE}
   */
  snapToGrid;
  /** [false on mobile] better true if not touch device, TODO add an helper/listener to close if false */
  search_hide_on_mouse_leave = true;
  /**
   * [true!] enable filtering slots type in the search widget
   * !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out]
   */
  search_filter_enabled = false;
  /** [true!] opens the results list when opening the search widget */
  search_show_all_on_open = true;
  /**
   * [if want false, use true, run, get vars values to be statically set, than disable]
   * nodes types and nodeclass association with node types need to be calculated,
   * if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out]
   */
  auto_load_slot_types = false;
  // set these values if not using auto_load_slot_types
  /** slot types for nodeclass */
  registered_slot_in_types = {};
  /** slot types for nodeclass */
  registered_slot_out_types = {};
  /** slot types IN */
  slot_types_in = [];
  /** slot types OUT */
  slot_types_out = [];
  /**
   * specify for each IN slot type a(/many) default node(s), use single string, array, or object
   * (with node, title, parameters, ..) like for search
   */
  slot_types_default_in = {};
  /**
   * specify for each OUT slot type a(/many) default node(s), use single string, array, or object
   * (with node, title, parameters, ..) like for search
   */
  slot_types_default_out = {};
  /** [true!] very handy, ALT click to clone and drag the new node */
  alt_drag_do_clone_nodes = false;
  /**
   * [true!] will create and connect event slots when using action/events connections,
   * !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this
   */
  do_add_triggers_slots = false;
  /** [false!] being events, it is strongly recommended to use them sequentially, one by one */
  allow_multi_output_for_events = true;
  /** [true!] allows to create and connect a node clicking with the third button (wheel) */
  middle_click_slot_add_default_node = false;
  /** [true!] dragging a link to empty space will open a menu, add from list, search or defaults */
  release_link_on_empty_shows_menu = false;
  /** "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) */
  pointerevents_method = "pointer";
  /**
   * [true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected
   * with the inputs of the newly pasted nodes
   */
  ctrl_shift_v_paste_connect_unselected_outputs = true;
  // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers.
  // use this if you must have node IDs that are unique across all graphs and subgraphs.
  use_uuids = false;
  // Whether to highlight the bounding box of selected groups
  highlight_selected_group = true;
  /** Whether to scale context with the graph when zooming in.  Zooming out never makes context menus smaller. */
  context_menu_scaling = false;
  /**
   * Debugging flag. Repeats deprecation warnings every time they are reported.
   * May impact performance.
   */
  alwaysRepeatWarnings = false;
  /**
   * Array of callbacks to execute when Litegraph first reports a deprecated API being used.
   * @see alwaysRepeatWarnings By default, will not repeat identical messages.
   */
  onDeprecationWarning = [
    console.warn
  ];
  /**
   * @deprecated Removed; has no effect.
   * If `true`, mouse wheel events will be interpreted as trackpad gestures.
   * Tested on MacBook M4 Pro.
   * @default false
   * @see macGesturesRequireMac
   */
  macTrackpadGestures = false;
  /**
   * @deprecated Removed; has no effect.
   * If both this setting and {@link macTrackpadGestures} are `true`, trackpad gestures will
   * only be enabled when the browser user agent includes "Mac".
   * @default true
   * @see macTrackpadGestures
   */
  macGesturesRequireMac = true;
  /**
   * "standard": change the dragging on left mouse button click to select, enable middle-click or spacebar+left-click dragging
   * "legacy": Enable dragging on left-click (original behavior)
   * "custom": Use leftMouseClickBehavior and mouseWheelScroll settings
   * @default "legacy"
   */
  canvasNavigationMode = "legacy";
  leftMouseClickBehavior = "panning";
  mouseWheelScroll = "panning";
  /**
   * If `true`, widget labels and values will both be truncated (proportionally to size),
   * until they fit within the widget.
   *
   * Otherwise, the label will be truncated completely before the value is truncated.
   * @default false
   */
  truncateWidgetTextEvenly = false;
  /**
   * If `true`, widget values will be completely truncated when shrinking a widget,
   * before truncating widget labels.  {@link truncateWidgetTextEvenly} must be `false`.
   * @default false
   */
  truncateWidgetValuesFirst = false;
  /**
   * If `true`, the current viewport scale & offset of the first attached canvas will be included with the graph when exporting.
   * @default true
   */
  saveViewportWithGraph = true;
  /**
   * Enable Vue nodes mode for rendering and positioning.
   * When true:
   * - Nodes will calculate slot positions using Vue component dimensions
   * - LiteGraph will skip rendering node bodies entirely
   * - Vue components will handle all node rendering
   * - LiteGraph continues to render connections, links, and graph background
   * This should be set by the frontend when the Vue nodes feature is enabled.
   * @default false
   */
  vueNodesMode = false;
  // Special Rendering Values pulled out of app.ts patches
  nodeOpacity = 1;
  nodeLightness = void 0;
  // TODO: Remove legacy accessors
  LGraph = LGraph;
  LLink = LLink;
  LGraphNode = LGraphNode;
  LGraphGroup = LGraphGroup;
  DragAndScale = DragAndScale;
  LGraphCanvas = LGraphCanvas;
  ContextMenu = ContextMenu;
  CurveEditor = CurveEditor;
  Reroute = Reroute;
  constructor() {
    Object.defineProperty(this, "Classes", { writable: false });
  }
  Classes = {
    get SubgraphSlot() {
      return SubgraphSlot;
    },
    get SubgraphIONodeBase() {
      return SubgraphIONodeBase;
    },
    // Rich drawing
    get Rectangle() {
      return Rectangle;
    },
    // Debug / helpers
    get InputIndicators() {
      return InputIndicators;
    }
  };
  /**
   * Register a node class so it can be listed when the user wants to create a new one
   * @param type name of the node and path
   * @param base_class class containing the structure of a node
   */
  registerNodeType(type, base_class) {
    if (!base_class.prototype)
      throw "Cannot register a simple object, it must be a class with a prototype";
    base_class.type = type;
    const classname = base_class.name;
    const pos = type.lastIndexOf("/");
    base_class.category = type.substring(0, pos);
    base_class.title ||= classname;
    for (const i in LGraphNode.prototype) {
      base_class.prototype[i] ||= LGraphNode.prototype[i];
    }
    const prev = this.registered_node_types[type];
    if (prev && this.debug) {
      console.warn("replacing node type:", type);
    }
    this.registered_node_types[type] = base_class;
    if (base_class.constructor.name) this.Nodes[classname] = base_class;
    this.onNodeTypeRegistered?.(type, base_class);
    if (prev) this.onNodeTypeReplaced?.(type, base_class, prev);
    if (base_class.prototype.onPropertyChange)
      console.warn(
        `LiteGraph node class ${type} has onPropertyChange method, it must be called onPropertyChanged with d at the end`
      );
    if (this.auto_load_slot_types) new base_class(base_class.title || "tmpnode");
  }
  /**
   * removes a node type from the system
   * @param type name of the node or the node constructor itself
   */
  unregisterNodeType(type) {
    const base_class = typeof type === "string" ? this.registered_node_types[type] : type;
    if (!base_class) throw `node type not found: ${String(type)}`;
    delete this.registered_node_types[String(base_class.type)];
    const name2 = base_class.constructor.name;
    if (name2) delete this.Nodes[name2];
  }
  /**
   * Save a slot type and his node
   * @param type name of the node or the node constructor itself
   * @param slot_type name of the slot type (variable type), eg. string, number, array, boolean, ..
   */
  registerNodeAndSlotType(type, slot_type, out) {
    out ||= false;
    const base_class = typeof type === "string" && // @ts-expect-error Confirm this function no longer supports string types - base_class should always be an instance not a constructor.
    this.registered_node_types[type] !== "anonymous" ? this.registered_node_types[type] : type;
    const class_type = base_class.constructor.type;
    let allTypes = [];
    if (typeof slot_type === "string") {
      allTypes = slot_type.split(",");
    } else if (slot_type == this.EVENT || slot_type == this.ACTION) {
      allTypes = ["_event_"];
    } else {
      allTypes = ["*"];
    }
    for (let slotType of allTypes) {
      if (slotType === "") slotType = "*";
      const register = out ? this.registered_slot_out_types : this.registered_slot_in_types;
      register[slotType] ??= { nodes: [] };
      const { nodes } = register[slotType];
      if (!nodes.includes(class_type)) nodes.push(class_type);
      const types = out ? this.slot_types_out : this.slot_types_in;
      const type2 = slotType.toLowerCase();
      if (!types.includes(type2)) {
        types.push(type2);
        types.sort();
      }
    }
  }
  /**
   * Removes all previously registered node's types
   */
  clearRegisteredTypes() {
    this.registered_node_types = {};
    this.node_types_by_file_extension = {};
    this.Nodes = {};
    this.searchbox_extras = {};
  }
  /**
   * Create a node of a given type with a name. The node is not attached to any graph yet.
   * @param type full name of the node class. p.e. "math/sin"
   * @param title a name to distinguish from other nodes
   * @param options to set options
   */
  createNode(type, title, options) {
    const base_class = this.registered_node_types[type];
    if (!base_class) {
      if (this.debug) console.warn(`GraphNode type "${type}" not registered.`);
      return null;
    }
    title = title || base_class.title || type;
    let node2 = null;
    if (this.catch_exceptions) {
      try {
        node2 = new base_class(title);
      } catch (error) {
        console.error(error);
        return null;
      }
    } else {
      node2 = new base_class(title);
    }
    node2.type = type;
    if (!node2.title && title) node2.title = title;
    node2.properties ||= {};
    node2.properties_info ||= [];
    node2.flags ||= {};
    node2.size ||= node2.computeSize();
    node2.pos ||= [this.DEFAULT_POSITION[0], this.DEFAULT_POSITION[1]];
    node2.mode ||= LGraphEventMode.ALWAYS;
    if (options) {
      for (const i in options) {
        node2[i] = options[i];
      }
    }
    node2.onNodeCreated?.();
    return node2;
  }
  /**
   * Returns a registered node type with a given name
   * @param type full name of the node class. p.e. "math/sin"
   * @returns the node class
   */
  getNodeType(type) {
    return this.registered_node_types[type];
  }
  /**
   * Returns a list of node types matching one category
   * @param category category name
   * @returns array with all the node classes
   */
  getNodeTypesInCategory(category, filter2) {
    const r = [];
    for (const i in this.registered_node_types) {
      const type = this.registered_node_types[i];
      if (type.filter != filter2) continue;
      if (category == "") {
        if (type.category == null) r.push(type);
      } else if (type.category == category) {
        r.push(type);
      }
    }
    return r;
  }
  /**
   * Returns a list with all the node type categories
   * @param filter only nodes with ctor.filter equal can be shown
   * @returns array with all the names of the categories
   */
  getNodeTypesCategories(filter2) {
    const categories = { "": 1 };
    for (const i in this.registered_node_types) {
      const type = this.registered_node_types[i];
      if (type.category && !type.skip_list) {
        if (type.filter != filter2) continue;
        categories[type.category] = 1;
      }
    }
    const result = [];
    for (const i in categories) {
      result.push(i);
    }
    return result;
  }
  // debug purposes: reloads all the js scripts that matches a wildcard
  reloadNodes(folder_wildcard) {
    const tmp = document.getElementsByTagName("script");
    const script_files = [];
    for (const element of tmp) {
      script_files.push(element);
    }
    const docHeadObj = document.getElementsByTagName("head")[0];
    folder_wildcard = document.location.href + folder_wildcard;
    for (const script_file of script_files) {
      const src = script_file.src;
      if (!src || src.substr(0, folder_wildcard.length) != folder_wildcard)
        continue;
      try {
        const dynamicScript = document.createElement("script");
        dynamicScript.type = "text/javascript";
        dynamicScript.src = src;
        docHeadObj.append(dynamicScript);
        script_file.remove();
      } catch (error) {
        if (this.throw_errors) throw error;
        if (this.debug) console.error("Error while reloading", src);
      }
    }
  }
  // separated just to improve if it doesn't work
  /** @deprecated Prefer {@link structuredClone} */
  cloneObject(obj, target) {
    if (obj == null) return null;
    const r = JSON.parse(JSON.stringify(obj));
    if (!target) return r;
    for (const i in r) {
      target[i] = r[i];
    }
    return target;
  }
  /** @see {@link createUuidv4} @inheritdoc */
  uuidv4 = createUuidv4;
  /**
   * Returns if the types of two slots are compatible (taking into account wildcards, etc)
   * @param type_a output
   * @param type_b input
   * @returns true if they can be connected
   */
  isValidConnection(type_a, type_b) {
    if (type_a == "" || type_a === "*") type_a = 0;
    if (type_b == "" || type_b === "*") type_b = 0;
    if (!type_a || !type_b || type_a == type_b || type_a == this.EVENT && type_b == this.ACTION) {
      return true;
    }
    type_a = String(type_a);
    type_b = String(type_b);
    type_a = type_a.toLowerCase();
    type_b = type_b.toLowerCase();
    if (!type_a.includes(",") && !type_b.includes(",")) return type_a == type_b;
    const supported_types_a = type_a.split(",");
    const supported_types_b = type_b.split(",");
    for (const a of supported_types_a) {
      for (const b of supported_types_b) {
        if (this.isValidConnection(a, b)) return true;
      }
    }
    return false;
  }
  // used to create nodes from wrapping functions
  getParameterNames(func) {
    return String(func).replaceAll(/\/\/.*$/gm, "").replaceAll(/\s+/g, "").replaceAll(/\/\*[^*/]*\*\//g, "").split("){", 1)[0].replace(/^[^(]*\(/, "").replaceAll(/=[^,]+/g, "").split(",").filter(Boolean);
  }
  /* helper for interaction: pointer, touch, mouse Listeners
    used by LGraphCanvas DragAndScale ContextMenu */
  pointerListenerAdd(oDOM, sEvIn, fCall, capture = false) {
    if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall !== "function")
      return;
    let sMethod = this.pointerevents_method;
    let sEvent = sEvIn;
    if (sMethod == "pointer" && !window.PointerEvent) {
      console.warn("sMethod=='pointer' && !window.PointerEvent");
      console.warn(
        `Converting pointer[${sEvent}] : down move up cancel enter TO touchstart touchmove touchend, etc ..`
      );
      switch (sEvent) {
        case "down": {
          sMethod = "touch";
          sEvent = "start";
          break;
        }
        case "move": {
          sMethod = "touch";
          break;
        }
        case "up": {
          sMethod = "touch";
          sEvent = "end";
          break;
        }
        case "cancel": {
          sMethod = "touch";
          break;
        }
        case "enter": {
          break;
        }
        // case "over": case "out": not used at now
        default: {
          console.warn(
            `PointerEvent not available in this browser ? The event ${sEvent} would not be called`
          );
        }
      }
    }
    switch (sEvent) {
      // both pointer and move events
      case "down":
      case "up":
      case "move":
      case "over":
      case "out":
      // @ts-expect-error - intentional fallthrough
      case "enter": {
        oDOM.addEventListener(sMethod + sEvent, fCall, capture);
      }
      // only pointerevents
      // falls through
      case "leave":
      case "cancel":
      case "gotpointercapture":
      // @ts-expect-error - intentional fallthrough
      case "lostpointercapture": {
        if (sMethod != "mouse") {
          return oDOM.addEventListener(sMethod + sEvent, fCall, capture);
        }
      }
      // not "pointer" || "mouse"
      // falls through
      default:
        return oDOM.addEventListener(sEvent, fCall, capture);
    }
  }
  pointerListenerRemove(oDOM, sEvent, fCall, capture = false) {
    if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall !== "function")
      return;
    switch (sEvent) {
      // both pointer and move events
      case "down":
      case "up":
      case "move":
      case "over":
      case "out":
      // @ts-expect-error - intentional fallthrough
      case "enter": {
        if (this.pointerevents_method == "pointer" || this.pointerevents_method == "mouse") {
          oDOM.removeEventListener(
            this.pointerevents_method + sEvent,
            fCall,
            capture
          );
        }
      }
      // only pointerevents
      // falls through
      case "leave":
      case "cancel":
      case "gotpointercapture":
      // @ts-expect-error - intentional fallthrough
      case "lostpointercapture": {
        if (this.pointerevents_method == "pointer") {
          return oDOM.removeEventListener(
            this.pointerevents_method + sEvent,
            fCall,
            capture
          );
        }
      }
      // not "pointer" || "mouse"
      // falls through
      default:
        return oDOM.removeEventListener(sEvent, fCall, capture);
    }
  }
  getTime() {
    return performance.now();
  }
  distance = distance;
  colorToString(c2) {
    return `rgba(${Math.round(c2[0] * 255).toFixed()},${Math.round(
      c2[1] * 255
    ).toFixed()},${Math.round(c2[2] * 255).toFixed()},${c2.length == 4 ? c2[3].toFixed(2) : "1.0"})`;
  }
  isInsideRectangle = isInsideRectangle;
  // [minx,miny,maxx,maxy]
  growBounding(bounding, x2, y) {
    if (x2 < bounding[0]) {
      bounding[0] = x2;
    } else if (x2 > bounding[2]) {
      bounding[2] = x2;
    }
    if (y < bounding[1]) {
      bounding[1] = y;
    } else if (y > bounding[3]) {
      bounding[3] = y;
    }
  }
  overlapBounding = overlapBounding;
  // point inside bounding box
  isInsideBounding(p, bb) {
    if (p[0] < bb[0][0] || p[1] < bb[0][1] || p[0] > bb[1][0] || p[1] > bb[1][1]) {
      return false;
    }
    return true;
  }
  // Convert a hex value to its decimal value - the inputted hex must be in the
  // format of a hex triplet - the kind we use for HTML colours. The function
  // will return an array with three values.
  hex2num(hex) {
    if (hex.charAt(0) == "#") {
      hex = hex.slice(1);
    }
    hex = hex.toUpperCase();
    const hex_alphabets = "0123456789ABCDEF";
    const value = new Array(3);
    let k = 0;
    let int1, int2;
    for (let i = 0; i < 6; i += 2) {
      int1 = hex_alphabets.indexOf(hex.charAt(i));
      int2 = hex_alphabets.indexOf(hex.charAt(i + 1));
      value[k] = int1 * 16 + int2;
      k++;
    }
    return value;
  }
  // Give a array with three values as the argument and the function will return
  // the corresponding hex triplet.
  num2hex(triplet) {
    const hex_alphabets = "0123456789ABCDEF";
    let hex = "#";
    let int1, int2;
    for (let i = 0; i < 3; i++) {
      int1 = triplet[i] / 16;
      int2 = triplet[i] % 16;
      hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2);
    }
    return hex;
  }
  closeAllContextMenus(ref_window = window) {
    const elements = [
      ...ref_window.document.querySelectorAll(".litecontextmenu")
    ];
    if (!elements.length) return;
    for (const element of elements) {
      if ("close" in element && typeof element.close === "function") {
        element.close();
      } else {
        element.remove();
      }
    }
  }
  extendClass(target, origin) {
    for (const i in origin) {
      if (target.hasOwnProperty(i)) continue;
      target[i] = origin[i];
    }
    if (origin.prototype) {
      for (const i in origin.prototype) {
        if (!origin.prototype.hasOwnProperty(i)) continue;
        if (target.prototype.hasOwnProperty(i)) continue;
        if (origin.prototype.__lookupGetter__(i)) {
          target.prototype.__defineGetter__(
            i,
            origin.prototype.__lookupGetter__(i)
          );
        } else {
          target.prototype[i] = origin.prototype[i];
        }
        if (origin.prototype.__lookupSetter__(i)) {
          target.prototype.__defineSetter__(
            i,
            origin.prototype.__lookupSetter__(i)
          );
        }
      }
    }
  }
}
Symbol.dispose ??= /* @__PURE__ */ Symbol("Symbol.dispose");
Symbol.asyncDispose ??= /* @__PURE__ */ Symbol("Symbol.asyncDispose");
function loadPolyfills() {
  if (typeof window != "undefined" && window.CanvasRenderingContext2D && !window.CanvasRenderingContext2D.prototype.roundRect) {
    window.CanvasRenderingContext2D.prototype.roundRect = function(x2, y, w, h2, radius, radius_low) {
      let top_left_radius = 0;
      let top_right_radius = 0;
      let bottom_left_radius = 0;
      let bottom_right_radius = 0;
      if (radius === 0) {
        this.rect(x2, y, w, h2);
        return;
      }
      if (radius_low === void 0) radius_low = radius;
      if (Array.isArray(radius)) {
        if (radius.length == 1) {
          top_left_radius = top_right_radius = bottom_left_radius = bottom_right_radius = radius[0];
        } else if (radius.length == 2) {
          top_left_radius = bottom_right_radius = radius[0];
          top_right_radius = bottom_left_radius = radius[1];
        } else if (radius.length == 4) {
          top_left_radius = radius[0];
          top_right_radius = radius[1];
          bottom_left_radius = radius[2];
          bottom_right_radius = radius[3];
        } else {
          return;
        }
      } else {
        top_left_radius = radius || 0;
        top_right_radius = radius || 0;
        const low = !Array.isArray(radius_low) && radius_low ? radius_low : 0;
        bottom_left_radius = low;
        bottom_right_radius = low;
      }
      this.moveTo(x2 + top_left_radius, y);
      this.lineTo(x2 + w - top_right_radius, y);
      this.quadraticCurveTo(x2 + w, y, x2 + w, y + top_right_radius);
      this.lineTo(x2 + w, y + h2 - bottom_right_radius);
      this.quadraticCurveTo(x2 + w, y + h2, x2 + w - bottom_right_radius, y + h2);
      this.lineTo(x2 + bottom_right_radius, y + h2);
      this.quadraticCurveTo(x2, y + h2, x2, y + h2 - bottom_left_radius);
      this.lineTo(x2, y + bottom_left_radius);
      this.quadraticCurveTo(x2, y, x2 + top_left_radius, y);
    };
  }
  if (typeof window != "undefined" && !window["requestAnimationFrame"]) {
    window.requestAnimationFrame = // @ts-expect-error Legacy code
    window.webkitRequestAnimationFrame || // @ts-expect-error Legacy code
    window.mozRequestAnimationFrame || function(callback) {
      window.setTimeout(callback, 1e3 / 60);
    };
  }
}
__name(loadPolyfills, "loadPolyfills");
const LiteGraph = new LiteGraphGlobal();
loadPolyfills();
function isNodeLocatorId(value) {
  if (typeof value !== "string") return false;
  const parts2 = value.split(":");
  if (parts2.length === 1) {
    return value.length > 0;
  }
  if (parts2.length !== 2) return false;
  if (!parts2[1]) return false;
  const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  return uuidPattern.test(parts2[0]);
}
__name(isNodeLocatorId, "isNodeLocatorId");
function isNodeExecutionId(value) {
  if (typeof value !== "string") return false;
  return value.includes(":");
}
__name(isNodeExecutionId, "isNodeExecutionId");
function parseNodeLocatorId(id2) {
  if (!isNodeLocatorId(id2)) return null;
  const parts2 = id2.split(":");
  if (parts2.length === 1) {
    return {
      subgraphUuid: null,
      localNodeId: isNaN(Number(id2)) ? id2 : Number(id2)
    };
  }
  const [subgraphUuid, localNodeId] = parts2;
  return {
    subgraphUuid,
    localNodeId: isNaN(Number(localNodeId)) ? localNodeId : Number(localNodeId)
  };
}
__name(parseNodeLocatorId, "parseNodeLocatorId");
function createNodeLocatorId(subgraphUuid, localNodeId) {
  return `${subgraphUuid}:${localNodeId}`;
}
__name(createNodeLocatorId, "createNodeLocatorId");
function parseNodeExecutionId(id2) {
  if (!isNodeExecutionId(id2)) return null;
  return id2.split(":").map((part) => isNaN(Number(part)) ? part : Number(part));
}
__name(parseNodeExecutionId, "parseNodeExecutionId");
function createNodeExecutionId(nodeIds) {
  return nodeIds.join(":");
}
__name(createNodeExecutionId, "createNodeExecutionId");
const isAbortError = /* @__PURE__ */ __name((err) => err instanceof DOMException && err.name === "AbortError", "isAbortError");
const isSubgraph = /* @__PURE__ */ __name((item) => item?.isRootGraph === false, "isSubgraph");
const isNonNullish = /* @__PURE__ */ __name((item) => item != null, "isNonNullish");
const isSubgraphIoNode = /* @__PURE__ */ __name((node2) => {
  const nodeClass = node2.constructor?.comfyClass;
  return nodeClass === "SubgraphInputNode" || nodeClass === "SubgraphOutputNode";
}, "isSubgraphIoNode");
const isSlotObject = /* @__PURE__ */ __name((obj) => {
  return obj !== null && typeof obj === "object" && "name" in obj && "type" in obj && "boundingRect" in obj;
}, "isSlotObject");
const isResultItemType = /* @__PURE__ */ __name((value) => {
  return value === "input" || value === "output" || value === "temp";
}, "isResultItemType");
function getLocatorIdFromNodeData(nodeData) {
  return nodeData.subgraphId ? `${nodeData.subgraphId}:${String(nodeData.id)}` : String(nodeData.id);
}
__name(getLocatorIdFromNodeData, "getLocatorIdFromNodeData");
function parseExecutionId(executionId) {
  if (!executionId || typeof executionId !== "string") return null;
  return executionId.split(":").filter((part) => part.length > 0);
}
__name(parseExecutionId, "parseExecutionId");
function getLocalNodeIdFromExecutionId(executionId) {
  const parts2 = parseExecutionId(executionId);
  return parts2 ? parts2[parts2.length - 1] : null;
}
__name(getLocalNodeIdFromExecutionId, "getLocalNodeIdFromExecutionId");
function getSubgraphPathFromExecutionId(executionId) {
  const parts2 = parseExecutionId(executionId);
  return parts2 ? parts2.slice(0, -1) : [];
}
__name(getSubgraphPathFromExecutionId, "getSubgraphPathFromExecutionId");
function visitGraphNodes(graph, visitor) {
  for (const node2 of graph.nodes) {
    visitor(node2);
  }
}
__name(visitGraphNodes, "visitGraphNodes");
function traverseSubgraphPath(startGraph, path) {
  let currentGraph = startGraph;
  for (const nodeId of path) {
    const node2 = currentGraph.getNodeById(nodeId);
    if (!node2?.isSubgraphNode?.() || !node2.subgraph) return null;
    currentGraph = node2.subgraph;
  }
  return currentGraph;
}
__name(traverseSubgraphPath, "traverseSubgraphPath");
function triggerCallbackOnAllNodes(graph, callbackProperty) {
  forEachNode(graph, (node2) => {
    const callback = node2[callbackProperty];
    if (typeof callback === "function") {
      callback.call(node2);
    }
  });
}
__name(triggerCallbackOnAllNodes, "triggerCallbackOnAllNodes");
function mapAllNodes(graph, mapFn) {
  const results = [];
  visitGraphNodes(graph, (node2) => {
    if (node2.isSubgraphNode?.() && node2.subgraph) {
      results.push(...mapAllNodes(node2.subgraph, mapFn));
    }
    const result = mapFn(node2);
    if (result !== void 0) {
      results.push(result);
    }
  });
  return results;
}
__name(mapAllNodes, "mapAllNodes");
function forEachNode(graph, fn) {
  visitGraphNodes(graph, (node2) => {
    if (node2.isSubgraphNode?.() && node2.subgraph) {
      forEachNode(node2.subgraph, fn);
    }
    fn(node2);
  });
}
__name(forEachNode, "forEachNode");
function collectAllNodes(graph, filter2) {
  return mapAllNodes(graph, (node2) => {
    if (!filter2 || filter2(node2)) {
      return node2;
    }
    return void 0;
  });
}
__name(collectAllNodes, "collectAllNodes");
function findSubgraphByUuid(graph, targetUuid) {
  for (const node2 of graph.nodes) {
    if (node2.isSubgraphNode?.() && node2.subgraph) {
      if (node2.subgraph.id === targetUuid) {
        return node2.subgraph;
      }
      const found = findSubgraphByUuid(node2.subgraph, targetUuid);
      if (found) return found;
    }
  }
  return null;
}
__name(findSubgraphByUuid, "findSubgraphByUuid");
function findSubgraphPathById(rootGraph, targetId) {
  const stack = [
    { graph: rootGraph, path: [] }
  ];
  while (stack.length > 0) {
    const { graph, path } = stack.pop();
    if (!graph || !graph._nodes || !Array.isArray(graph._nodes)) {
      continue;
    }
    for (const node2 of graph._nodes) {
      if (node2.isSubgraphNode?.() && node2.subgraph) {
        const newPath = [...path, String(node2.subgraph.id)];
        if (node2.subgraph.id === targetId) {
          return newPath;
        }
        stack.push({ graph: node2.subgraph, path: newPath });
      }
    }
  }
  return null;
}
__name(findSubgraphPathById, "findSubgraphPathById");
function getNodeByExecutionId(rootGraph, executionId) {
  if (!rootGraph) return null;
  const localNodeId = getLocalNodeIdFromExecutionId(executionId);
  if (!localNodeId) return null;
  const subgraphPath = getSubgraphPathFromExecutionId(executionId);
  if (subgraphPath.length === 0) {
    return rootGraph.getNodeById(localNodeId) || null;
  }
  const targetGraph = traverseSubgraphPath(rootGraph, subgraphPath);
  if (!targetGraph) return null;
  return targetGraph.getNodeById(localNodeId) || null;
}
__name(getNodeByExecutionId, "getNodeByExecutionId");
function getNodeByLocatorId(rootGraph, locatorId) {
  if (!rootGraph) return null;
  const parsedIds = parseNodeLocatorId(locatorId);
  if (!parsedIds) return null;
  const { subgraphUuid, localNodeId } = parsedIds;
  if (!subgraphUuid) {
    return rootGraph.getNodeById(localNodeId) || null;
  }
  const targetSubgraph = findSubgraphByUuid(rootGraph, subgraphUuid);
  if (!targetSubgraph) return null;
  return targetSubgraph.getNodeById(localNodeId) || null;
}
__name(getNodeByLocatorId, "getNodeByLocatorId");
function forEachSubgraphNode(rootGraph, subgraphId, fn) {
  if (!rootGraph || !subgraphId) return;
  forEachNode(rootGraph, (node2) => {
    if (node2.type === subgraphId) {
      fn(node2);
    }
  });
}
__name(forEachSubgraphNode, "forEachSubgraphNode");
function getAllNonIoNodesInSubgraph(subgraph) {
  return subgraph.nodes.filter((node2) => !isSubgraphIoNode(node2));
}
__name(getAllNonIoNodesInSubgraph, "getAllNonIoNodesInSubgraph");
function traverseNodesDepthFirst(nodes, options) {
  const {
    visitor = /* @__PURE__ */ __name(() => void 0, "visitor"),
    initialContext = void 0,
    expandSubgraphs = true
  } = options || {};
  const stack = [];
  for (const node2 of nodes) {
    stack.push({ node: node2, context: initialContext });
  }
  while (stack.length > 0) {
    const { node: node2, context } = stack.pop();
    const childContext = visitor(node2, context);
    if (expandSubgraphs && node2.isSubgraphNode?.() && node2.subgraph) {
      const children = node2.subgraph.nodes;
      for (let i = children.length - 1; i >= 0; i--) {
        stack.push({ node: children[i], context: childContext });
      }
    }
  }
}
__name(traverseNodesDepthFirst, "traverseNodesDepthFirst");
function collectFromNodes(nodes, options) {
  const {
    collector = /* @__PURE__ */ __name((node2) => node2, "collector"),
    contextBuilder = /* @__PURE__ */ __name(() => void 0, "contextBuilder"),
    initialContext = void 0,
    expandSubgraphs = true
  } = options || {};
  const results = [];
  traverseNodesDepthFirst(nodes, {
    visitor: /* @__PURE__ */ __name((node2, context) => {
      const data = collector(node2, context);
      if (data !== null) {
        results.push(data);
      }
      return contextBuilder(node2, context);
    }, "visitor"),
    initialContext,
    expandSubgraphs
  });
  return results;
}
__name(collectFromNodes, "collectFromNodes");
function getExecutionIdsForSelectedNodes(selectedNodes, startGraph = selectedNodes[0]?.graph) {
  if (!startGraph) return [];
  const rootGraph = startGraph.rootGraph;
  const parentPath = startGraph.isRootGraph ? "" : findPartialExecutionPathToGraph(startGraph, rootGraph);
  if (parentPath === void 0) return [];
  return collectFromNodes(selectedNodes, {
    collector: /* @__PURE__ */ __name((node2, parentExecutionId) => {
      const nodeId = String(node2.id);
      return parentExecutionId ? `${parentExecutionId}:${nodeId}` : nodeId;
    }, "collector"),
    contextBuilder: /* @__PURE__ */ __name((node2, parentExecutionId) => {
      const nodeId = String(node2.id);
      return parentExecutionId ? `${parentExecutionId}:${nodeId}` : nodeId;
    }, "contextBuilder"),
    initialContext: parentPath,
    expandSubgraphs: true
  });
}
__name(getExecutionIdsForSelectedNodes, "getExecutionIdsForSelectedNodes");
function findPartialExecutionPathToGraph(target, root) {
  for (const node2 of root.nodes) {
    if (!node2.isSubgraphNode()) continue;
    if (node2.subgraph === target) return `${node2.id}`;
    const subpath = findPartialExecutionPathToGraph(target, node2.subgraph);
    if (subpath !== void 0) return node2.id + ":" + subpath;
  }
  return void 0;
}
__name(findPartialExecutionPathToGraph, "findPartialExecutionPathToGraph");
function useSelectedLiteGraphItems() {
  const canvasStore = useCanvasStore();
  const isIgnoredItem = /* @__PURE__ */ __name((item) => {
    return item instanceof Reroute;
  }, "isIgnoredItem");
  const filterSelectableItems = /* @__PURE__ */ __name((items) => {
    const result = /* @__PURE__ */ new Set();
    for (const item of items) {
      if (!isIgnoredItem(item)) {
        result.add(item);
      }
    }
    return result;
  }, "filterSelectableItems");
  const getSelectableItems = /* @__PURE__ */ __name(() => {
    const { selectedItems } = canvasStore.getCanvas();
    return filterSelectableItems(selectedItems);
  }, "getSelectableItems");
  const hasSelectableItems = /* @__PURE__ */ __name(() => {
    return getSelectableItems().size > 0;
  }, "hasSelectableItems");
  const hasMultipleSelectableItems = /* @__PURE__ */ __name(() => {
    return getSelectableItems().size > 1;
  }, "hasMultipleSelectableItems");
  const getSelectedNodes = /* @__PURE__ */ __name(() => {
    const selectedNodes = app$1.canvas.selected_nodes;
    if (!selectedNodes) return [];
    const nodeArray = [];
    for (const i in selectedNodes) {
      nodeArray.push(selectedNodes[i]);
    }
    const hasSubgraphs = nodeArray.some(
      (node2) => node2.isSubgraphNode?.() && node2.subgraph
    );
    if (!hasSubgraphs) {
      return nodeArray;
    }
    return collectFromNodes(nodeArray);
  }, "getSelectedNodes");
  const toggleSelectedNodesMode = /* @__PURE__ */ __name((mode) => {
    const selectedNodes = app$1.canvas.selected_nodes;
    if (!selectedNodes) return;
    const selectedNodeArray = [];
    for (const i in selectedNodes) {
      selectedNodeArray.push(selectedNodes[i]);
    }
    const allNodesMatch = !selectedNodeArray.some(
      (selectedNode) => selectedNode.mode !== mode
    );
    const newModeForSelectedNode = allNodesMatch ? LGraphEventMode.ALWAYS : mode;
    selectedNodeArray.forEach((selectedNode) => {
      selectedNode.mode = newModeForSelectedNode;
      if (selectedNode.isSubgraphNode?.() && selectedNode.subgraph) {
        traverseNodesDepthFirst([selectedNode], {
          visitor: /* @__PURE__ */ __name((node2) => {
            if (node2 === selectedNode) return void 0;
            node2.mode = newModeForSelectedNode;
            return void 0;
          }, "visitor")
        });
      }
    });
  }, "toggleSelectedNodesMode");
  return {
    isIgnoredItem,
    filterSelectableItems,
    getSelectableItems,
    hasSelectableItems,
    hasMultipleSelectableItems,
    getSelectedNodes,
    toggleSelectedNodesMode
  };
}
__name(useSelectedLiteGraphItems, "useSelectedLiteGraphItems");
function appendJsonExt(path) {
  if (!path.toLowerCase().endsWith(".json")) {
    path += ".json";
  }
  return path;
}
__name(appendJsonExt, "appendJsonExt");
function highlightQuery(text, query) {
  if (!query) return text;
  const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const regex = new RegExp(`(${escapedQuery})`, "gi");
  return text.replace(regex, '<span class="highlight">$1</span>');
}
__name(highlightQuery, "highlightQuery");
function formatNumberWithSuffix(num, {
  precision = 1,
  roundToInt = false
} = {}) {
  const suffixes = ["", "k", "m", "b", "t"];
  const absNum = Math.abs(num);
  if (absNum < 1e3) {
    return roundToInt ? Math.round(num).toString() : num.toFixed(precision);
  }
  const exp = Math.min(Math.floor(Math.log10(absNum) / 3), suffixes.length - 1);
  const formattedNum = (num / Math.pow(1e3, exp)).toFixed(precision);
  return `${formattedNum}${suffixes[exp]}`;
}
__name(formatNumberWithSuffix, "formatNumberWithSuffix");
function formatSize(value) {
  if (value === null || value === void 0) {
    return "-";
  }
  const bytes = value;
  if (bytes === 0) return "0 B";
  const k = 1024;
  const sizes = ["B", "KB", "MB", "GB"];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}
__name(formatSize, "formatSize");
function formatCommitHash(value) {
  if (/^[a-f0-9]{40}$/i.test(value)) {
    return value.slice(0, 7);
  }
  return value;
}
__name(formatCommitHash, "formatCommitHash");
function getFilenameDetails(fullFilename) {
  if (fullFilename.includes(".")) {
    return {
      filename: fullFilename.split(".").slice(0, -1).join("."),
      suffix: fullFilename.split(".").pop() ?? null
    };
  } else {
    return { filename: fullFilename, suffix: null };
  }
}
__name(getFilenameDetails, "getFilenameDetails");
function getPathDetails(path) {
  const directory = path.split("/").slice(0, -1).join("/");
  const fullFilename = path.split("/").pop() ?? path;
  return { directory, fullFilename, ...getFilenameDetails(fullFilename) };
}
__name(getPathDetails, "getPathDetails");
function normalizeI18nKey(key) {
  return typeof key === "string" ? key.replace(/\./g, "_") : "";
}
__name(normalizeI18nKey, "normalizeI18nKey");
function processDynamicPrompt(input) {
  function stripComments(str) {
    return str.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
  }
  __name(stripComments, "stripComments");
  let i = 0;
  let result = "";
  input = stripComments(input);
  const handleEscape = /* @__PURE__ */ __name(() => {
    const nextChar = input[i++];
    return "\\" + nextChar;
  }, "handleEscape");
  function parseChoiceBlock() {
    const options = [];
    let choice = "";
    let depth = 0;
    while (i < input.length) {
      const char = input[i++];
      if (char === "\\") {
        choice += handleEscape();
        continue;
      } else if (char === "{") {
        depth++;
      } else if (char === "}") {
        if (!depth) break;
        depth--;
      } else if (char === "|") {
        if (!depth) {
          options.push(choice);
          choice = "";
          continue;
        }
      }
      choice += char;
    }
    options.push(choice);
    const chosenOption = options[Math.floor(Math.random() * options.length)];
    return processDynamicPrompt(chosenOption);
  }
  __name(parseChoiceBlock, "parseChoiceBlock");
  while (i < input.length) {
    const char = input[i++];
    if (char === "\\") {
      result += handleEscape();
    } else if (char === "{") {
      result += parseChoiceBlock();
    } else {
      result += char;
    }
  }
  return result.replace(/\\([{}|])/g, "$1");
}
__name(processDynamicPrompt, "processDynamicPrompt");
function isValidUrl(url) {
  try {
    new URL(url);
    return true;
  } catch {
    return false;
  }
}
__name(isValidUrl, "isValidUrl");
function parseFilePath(filepath) {
  if (!filepath?.trim()) return { filename: "", subfolder: "" };
  const normalizedPath = filepath.replace(/[\\/]+/g, "/").replace(/^\//, "").replace(/\/$/, "");
  const lastSlashIndex = normalizedPath.lastIndexOf("/");
  if (lastSlashIndex === -1) {
    return {
      filename: normalizedPath,
      subfolder: ""
    };
  }
  return {
    filename: normalizedPath.slice(lastSlashIndex + 1),
    subfolder: normalizedPath.slice(0, lastSlashIndex)
  };
}
__name(parseFilePath, "parseFilePath");
const parts = {
  d: /* @__PURE__ */ __name((d2) => d2.getDate(), "d"),
  M: /* @__PURE__ */ __name((d2) => d2.getMonth() + 1, "M"),
  h: /* @__PURE__ */ __name((d2) => d2.getHours(), "h"),
  m: /* @__PURE__ */ __name((d2) => d2.getMinutes(), "m"),
  s: /* @__PURE__ */ __name((d2) => d2.getSeconds(), "s")
};
const format = Object.keys(parts).map((k) => k + k + "?").join("|") + "|yyy?y?";
function formatDate(text, date) {
  return text.replace(new RegExp(format, "g"), (text2) => {
    if (text2 === "yy") return (date.getFullYear() + "").substring(2);
    if (text2 === "yyyy") return date.getFullYear().toString();
    if (text2[0] in parts) {
      const p = parts[text2[0]](date);
      return (p + "").padStart(text2.length, "0");
    }
    return text2;
  });
}
__name(formatDate, "formatDate");
const paramsToCacheKey = /* @__PURE__ */ __name((params) => {
  if (typeof params === "string") return params;
  if (typeof params === "object" && params !== null)
    return Object.keys(params).sort((a, b) => a.localeCompare(b)).map((key) => `${key}:${params[key]}`).join("&");
  return String(params);
}, "paramsToCacheKey");
const generateUUID = /* @__PURE__ */ __name(() => {
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
    return crypto.randomUUID();
  }
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c2) => {
    const r = Math.random() * 16 | 0;
    const v2 = c2 === "x" ? r : r & 3 | 8;
    return v2.toString(16);
  });
}, "generateUUID");
const isCivitaiModelUrl = /* @__PURE__ */ __name((url) => {
  if (!isValidUrl(url)) return false;
  if (!url.includes("civitai.com")) return false;
  const urlObj = new URL(url);
  const pathname = urlObj.pathname;
  return /^\/api\/download\/models\/(\d+)$/.test(pathname) || /^\/api\/v1\/models\/(\d+)$/.test(pathname) || /^\/api\/v1\/models-versions\/(\d+)$/.test(pathname);
}, "isCivitaiModelUrl");
const downloadUrlToHfRepoUrl = /* @__PURE__ */ __name((url) => {
  try {
    const urlObj = new URL(url);
    const pathname = urlObj.pathname;
    const regex = /^(.*?)(?:\/resolve\/|\/blob\/|$)/;
    const repoPathMatch = regex.exec(pathname);
    const repoPath = repoPathMatch?.[1]?.replace(/^\//, "") || "";
    return `https://huggingface.co/${repoPath}`;
  } catch (error) {
    return url;
  }
}, "downloadUrlToHfRepoUrl");
function formatMetronomeCurrency(amount, currency) {
  if (currency === "usd") {
    return (amount / 100).toFixed(2);
  }
  return amount.toString();
}
__name(formatMetronomeCurrency, "formatMetronomeCurrency");
function usdToMicros(usd) {
  return Math.round(usd * 1e6);
}
__name(usdToMicros, "usdToMicros");
function linkifyHtml(text) {
  if (!text) return "";
  const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%?=~_|])|(\bwww\.[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%?=~_|])/gi;
  return text.replace(urlRegex, (_match, p1, _p2, p3) => {
    const url = p1 || p3;
    const href = p3 ? `http://${url}` : url;
    return `<a href="${href}" target="_blank" rel="noopener noreferrer" class="text-primary-400 hover:underline">${url}</a>`;
  });
}
__name(linkifyHtml, "linkifyHtml");
function nl2br(text) {
  if (!text) return "";
  return text.replace(/\n/g, "<br />");
}
__name(nl2br, "nl2br");
function formatVersionAnchor(version) {
  return `v${version.replace(/\./g, "-")}`;
}
__name(formatVersionAnchor, "formatVersionAnchor");
function stringToLocale(locale) {
  const supportedLocales = [
    "en",
    "es",
    "fr",
    "ja",
    "ko",
    "ru",
    "zh"
  ];
  return supportedLocales.includes(locale) ? locale : "en";
}
__name(stringToLocale, "stringToLocale");
function formatDuration(milliseconds) {
  if (!milliseconds || milliseconds < 0) return "0s";
  const totalSeconds = Math.floor(milliseconds / 1e3);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor(totalSeconds % 3600 / 60);
  const remainingSeconds = Math.floor(totalSeconds % 60);
  const parts2 = [];
  if (hours > 0) {
    parts2.push(`${hours}h`);
  }
  if (minutes > 0) {
    parts2.push(`${minutes}m`);
  }
  if (remainingSeconds > 0 || parts2.length === 0) {
    parts2.push(`${remainingSeconds}s`);
  }
  return parts2.join(" ");
}
__name(formatDuration, "formatDuration");
const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp", "bmp"];
const VIDEO_EXTENSIONS = ["mp4", "webm", "mov", "avi"];
const AUDIO_EXTENSIONS = ["mp3", "wav", "ogg", "flac"];
const THREE_D_EXTENSIONS = ["obj", "fbx", "gltf", "glb"];
function truncateFilename(filename, maxLength = 20) {
  if (!filename || filename.length <= maxLength) {
    return filename;
  }
  const lastDotIndex = filename.lastIndexOf(".");
  const nameWithoutExt = lastDotIndex > -1 ? filename.substring(0, lastDotIndex) : filename;
  const extension = lastDotIndex > -1 ? filename.substring(lastDotIndex) : "";
  if (nameWithoutExt.length <= maxLength) {
    return filename;
  }
  const halfLength = Math.floor((maxLength - 3) / 2);
  const start = nameWithoutExt.substring(0, halfLength);
  const end = nameWithoutExt.substring(nameWithoutExt.length - halfLength);
  return `${start}...${end}${extension}`;
}
__name(truncateFilename, "truncateFilename");
function getMediaTypeFromFilename(filename) {
  if (!filename) return "image";
  const ext = filename.split(".").pop()?.toLowerCase();
  if (!ext) return "image";
  if (IMAGE_EXTENSIONS.includes(ext)) return "image";
  if (VIDEO_EXTENSIONS.includes(ext)) return "video";
  if (AUDIO_EXTENSIONS.includes(ext)) return "audio";
  if (THREE_D_EXTENSIONS.includes(ext)) return "3D";
  return "image";
}
__name(getMediaTypeFromFilename, "getMediaTypeFromFilename");
const _hoisted_1$23 = { class: "relative max-h-[200px] min-h-[28px] w-full overflow-y-auto rounded-lg px-4 py-2 text-xs" };
const _hoisted_2$1B = { class: "flex items-center gap-2" };
const _hoisted_3$12 = { class: "flex flex-1 items-center gap-2 break-all" };
const _hoisted_4$N = ["innerHTML"];
const _sfc_main$2x = /* @__PURE__ */ defineComponent({
  __name: "TextPreviewWidget",
  props: /* @__PURE__ */ mergeModels({
    nodeId: {}
  }, {
    "modelValue": { required: true },
    "modelModifiers": {}
  }),
  emits: ["update:modelValue"],
  setup(__props) {
    const modelValue = useModel(__props, "modelValue");
    const props = __props;
    const executionStore = useExecutionStore();
    const isParentNodeExecuting = ref(true);
    const formattedText = computed(() => {
      const src = modelValue.value;
      const tokens = [];
      const holed = src.replace(
        /\[\[([^|\]]+)\|([^\]]+)\]\]/g,
        (_m, label, url) => {
          tokens.push({ label: String(label), url: String(url) });
          return `__LNK${tokens.length - 1}__`;
        }
      );
      let html = nl2br(linkifyHtml(holed));
      html = html.replace(/__LNK(\d+)__/g, (_m, i) => {
        const { label, url } = tokens[+i];
        const safeHref = url.replace(/"/g, "&quot;");
        const safeLabel = label.replace(/</g, "&lt;").replace(/>/g, "&gt;");
        return /^https?:\/\//i.test(url) ? `<a href="${safeHref}" target="_blank" rel="noopener noreferrer">${safeLabel}</a>` : safeLabel;
      });
      return html;
    });
    let parentNodeId = null;
    onMounted(() => {
      parentNodeId = props.nodeId;
    });
    const stopWatching = watch(
      [() => executionStore.executingNodeIds, () => executionStore.isIdle],
      () => {
        if (executionStore.isIdle) {
          isParentNodeExecuting.value = false;
          stopWatching();
          return;
        }
        if (parentNodeId && !executionStore.executingNodeIds.includes(parentNodeId)) {
          isParentNodeExecuting.value = false;
          stopWatching();
        }
        if (!parentNodeId && executionStore.executingNodeIds.length > 0) {
          parentNodeId = executionStore.executingNodeIds[0];
        }
      }
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$23, [
        createBaseVNode("div", _hoisted_2$1B, [
          createBaseVNode("div", _hoisted_3$12, [
            createBaseVNode("span", { innerHTML: formattedText.value }, null, 8, _hoisted_4$N),
            isParentNodeExecuting.value ? (openBlock(), createBlock(unref(script$1), {
              key: 0,
              class: "h-4! flex-1!"
            })) : createCommentVNode("", true)
          ])
        ])
      ]);
    };
  }
});
const useChainCallback = /* @__PURE__ */ __name((originalCallback, ...callbacks) => {
  return function(...args) {
    originalCallback?.call(this, ...args);
    for (const callback of callbacks) callback.call(this, ...args);
  };
}, "useChainCallback");
const useDomWidgetStore = defineStore("domWidget", () => {
  const widgetStates = ref(/* @__PURE__ */ new Map());
  const activeWidgetStates = computed(
    () => [...widgetStates.value.values()].filter((state2) => state2.active)
  );
  const inactiveWidgetStates = computed(
    () => [...widgetStates.value.values()].filter((state2) => !state2.active)
  );
  const registerWidget = /* @__PURE__ */ __name((widget) => {
    widgetStates.value.set(widget.id, {
      widget: markRaw(widget),
      visible: true,
      readonly: false,
      zIndex: 0,
      pos: [0, 0],
      size: [0, 0],
      active: true
    });
  }, "registerWidget");
  const unregisterWidget = /* @__PURE__ */ __name((widgetId) => {
    widgetStates.value.delete(widgetId);
  }, "unregisterWidget");
  const activateWidget = /* @__PURE__ */ __name((widgetId) => {
    const state2 = widgetStates.value.get(widgetId);
    if (state2) state2.active = true;
  }, "activateWidget");
  const deactivateWidget = /* @__PURE__ */ __name((widgetId) => {
    const state2 = widgetStates.value.get(widgetId);
    if (state2) state2.active = false;
  }, "deactivateWidget");
  const setWidget = /* @__PURE__ */ __name((widget) => {
    const state2 = widgetStates.value.get(widget.id);
    if (!state2) return;
    state2.active = true;
    state2.widget = widget;
  }, "setWidget");
  const clear = /* @__PURE__ */ __name(() => {
    widgetStates.value.clear();
  }, "clear");
  return {
    widgetStates,
    activeWidgetStates,
    inactiveWidgetStates,
    registerWidget,
    unregisterWidget,
    activateWidget,
    deactivateWidget,
    setWidget,
    clear
  };
});
const isDOMWidget = /* @__PURE__ */ __name((widget) => "element" in widget && !!widget.element, "isDOMWidget");
const isComponentWidget = /* @__PURE__ */ __name((widget) => "component" in widget && !!widget.component, "isComponentWidget");
class BaseDOMWidgetImpl extends LegacyWidget {
  static {
    __name(this, "BaseDOMWidgetImpl");
  }
  static DEFAULT_MARGIN = 10;
  id;
  constructor(obj) {
    const { node: node2, name: name2, type, options } = obj;
    super({ y: 0, name: name2, type, options }, node2);
    this.id = generateUUID();
  }
  get value() {
    return this.options.getValue?.() ?? "";
  }
  set value(v2) {
    this.options.setValue?.(v2);
    this.callback?.(this.value);
  }
  get margin() {
    return this.options.margin ?? BaseDOMWidgetImpl.DEFAULT_MARGIN;
  }
  isVisible() {
    return !["hidden"].includes(this.type) && this.node.isWidgetVisible(this);
  }
  draw(ctx, _node, widget_width, y, widget_height, lowQuality) {
    if (this.options.hideOnZoom && lowQuality && this.isVisible()) {
      const originalFillStyle = ctx.fillStyle;
      ctx.beginPath();
      ctx.fillStyle = LiteGraph.WIDGET_BGCOLOR;
      ctx.rect(
        this.margin,
        y + this.margin,
        widget_width - this.margin * 2,
        (this.computedHeight ?? widget_height) - 2 * this.margin
      );
      ctx.fill();
      ctx.fillStyle = originalFillStyle;
    } else if (this.promoted && this.isVisible()) {
      ctx.save();
      const adjustedMargin = this.margin - 1;
      ctx.beginPath();
      ctx.strokeStyle = LiteGraph.WIDGET_PROMOTED_OUTLINE_COLOR;
      ctx.strokeRect(
        adjustedMargin,
        y + adjustedMargin,
        widget_width - adjustedMargin * 2,
        (this.computedHeight ?? widget_height) - 2 * adjustedMargin
      );
      ctx.restore();
    }
    this.options.onDraw?.(this);
  }
  onRemove() {
    useDomWidgetStore().unregisterWidget(this.id);
  }
  createCopyForNode(node2) {
    const cloned = new this.constructor({
      node: node2,
      name: this.name,
      type: this.type,
      options: this.options
    });
    cloned.value = this.value;
    cloned.y = this.y;
    return cloned;
  }
}
class DOMWidgetImpl extends BaseDOMWidgetImpl {
  static {
    __name(this, "DOMWidgetImpl");
  }
  element;
  constructor(obj) {
    super(obj);
    this.element = obj.element;
  }
  createCopyForNode(node2) {
    const cloned = new this.constructor({
      node: node2,
      name: this.name,
      type: this.type,
      element: this.element,
      // Include the element!
      options: this.options
    });
    cloned.value = this.value;
    cloned.y = this.y;
    return cloned;
  }
  /** Extract DOM widget size info */
  computeLayoutSize(node2) {
    if (this.type === "hidden") {
      return {
        minHeight: 0,
        maxHeight: 0,
        minWidth: 0
      };
    }
    const styles = getComputedStyle(this.element);
    let minHeight = this.options.getMinHeight?.() ?? parseInt(styles.getPropertyValue("--comfy-widget-min-height"));
    let maxHeight = this.options.getMaxHeight?.() ?? parseInt(styles.getPropertyValue("--comfy-widget-max-height"));
    let prefHeight = this.options.getHeight?.() ?? styles.getPropertyValue("--comfy-widget-height");
    if (typeof prefHeight === "string" && prefHeight.endsWith?.("%")) {
      prefHeight = node2.size[1] * (parseFloat(prefHeight.substring(0, prefHeight.length - 1)) / 100);
    } else {
      prefHeight = typeof prefHeight === "number" ? prefHeight : parseInt(prefHeight);
      if (isNaN(minHeight)) {
        minHeight = prefHeight;
      }
    }
    return {
      minHeight: isNaN(minHeight) ? 50 : minHeight,
      maxHeight: isNaN(maxHeight) ? void 0 : maxHeight,
      minWidth: 0
    };
  }
}
class ComponentWidgetImpl extends BaseDOMWidgetImpl {
  static {
    __name(this, "ComponentWidgetImpl");
  }
  component;
  inputSpec;
  props;
  constructor(obj) {
    super({
      type: "custom",
      ...obj
    });
    this.component = obj.component;
    this.inputSpec = obj.inputSpec;
    this.props = obj.props;
  }
  computeLayoutSize() {
    const minHeight = this.options.getMinHeight?.() ?? 50;
    const maxHeight = this.options.getMaxHeight?.();
    return {
      minHeight,
      maxHeight,
      minWidth: 0
    };
  }
  serializeValue() {
    return toRaw(this.value);
  }
}
const addWidget = /* @__PURE__ */ __name((node2, widget) => {
  node2.addCustomWidget(widget);
  if (node2.graph) {
    useDomWidgetStore().registerWidget(widget);
  }
  node2.onAdded = useChainCallback(node2.onAdded, () => {
    useDomWidgetStore().registerWidget(widget);
  });
  node2.onRemoved = useChainCallback(node2.onRemoved, () => {
    widget.onRemove?.();
  });
  node2.onResize = useChainCallback(node2.onResize, () => {
    widget.options.beforeResize?.call(widget, node2);
    widget.options.afterResize?.call(widget, node2);
  });
}, "addWidget");
LGraphNode.prototype.addDOMWidget = function(name2, type, element, options = {}) {
  const widget = new DOMWidgetImpl({
    node: this,
    name: name2,
    type,
    element,
    options: { hideOnZoom: true, ...options }
  });
  addWidget(this, widget);
  Object.defineProperty(widget, "value", {
    get() {
      return this.options.getValue?.() ?? "";
    },
    set(v2) {
      this.options.setValue?.(v2);
      this.callback?.(this.value);
    }
  });
  return widget;
};
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.domWidget = window.comfyAPI.domWidget || {};
window.comfyAPI.domWidget.isDOMWidget = isDOMWidget;
window.comfyAPI.domWidget.isComponentWidget = isComponentWidget;
window.comfyAPI.domWidget.DOMWidgetImpl = DOMWidgetImpl;
window.comfyAPI.domWidget.ComponentWidgetImpl = ComponentWidgetImpl;
window.comfyAPI.domWidget.addWidget = addWidget;
const PADDING = 16;
function useTextPreviewWidget(options = {}) {
  function widgetConstructor(node2, inputSpec) {
    const widgetValue = ref("");
    const widget = new ComponentWidgetImpl({
      node: node2,
      name: inputSpec.name,
      component: _sfc_main$2x,
      inputSpec,
      props: {
        nodeId: node2.id
      },
      options: {
        getValue: /* @__PURE__ */ __name(() => widgetValue.value, "getValue"),
        setValue: /* @__PURE__ */ __name((value) => {
          widgetValue.value = typeof value === "string" ? value : String(value);
        }, "setValue"),
        getMinHeight: /* @__PURE__ */ __name(() => options.minHeight ?? 42 + PADDING, "getMinHeight"),
        serialize: false,
        read_only: true
      },
      type: inputSpec.type
    });
    addWidget(node2, widget);
    return widget;
  }
  __name(widgetConstructor, "widgetConstructor");
  return widgetConstructor;
}
__name(useTextPreviewWidget, "useTextPreviewWidget");
const TEXT_PREVIEW_WIDGET_NAME = "$$node-text-preview";
function useNodeProgressText() {
  const textPreviewWidget = useTextPreviewWidget();
  const findTextPreviewWidget = /* @__PURE__ */ __name((node2) => node2.widgets?.find((w) => w.name === TEXT_PREVIEW_WIDGET_NAME), "findTextPreviewWidget");
  const addTextPreviewWidget = /* @__PURE__ */ __name((node2) => textPreviewWidget(node2, {
    name: TEXT_PREVIEW_WIDGET_NAME,
    type: "progressText"
  }), "addTextPreviewWidget");
  function showTextPreview(node2, text) {
    const widget = findTextPreviewWidget(node2) ?? addTextPreviewWidget(node2);
    widget.value = text;
    node2.setDirtyCanvas?.(true);
  }
  __name(showTextPreview, "showTextPreview");
  function removeTextPreview(node2) {
    if (!node2.widgets) return;
    const widgetIdx = node2.widgets.findIndex(
      (w) => w.name === TEXT_PREVIEW_WIDGET_NAME
    );
    if (widgetIdx > -1) {
      node2.widgets[widgetIdx].onRemove?.();
      node2.widgets.splice(widgetIdx, 1);
    }
  }
  __name(removeTextPreview, "removeTextPreview");
  return {
    showTextPreview,
    removeTextPreview
  };
}
__name(useNodeProgressText, "useNodeProgressText");
function useErrorHandling() {
  const toast = useToastStore();
  const toastErrorHandler = /* @__PURE__ */ __name((error) => {
    toast.add({
      severity: "error",
      summary: t("g.error"),
      detail: error instanceof Error ? error.message : t("g.unknownError")
    });
    console.error(error);
  }, "toastErrorHandler");
  const wrapWithErrorHandling = /* @__PURE__ */ __name((action, errorHandler, finallyHandler) => (...args) => {
    try {
      return action(...args);
    } catch (e2) {
      (errorHandler ?? toastErrorHandler)(e2);
    } finally {
      finallyHandler?.();
    }
  }, "wrapWithErrorHandling");
  const wrapWithErrorHandlingAsync = /* @__PURE__ */ __name((action, errorHandler, finallyHandler, recoveryStrategies = []) => async (...args) => {
    try {
      return await action(...args);
    } catch (e2) {
      for (const strategy of recoveryStrategies) {
        if (strategy.shouldHandle(e2)) {
          try {
            await strategy.recover(e2, action, args);
            return;
          } catch (recoveryError) {
            console.error("Recovery strategy failed:", recoveryError);
          }
        }
      }
      (errorHandler ?? toastErrorHandler)(e2);
    } finally {
      finallyHandler?.();
    }
  }, "wrapWithErrorHandlingAsync");
  return {
    wrapWithErrorHandling,
    wrapWithErrorHandlingAsync,
    toastErrorHandler
  };
}
__name(useErrorHandling, "useErrorHandling");
const STAGING_API_BASE_URL = "https://stagingapi.comfy.org";
const STAGING_PLATFORM_BASE_URL = "https://stagingplatform.comfy.org";
const BUILD_TIME_API_BASE_URL = STAGING_API_BASE_URL;
const BUILD_TIME_PLATFORM_BASE_URL = STAGING_PLATFORM_BASE_URL;
function getComfyApiBaseUrl() {
  {
    return BUILD_TIME_API_BASE_URL;
  }
}
__name(getComfyApiBaseUrl, "getComfyApiBaseUrl");
function getComfyPlatformBaseUrl() {
  {
    return BUILD_TIME_PLATFORM_BASE_URL;
  }
}
__name(getComfyPlatformBaseUrl, "getComfyPlatformBaseUrl");
const STORAGE_KEY$1 = "comfy_api_key";
const useApiKeyAuthStore = defineStore("apiKeyAuth", () => {
  const firebaseAuthStore = useFirebaseAuthStore();
  const apiKey = useLocalStorage(STORAGE_KEY$1, null);
  const toastStore = useToastStore();
  const { wrapWithErrorHandlingAsync, toastErrorHandler } = useErrorHandling();
  const currentUser = ref(null);
  const isAuthenticated = computed(() => !!currentUser.value);
  const initializeUserFromApiKey = /* @__PURE__ */ __name(async () => {
    const createCustomerResponse = await firebaseAuthStore.createCustomer().catch((err) => {
      console.error(err);
      return;
    });
    if (!createCustomerResponse) {
      apiKey.value = null;
      throw new Error(t("auth.login.noAssociatedUser"));
    }
    currentUser.value = createCustomerResponse;
  }, "initializeUserFromApiKey");
  watch(
    apiKey,
    () => {
      if (apiKey.value) {
        void initializeUserFromApiKey();
      } else {
        currentUser.value = null;
      }
    },
    { immediate: true }
  );
  const reportError = /* @__PURE__ */ __name((error) => {
    if (error instanceof Error && error.message === "STORAGE_FAILED") {
      toastStore.add({
        severity: "error",
        summary: t("auth.apiKey.storageFailed"),
        detail: t("auth.apiKey.storageFailedDetail")
      });
    } else {
      toastErrorHandler(error);
    }
  }, "reportError");
  const storeApiKey = wrapWithErrorHandlingAsync(async (newApiKey) => {
    apiKey.value = newApiKey;
    toastStore.add({
      severity: "success",
      summary: t("auth.apiKey.stored"),
      detail: t("auth.apiKey.storedDetail"),
      life: 5e3
    });
    return true;
  }, reportError);
  const clearStoredApiKey = wrapWithErrorHandlingAsync(async () => {
    apiKey.value = null;
    toastStore.add({
      severity: "success",
      summary: t("auth.apiKey.cleared"),
      detail: t("auth.apiKey.clearedDetail"),
      life: 5e3
    });
    return true;
  }, reportError);
  const getApiKey = /* @__PURE__ */ __name(() => apiKey.value, "getApiKey");
  const getAuthHeader = /* @__PURE__ */ __name(() => {
    const comfyOrgApiKey = getApiKey();
    if (comfyOrgApiKey) {
      return {
        "X-API-KEY": comfyOrgApiKey
      };
    }
    return null;
  }, "getAuthHeader");
  return {
    // State
    currentUser,
    isAuthenticated,
    // Actions
    storeApiKey,
    clearStoredApiKey,
    getAuthHeader,
    getApiKey
  };
});
class FirebaseAuthStoreError extends Error {
  static {
    __name(this, "FirebaseAuthStoreError");
  }
  constructor(message) {
    super(message);
    this.name = "FirebaseAuthStoreError";
  }
}
const useFirebaseAuthStore = defineStore("firebaseAuth", () => {
  const loading = ref(false);
  const currentUser = ref(null);
  const isInitialized2 = ref(false);
  const customerCreated = ref(false);
  const isFetchingBalance = ref(false);
  const balance = ref(null);
  const lastBalanceUpdateTime = ref(null);
  const tokenRefreshTrigger = ref(0);
  const lastTokenUserId = ref(null);
  const buildApiUrl = /* @__PURE__ */ __name((path) => `${getComfyApiBaseUrl()}${path}`, "buildApiUrl");
  const googleProvider = new GoogleAuthProvider();
  googleProvider.addScope("email");
  googleProvider.setCustomParameters({
    prompt: "select_account"
  });
  const githubProvider = new GithubAuthProvider();
  githubProvider.addScope("user:email");
  githubProvider.setCustomParameters({
    prompt: "select_account"
  });
  const isAuthenticated = computed(() => !!currentUser.value);
  const userEmail = computed(() => currentUser.value?.email);
  const userId = computed(() => currentUser.value?.uid);
  const auth2 = useFirebaseAuth();
  void setPersistence(auth2, browserLocalPersistence);
  onAuthStateChanged(auth2, (user) => {
    currentUser.value = user;
    isInitialized2.value = true;
    if (user === null) {
      lastTokenUserId.value = null;
    }
    balance.value = null;
    lastBalanceUpdateTime.value = null;
  });
  onIdTokenChanged(auth2, (user) => {
  });
  const getIdToken = /* @__PURE__ */ __name(async () => {
    if (!currentUser.value) return;
    try {
      return await currentUser.value.getIdToken();
    } catch (error) {
      if (error instanceof FirebaseError && error.code === AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY.NETWORK_REQUEST_FAILED) {
        console.warn(
          "Could not authenticate with Firebase. Features requiring authentication might not work."
        );
        return;
      }
      useDialogService().showErrorDialog(error, {
        title: t("errorDialog.defaultTitle"),
        reportType: "authenticationError"
      });
      console.error(error);
    }
  }, "getIdToken");
  const getAuthHeader = /* @__PURE__ */ __name(async () => {
    const token = await getIdToken();
    if (token) {
      return {
        Authorization: `Bearer ${token}`
      };
    }
    return useApiKeyAuthStore().getAuthHeader();
  }, "getAuthHeader");
  const fetchBalance = /* @__PURE__ */ __name(async () => {
    isFetchingBalance.value = true;
    try {
      const authHeader = await getAuthHeader();
      if (!authHeader) {
        throw new FirebaseAuthStoreError(
          t("toastMessages.userNotAuthenticated")
        );
      }
      const response = await fetch(buildApiUrl("/customers/balance"), {
        headers: {
          ...authHeader,
          "Content-Type": "application/json"
        }
      });
      if (!response.ok) {
        if (response.status === 404) {
          return null;
        }
        const errorData = await response.json();
        throw new FirebaseAuthStoreError(
          t("toastMessages.failedToFetchBalance", {
            error: errorData.message
          })
        );
      }
      const balanceData = await response.json();
      lastBalanceUpdateTime.value = /* @__PURE__ */ new Date();
      balance.value = balanceData;
      return balanceData;
    } finally {
      isFetchingBalance.value = false;
    }
  }, "fetchBalance");
  const createCustomer = /* @__PURE__ */ __name(async () => {
    const authHeader = await getAuthHeader();
    if (!authHeader) {
      throw new FirebaseAuthStoreError(t("toastMessages.userNotAuthenticated"));
    }
    const createCustomerRes = await fetch(buildApiUrl("/customers"), {
      method: "POST",
      headers: {
        ...authHeader,
        "Content-Type": "application/json"
      }
    });
    if (!createCustomerRes.ok) {
      throw new FirebaseAuthStoreError(
        t("toastMessages.failedToCreateCustomer", {
          error: createCustomerRes.statusText
        })
      );
    }
    const createCustomerResJson = await createCustomerRes.json();
    if (!createCustomerResJson?.id) {
      throw new FirebaseAuthStoreError(
        t("toastMessages.failedToCreateCustomer", {
          error: "No customer ID returned"
        })
      );
    }
    return createCustomerResJson;
  }, "createCustomer");
  const executeAuthAction = /* @__PURE__ */ __name(async (action, options = {}) => {
    loading.value = true;
    try {
      const result = await action(auth2);
      if (options?.createCustomer) {
        const token = await getIdToken();
        if (!token) {
          throw new Error("Cannot create customer: User not authenticated");
        }
        await createCustomer();
      }
      return result;
    } finally {
      loading.value = false;
    }
  }, "executeAuthAction");
  const login = /* @__PURE__ */ __name(async (email, password) => {
    const result = await executeAuthAction(
      (authInstance) => signInWithEmailAndPassword(authInstance, email, password),
      { createCustomer: true }
    );
    return result;
  }, "login");
  const register = /* @__PURE__ */ __name(async (email, password) => {
    const result = await executeAuthAction(
      (authInstance) => createUserWithEmailAndPassword(authInstance, email, password),
      { createCustomer: true }
    );
    return result;
  }, "register");
  const loginWithGoogle = /* @__PURE__ */ __name(async () => {
    const result = await executeAuthAction(
      (authInstance) => signInWithPopup(authInstance, googleProvider),
      { createCustomer: true }
    );
    return result;
  }, "loginWithGoogle");
  const loginWithGithub = /* @__PURE__ */ __name(async () => {
    const result = await executeAuthAction(
      (authInstance) => signInWithPopup(authInstance, githubProvider),
      { createCustomer: true }
    );
    return result;
  }, "loginWithGithub");
  const logout = /* @__PURE__ */ __name(async () => executeAuthAction((authInstance) => signOut(authInstance)), "logout");
  const sendPasswordReset = /* @__PURE__ */ __name(async (email) => executeAuthAction(
    (authInstance) => sendPasswordResetEmail(authInstance, email)
  ), "sendPasswordReset");
  const _updatePassword = /* @__PURE__ */ __name(async (newPassword) => {
    if (!currentUser.value) {
      throw new FirebaseAuthStoreError(t("toastMessages.userNotAuthenticated"));
    }
    await updatePassword(currentUser.value, newPassword);
  }, "_updatePassword");
  const _deleteAccount = /* @__PURE__ */ __name(async () => {
    if (!currentUser.value) {
      throw new FirebaseAuthStoreError(t("toastMessages.userNotAuthenticated"));
    }
    await deleteUser(currentUser.value);
  }, "_deleteAccount");
  const addCredits = /* @__PURE__ */ __name(async (requestBodyContent) => {
    const authHeader = await getAuthHeader();
    if (!authHeader) {
      throw new FirebaseAuthStoreError(t("toastMessages.userNotAuthenticated"));
    }
    if (!customerCreated.value) {
      await createCustomer();
      customerCreated.value = true;
    }
    const response = await fetch(buildApiUrl("/customers/credit"), {
      method: "POST",
      headers: {
        ...authHeader,
        "Content-Type": "application/json"
      },
      body: JSON.stringify(requestBodyContent)
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new FirebaseAuthStoreError(
        t("toastMessages.failedToInitiateCreditPurchase", {
          error: errorData.message
        })
      );
    }
    return response.json();
  }, "addCredits");
  const initiateCreditPurchase = /* @__PURE__ */ __name(async (requestBodyContent) => executeAuthAction((_) => addCredits(requestBodyContent)), "initiateCreditPurchase");
  const accessBillingPortal = /* @__PURE__ */ __name(async (targetTier) => {
    const authHeader = await getAuthHeader();
    if (!authHeader) {
      throw new FirebaseAuthStoreError(t("toastMessages.userNotAuthenticated"));
    }
    const requestBody = targetTier ? { target_tier: targetTier } : void 0;
    const response = await fetch(buildApiUrl("/customers/billing"), {
      method: "POST",
      headers: {
        ...authHeader,
        "Content-Type": "application/json"
      },
      ...requestBody && {
        body: JSON.stringify(requestBody)
      }
    });
    if (!response.ok) {
      const errorData = await response.json();
      throw new FirebaseAuthStoreError(
        t("toastMessages.failedToAccessBillingPortal", {
          error: errorData.message
        })
      );
    }
    return response.json();
  }, "accessBillingPortal");
  return {
    // State
    loading,
    currentUser,
    isInitialized: isInitialized2,
    balance,
    lastBalanceUpdateTime,
    isFetchingBalance,
    tokenRefreshTrigger,
    // Getters
    isAuthenticated,
    userEmail,
    userId,
    // Actions
    login,
    register,
    logout,
    createCustomer,
    getIdToken,
    loginWithGoogle,
    loginWithGithub,
    initiateCreditPurchase,
    fetchBalance,
    accessBillingPortal,
    sendPasswordReset,
    updatePassword: _updatePassword,
    deleteAccount: _deleteAccount,
    getAuthHeader
  };
});
const TIER_TO_KEY = {
  STANDARD: "standard",
  CREATOR: "creator",
  PRO: "pro",
  FOUNDERS_EDITION: "founder"
};
const TIER_PRICING = {
  standard: { monthly: 20, yearly: 16, credits: 4200, videoEstimate: 164 },
  creator: { monthly: 35, yearly: 28, credits: 7400, videoEstimate: 288 },
  pro: { monthly: 100, yearly: 80, credits: 21100, videoEstimate: 821 }
};
const MAX_CANCELLATION_ATTEMPTS = 4;
const CANCELLATION_BASE_DELAY_MS = 5e3;
const CANCELLATION_BACKOFF_MULTIPLIER = 3;
function useSubscriptionCancellationWatcher({
  fetchStatus,
  isActiveSubscription,
  subscriptionStatus,
  telemetry,
  shouldWatchCancellation
}) {
  const watcherActive = ref(false);
  const cancellationAttempts = ref(0);
  const cancellationTracked = ref(false);
  const cancellationCheckInFlight = ref(false);
  const nextDelay = ref(CANCELLATION_BASE_DELAY_MS);
  let detachFocusListener = null;
  const { start: startTimer, stop: stopTimer } = useTimeoutFn(
    () => {
      void checkForCancellation();
    },
    nextDelay,
    { immediate: false }
  );
  const stopCancellationWatcher = /* @__PURE__ */ __name(() => {
    watcherActive.value = false;
    stopTimer();
    cancellationAttempts.value = 0;
    cancellationCheckInFlight.value = false;
    if (detachFocusListener) {
      detachFocusListener();
      detachFocusListener = null;
    }
  }, "stopCancellationWatcher");
  const scheduleNextCancellationCheck = /* @__PURE__ */ __name(() => {
    if (!watcherActive.value) return;
    if (cancellationAttempts.value >= MAX_CANCELLATION_ATTEMPTS) {
      stopCancellationWatcher();
      return;
    }
    nextDelay.value = CANCELLATION_BASE_DELAY_MS * CANCELLATION_BACKOFF_MULTIPLIER ** cancellationAttempts.value;
    cancellationAttempts.value += 1;
    startTimer();
  }, "scheduleNextCancellationCheck");
  const checkForCancellation = /* @__PURE__ */ __name(async (triggeredFromFocus = false) => {
    if (!watcherActive.value || cancellationCheckInFlight.value) return;
    cancellationCheckInFlight.value = true;
    try {
      await fetchStatus();
      if (!isActiveSubscription.value) {
        if (!cancellationTracked.value) {
          cancellationTracked.value = true;
          try {
            telemetry?.trackMonthlySubscriptionCancelled();
          } catch (telemetryError) {
            console.error(
              "[Subscription] Failed to track cancellation telemetry:",
              telemetryError
            );
          }
        }
        stopCancellationWatcher();
        return;
      }
      if (!triggeredFromFocus) {
        scheduleNextCancellationCheck();
      }
    } catch (error) {
      console.error("[Subscription] Error checking cancellation status:", error);
      scheduleNextCancellationCheck();
    } finally {
      cancellationCheckInFlight.value = false;
    }
  }, "checkForCancellation");
  const startCancellationWatcher = /* @__PURE__ */ __name(() => {
    if (!shouldWatchCancellation() || !subscriptionStatus.value?.is_active) {
      return;
    }
    stopCancellationWatcher();
    watcherActive.value = true;
    cancellationTracked.value = false;
    cancellationAttempts.value = 0;
    if (!detachFocusListener && defaultWindow) {
      detachFocusListener = useEventListener(defaultWindow, "focus", () => {
        if (!watcherActive.value) return;
        void checkForCancellation(true);
      });
    }
    scheduleNextCancellationCheck();
  }, "startCancellationWatcher");
  onScopeDispose(() => {
    stopCancellationWatcher();
  });
  return {
    startCancellationWatcher,
    stopCancellationWatcher
  };
}
__name(useSubscriptionCancellationWatcher, "useSubscriptionCancellationWatcher");
function useSubscriptionInternal() {
  const subscriptionStatus = ref(null);
  const telemetry = useTelemetry();
  const isSubscribedOrIsNotCloud = computed(() => {
    return true;
  });
  const { reportError, accessBillingPortal } = useFirebaseAuthActions();
  const { showSubscriptionRequiredDialog } = useDialogService();
  const { getAuthHeader } = useFirebaseAuthStore();
  const { wrapWithErrorHandlingAsync } = useErrorHandling();
  const { isLoggedIn } = useCurrentUser();
  const isCancelled = computed(() => {
    return !!subscriptionStatus.value?.end_date;
  });
  const formattedRenewalDate = computed(() => {
    if (!subscriptionStatus.value?.renewal_date) return "";
    const renewalDate = new Date(subscriptionStatus.value.renewal_date);
    return renewalDate.toLocaleDateString("en-US", {
      month: "short",
      day: "numeric",
      year: "numeric"
    });
  });
  const formattedEndDate = computed(() => {
    if (!subscriptionStatus.value?.end_date) return "";
    const endDate = new Date(subscriptionStatus.value.end_date);
    return endDate.toLocaleDateString("en-US", {
      month: "short",
      day: "numeric",
      year: "numeric"
    });
  });
  const subscriptionTier = computed(
    () => subscriptionStatus.value?.subscription_tier ?? null
  );
  const subscriptionDuration = computed(
    () => subscriptionStatus.value?.subscription_duration ?? null
  );
  const isYearlySubscription = computed(
    () => subscriptionDuration.value === "ANNUAL"
  );
  const subscriptionTierName = computed(() => {
    const tier = subscriptionTier.value;
    if (!tier) return "";
    const key = TIER_TO_KEY[tier] ?? "standard";
    const baseName = t(`subscription.tiers.${key}.name`);
    return isYearlySubscription.value ? t("subscription.tierNameYearly", { name: baseName }) : baseName;
  });
  const buildApiUrl = /* @__PURE__ */ __name((path) => `${getComfyApiBaseUrl()}${path}`, "buildApiUrl");
  const fetchStatus = wrapWithErrorHandlingAsync(
    fetchSubscriptionStatus,
    reportError
  );
  const subscribe = wrapWithErrorHandlingAsync(async () => {
    const response = await initiateSubscriptionCheckout();
    if (!response.checkout_url) {
      throw new Error(
        t("toastMessages.failedToInitiateSubscription", {
          error: "No checkout URL returned"
        })
      );
    }
    window.open(response.checkout_url, "_blank");
  }, reportError);
  const showSubscriptionDialog = /* @__PURE__ */ __name(() => {
    void showSubscriptionRequiredDialog();
  }, "showSubscriptionDialog");
  const shouldWatchCancellation = /* @__PURE__ */ __name(() => Boolean(isCloud), "shouldWatchCancellation");
  const { startCancellationWatcher, stopCancellationWatcher } = useSubscriptionCancellationWatcher({
    fetchStatus,
    isActiveSubscription: isSubscribedOrIsNotCloud,
    subscriptionStatus,
    telemetry,
    shouldWatchCancellation
  });
  const manageSubscription = /* @__PURE__ */ __name(async () => {
    await accessBillingPortal();
    startCancellationWatcher();
  }, "manageSubscription");
  const requireActiveSubscription = /* @__PURE__ */ __name(async () => {
    await fetchSubscriptionStatus();
    if (!isSubscribedOrIsNotCloud.value) {
      showSubscriptionDialog();
    }
  }, "requireActiveSubscription");
  const handleViewUsageHistory = /* @__PURE__ */ __name(() => {
    window.open(`${getComfyPlatformBaseUrl()}/profile/usage`, "_blank");
  }, "handleViewUsageHistory");
  const handleLearnMore = /* @__PURE__ */ __name(() => {
    window.open("https://docs.comfy.org", "_blank");
  }, "handleLearnMore");
  const handleInvoiceHistory = /* @__PURE__ */ __name(async () => {
    await accessBillingPortal();
  }, "handleInvoiceHistory");
  async function fetchSubscriptionStatus() {
    const authHeader = await getAuthHeader();
    if (!authHeader) {
      throw new FirebaseAuthStoreError(t("toastMessages.userNotAuthenticated"));
    }
    const response = await fetch(
      buildApiUrl("/customers/cloud-subscription-status"),
      {
        headers: {
          ...authHeader,
          "Content-Type": "application/json"
        }
      }
    );
    if (!response.ok) {
      const errorData = await response.json();
      throw new FirebaseAuthStoreError(
        t("toastMessages.failedToFetchSubscription", {
          error: errorData.message
        })
      );
    }
    const statusData = await response.json();
    subscriptionStatus.value = statusData;
    return statusData;
  }
  __name(fetchSubscriptionStatus, "fetchSubscriptionStatus");
  watch(
    () => isLoggedIn.value,
    async (loggedIn) => {
      if (loggedIn) {
        await fetchSubscriptionStatus();
      } else {
        subscriptionStatus.value = null;
        stopCancellationWatcher();
      }
    },
    { immediate: true }
  );
  const initiateSubscriptionCheckout = /* @__PURE__ */ __name(async () => {
    const authHeader = await getAuthHeader();
    if (!authHeader) {
      throw new FirebaseAuthStoreError(
        t("toastMessages.userNotAuthenticated")
      );
    }
    const response = await fetch(
      buildApiUrl("/customers/cloud-subscription-checkout"),
      {
        method: "POST",
        headers: {
          ...authHeader,
          "Content-Type": "application/json"
        }
      }
    );
    if (!response.ok) {
      const errorData = await response.json();
      throw new FirebaseAuthStoreError(
        t("toastMessages.failedToInitiateSubscription", {
          error: errorData.message
        })
      );
    }
    return response.json();
  }, "initiateSubscriptionCheckout");
  return {
    // State
    isActiveSubscription: isSubscribedOrIsNotCloud,
    isCancelled,
    formattedRenewalDate,
    formattedEndDate,
    subscriptionTier,
    subscriptionDuration,
    isYearlySubscription,
    subscriptionTierName,
    subscriptionStatus,
    // Actions
    subscribe,
    fetchStatus,
    showSubscriptionDialog,
    manageSubscription,
    requireActiveSubscription,
    handleViewUsageHistory,
    handleLearnMore,
    handleInvoiceHistory
  };
}
__name(useSubscriptionInternal, "useSubscriptionInternal");
const useSubscription = createSharedComposable(useSubscriptionInternal);
const useFirebaseAuthActions = /* @__PURE__ */ __name(() => {
  const authStore = useFirebaseAuthStore();
  const toastStore = useToastStore();
  const { wrapWithErrorHandlingAsync, toastErrorHandler } = useErrorHandling();
  const accessError = ref(false);
  const reportError = /* @__PURE__ */ __name((error) => {
    if (error instanceof FirebaseError && [
      "auth/unauthorized-domain",
      "auth/invalid-dynamic-link-domain",
      "auth/unauthorized-continue-uri"
    ].includes(error.code)) {
      accessError.value = true;
      toastStore.add({
        severity: "error",
        summary: t("g.error"),
        detail: t("toastMessages.unauthorizedDomain", {
          domain: window.location.hostname,
          email: "support@comfy.org"
        })
      });
    } else {
      toastErrorHandler(error);
    }
  }, "reportError");
  const logout = wrapWithErrorHandlingAsync(async () => {
    await authStore.logout();
    toastStore.add({
      severity: "success",
      summary: t("auth.signOut.success"),
      detail: t("auth.signOut.successDetail"),
      life: 5e3
    });
  }, reportError);
  const sendPasswordReset = wrapWithErrorHandlingAsync(
    async (email) => {
      await authStore.sendPasswordReset(email);
      toastStore.add({
        severity: "success",
        summary: t("auth.login.passwordResetSent"),
        detail: t("auth.login.passwordResetSentDetail"),
        life: 5e3
      });
    },
    reportError
  );
  const purchaseCredits = wrapWithErrorHandlingAsync(async (amount) => {
    const { isActiveSubscription } = useSubscription();
    if (!isActiveSubscription.value) return;
    const response = await authStore.initiateCreditPurchase({
      amount_micros: usdToMicros(amount),
      currency: "usd"
    });
    if (!response.checkout_url) {
      throw new Error(
        t("toastMessages.failedToPurchaseCredits", {
          error: "No checkout URL returned"
        })
      );
    }
    window.open(response.checkout_url, "_blank");
  }, reportError);
  const accessBillingPortal = wrapWithErrorHandlingAsync(async (targetTier) => {
    const response = await authStore.accessBillingPortal(targetTier);
    if (!response.billing_portal_url) {
      throw new Error(
        t("toastMessages.failedToAccessBillingPortal", {
          error: "No billing portal URL returned"
        })
      );
    }
    window.open(response.billing_portal_url, "_blank");
  }, reportError);
  const fetchBalance = wrapWithErrorHandlingAsync(async () => {
    const result = await authStore.fetchBalance();
    return result;
  }, reportError);
  const signInWithGoogle = wrapWithErrorHandlingAsync(async () => {
    return await authStore.loginWithGoogle();
  }, reportError);
  const signInWithGithub = wrapWithErrorHandlingAsync(async () => {
    return await authStore.loginWithGithub();
  }, reportError);
  const signInWithEmail = wrapWithErrorHandlingAsync(
    async (email, password) => {
      return await authStore.login(email, password);
    },
    reportError
  );
  const signUpWithEmail = wrapWithErrorHandlingAsync(
    async (email, password) => {
      return await authStore.register(email, password);
    },
    reportError
  );
  const createReauthenticationRecovery = /* @__PURE__ */ __name(() => {
    const dialogService = useDialogService();
    return {
      shouldHandle: /* @__PURE__ */ __name((error) => error instanceof FirebaseError && error.code === AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY.CREDENTIAL_TOO_OLD_LOGIN_AGAIN, "shouldHandle"),
      recover: /* @__PURE__ */ __name(async (_error, retry, args) => {
        const confirmed = await dialogService.confirm({
          title: t("auth.reauthRequired.title"),
          message: t("auth.reauthRequired.message"),
          type: "default"
        });
        if (!confirmed) {
          return;
        }
        await authStore.logout();
        const signedIn = await dialogService.showSignInDialog();
        if (signedIn) {
          await retry(...args);
        }
      }, "recover")
    };
  }, "createReauthenticationRecovery");
  const updatePassword2 = wrapWithErrorHandlingAsync(
    async (newPassword) => {
      await authStore.updatePassword(newPassword);
      toastStore.add({
        severity: "success",
        summary: t("auth.passwordUpdate.success"),
        detail: t("auth.passwordUpdate.successDetail"),
        life: 5e3
      });
    },
    reportError,
    void 0,
    [createReauthenticationRecovery()]
  );
  const deleteAccount = wrapWithErrorHandlingAsync(
    async () => {
      await authStore.deleteAccount();
      toastStore.add({
        severity: "success",
        summary: t("auth.deleteAccount.success"),
        detail: t("auth.deleteAccount.successDetail"),
        life: 5e3
      });
    },
    reportError,
    void 0,
    [createReauthenticationRecovery()]
  );
  return {
    logout,
    sendPasswordReset,
    purchaseCredits,
    accessBillingPortal,
    fetchBalance,
    signInWithGoogle,
    signInWithGithub,
    signInWithEmail,
    signUpWithEmail,
    updatePassword: updatePassword2,
    deleteAccount,
    accessError,
    reportError
  };
}, "useFirebaseAuthActions");
const RESERVED_BY_TEXT_INPUT = /* @__PURE__ */ new Set([
  "Ctrl + a",
  "Ctrl + c",
  "Ctrl + v",
  "Ctrl + x",
  "Ctrl + z",
  "Ctrl + y",
  "Ctrl + p",
  "Enter",
  "Shift + Enter",
  "Ctrl + Backspace",
  "Ctrl + Delete",
  "Home",
  "Ctrl + Home",
  "Ctrl + Shift + Home",
  "End",
  "Ctrl + End",
  "Ctrl + Shift + End",
  "PageUp",
  "PageDown",
  "Shift + PageUp",
  "Shift + PageDown",
  "ArrowLeft",
  "Ctrl + ArrowLeft",
  "Shift + ArrowLeft",
  "Ctrl + Shift + ArrowLeft",
  "ArrowRight",
  "Ctrl + ArrowRight",
  "Shift + ArrowRight",
  "Ctrl + Shift + ArrowRight",
  "ArrowUp",
  "Shift + ArrowUp",
  "ArrowDown",
  "Shift + ArrowDown"
]);
class KeybindingImpl {
  static {
    __name(this, "KeybindingImpl");
  }
  commandId;
  combo;
  targetElementId;
  constructor(obj) {
    this.commandId = obj.commandId;
    this.combo = new KeyComboImpl(obj.combo);
    this.targetElementId = obj.targetElementId;
  }
  equals(other) {
    const raw = toRaw(other);
    return raw instanceof KeybindingImpl ? this.commandId === raw.commandId && this.combo.equals(raw.combo) && this.targetElementId === raw.targetElementId : false;
  }
}
class KeyComboImpl {
  static {
    __name(this, "KeyComboImpl");
  }
  key;
  // ctrl or meta(cmd on mac)
  ctrl;
  alt;
  shift;
  constructor(obj) {
    this.key = obj.key;
    this.ctrl = obj.ctrl ?? false;
    this.alt = obj.alt ?? false;
    this.shift = obj.shift ?? false;
  }
  static fromEvent(event) {
    return new KeyComboImpl({
      key: event.key,
      ctrl: event.ctrlKey || event.metaKey,
      alt: event.altKey,
      shift: event.shiftKey
    });
  }
  equals(other) {
    const raw = toRaw(other);
    return raw instanceof KeyComboImpl ? this.key.toUpperCase() === raw.key.toUpperCase() && this.ctrl === raw.ctrl && this.alt === raw.alt && this.shift === raw.shift : false;
  }
  serialize() {
    return `${this.key.toUpperCase()}:${this.ctrl}:${this.alt}:${this.shift}`;
  }
  toString() {
    return this.getKeySequences().join(" + ");
  }
  get hasModifier() {
    return this.ctrl || this.alt || this.shift;
  }
  get isModifier() {
    return ["Control", "Meta", "Alt", "Shift"].includes(this.key);
  }
  get modifierCount() {
    const modifiers = [this.ctrl, this.alt, this.shift];
    return modifiers.reduce((acc, cur) => acc + Number(cur), 0);
  }
  get isShiftOnly() {
    return this.shift && this.modifierCount === 1;
  }
  get isReservedByTextInput() {
    return !this.hasModifier || this.isShiftOnly || RESERVED_BY_TEXT_INPUT.has(this.toString());
  }
  getKeySequences() {
    const sequences = [];
    if (this.ctrl) {
      sequences.push("Ctrl");
    }
    if (this.alt) {
      sequences.push("Alt");
    }
    if (this.shift) {
      sequences.push("Shift");
    }
    sequences.push(this.key);
    return sequences;
  }
}
const useKeybindingStore = defineStore("keybinding", () => {
  const defaultKeybindings = ref({});
  const userKeybindings = ref({});
  const userUnsetKeybindings = ref({});
  function getUserKeybindings() {
    return userKeybindings.value;
  }
  __name(getUserKeybindings, "getUserKeybindings");
  function getUserUnsetKeybindings() {
    return userUnsetKeybindings.value;
  }
  __name(getUserUnsetKeybindings, "getUserUnsetKeybindings");
  const keybindingByKeyCombo = computed(() => {
    const result = {
      ...defaultKeybindings.value
    };
    for (const keybinding of Object.values(userUnsetKeybindings.value)) {
      const serializedCombo = keybinding.combo.serialize();
      if (result[serializedCombo]?.equals(keybinding)) {
        delete result[serializedCombo];
      }
    }
    return {
      ...result,
      ...userKeybindings.value
    };
  });
  const keybindings = computed(
    () => Object.values(keybindingByKeyCombo.value)
  );
  function getKeybinding(combo) {
    return keybindingByKeyCombo.value[combo.serialize()];
  }
  __name(getKeybinding, "getKeybinding");
  const keybindingsByCommandId = computed(
    () => {
      return toolkit.groupBy(keybindings.value, "commandId");
    }
  );
  function getKeybindingsByCommandId(commandId) {
    return keybindingsByCommandId.value[commandId] ?? [];
  }
  __name(getKeybindingsByCommandId, "getKeybindingsByCommandId");
  const defaultKeybindingsByCommandId = computed(() => {
    return toolkit.groupBy(Object.values(defaultKeybindings.value), "commandId");
  });
  function getKeybindingByCommandId(commandId) {
    return getKeybindingsByCommandId(commandId)[0];
  }
  __name(getKeybindingByCommandId, "getKeybindingByCommandId");
  function addKeybinding(target, keybinding, { existOk = false }) {
    if (!existOk && keybinding.combo.serialize() in target.value) {
      throw new Error(
        `Keybinding on ${keybinding.combo} already exists on ${target.value[keybinding.combo.serialize()].commandId}`
      );
    }
    target.value[keybinding.combo.serialize()] = keybinding;
  }
  __name(addKeybinding, "addKeybinding");
  function addDefaultKeybinding(keybinding) {
    addKeybinding(defaultKeybindings, keybinding, { existOk: false });
  }
  __name(addDefaultKeybinding, "addDefaultKeybinding");
  function addUserKeybinding(keybinding) {
    const defaultKeybinding = defaultKeybindings.value[keybinding.combo.serialize()];
    const userUnsetKeybinding = userUnsetKeybindings.value[keybinding.combo.serialize()];
    if (keybinding.equals(defaultKeybinding) && keybinding.equals(userUnsetKeybinding)) {
      delete userUnsetKeybindings.value[keybinding.combo.serialize()];
      return;
    }
    if (defaultKeybinding && !defaultKeybinding.equals(userUnsetKeybinding)) {
      unsetKeybinding(defaultKeybinding);
    }
    addKeybinding(userKeybindings, keybinding, { existOk: true });
  }
  __name(addUserKeybinding, "addUserKeybinding");
  function unsetKeybinding(keybinding) {
    const serializedCombo = keybinding.combo.serialize();
    if (!(serializedCombo in keybindingByKeyCombo.value)) {
      console.warn(
        `Trying to unset non-exist keybinding: ${JSON.stringify(keybinding)}`
      );
      return;
    }
    if (userKeybindings.value[serializedCombo]?.equals(keybinding)) {
      delete userKeybindings.value[serializedCombo];
      return;
    }
    if (defaultKeybindings.value[serializedCombo]?.equals(keybinding)) {
      addKeybinding(userUnsetKeybindings, keybinding, { existOk: false });
      return;
    }
    console.warn(`Unset unknown keybinding: ${JSON.stringify(keybinding)}`);
  }
  __name(unsetKeybinding, "unsetKeybinding");
  function updateKeybindingOnCommand(keybinding) {
    const currentKeybinding = getKeybindingByCommandId(keybinding.commandId);
    if (currentKeybinding?.equals(keybinding)) {
      return false;
    }
    if (currentKeybinding) {
      unsetKeybinding(currentKeybinding);
    }
    addUserKeybinding(keybinding);
    return true;
  }
  __name(updateKeybindingOnCommand, "updateKeybindingOnCommand");
  function resetAllKeybindings() {
    userKeybindings.value = {};
    userUnsetKeybindings.value = {};
  }
  __name(resetAllKeybindings, "resetAllKeybindings");
  function resetKeybindingForCommand(commandId) {
    const currentKeybinding = getKeybindingByCommandId(commandId);
    const defaultKeybinding = defaultKeybindingsByCommandId.value[commandId]?.[0];
    if (!defaultKeybinding) {
      if (currentKeybinding) {
        unsetKeybinding(currentKeybinding);
        return true;
      }
      return false;
    }
    if (currentKeybinding?.equals(defaultKeybinding)) {
      return false;
    }
    if (currentKeybinding) {
      unsetKeybinding(currentKeybinding);
    }
    const serializedCombo = defaultKeybinding.combo.serialize();
    if (userUnsetKeybindings.value[serializedCombo]?.equals(defaultKeybinding)) {
      delete userUnsetKeybindings.value[serializedCombo];
    }
    return true;
  }
  __name(resetKeybindingForCommand, "resetKeybindingForCommand");
  function isCommandKeybindingModified(commandId) {
    const currentKeybinding = getKeybindingByCommandId(commandId);
    const defaultKeybinding = defaultKeybindingsByCommandId.value[commandId]?.[0];
    return !(currentKeybinding === void 0 && defaultKeybinding === void 0 || currentKeybinding?.equals(defaultKeybinding));
  }
  __name(isCommandKeybindingModified, "isCommandKeybindingModified");
  return {
    keybindings,
    getUserKeybindings,
    getUserUnsetKeybindings,
    getKeybinding,
    getKeybindingsByCommandId,
    getKeybindingByCommandId,
    addDefaultKeybinding,
    addUserKeybinding,
    unsetKeybinding,
    updateKeybindingOnCommand,
    resetAllKeybindings,
    resetKeybindingForCommand,
    isCommandKeybindingModified
  };
});
class ComfyCommandImpl {
  static {
    __name(this, "ComfyCommandImpl");
  }
  id;
  function;
  _label;
  _icon;
  _tooltip;
  _menubarLabel;
  versionAdded;
  confirmation;
  source;
  active;
  category;
  constructor(command) {
    this.id = command.id;
    this.function = command.function;
    this._label = command.label;
    this._icon = command.icon;
    this._tooltip = command.tooltip;
    this._menubarLabel = command.menubarLabel ?? command.label;
    this.versionAdded = command.versionAdded;
    this.confirmation = command.confirmation;
    this.source = command.source;
    this.active = command.active;
    this.category = command.category;
  }
  get label() {
    return typeof this._label === "function" ? this._label() : this._label;
  }
  get icon() {
    return typeof this._icon === "function" ? this._icon() : this._icon;
  }
  get tooltip() {
    return typeof this._tooltip === "function" ? this._tooltip() : this._tooltip;
  }
  get menubarLabel() {
    return typeof this._menubarLabel === "function" ? this._menubarLabel() : this._menubarLabel;
  }
  get keybinding() {
    return useKeybindingStore().getKeybindingByCommandId(this.id);
  }
}
const useCommandStore = defineStore("command", () => {
  const commandsById = ref({});
  const commands2 = computed(() => Object.values(commandsById.value));
  const registerCommand = /* @__PURE__ */ __name((command) => {
    if (commandsById.value[command.id]) {
      console.warn(`Command ${command.id} already registered`);
    }
    commandsById.value[command.id] = new ComfyCommandImpl(command);
  }, "registerCommand");
  const registerCommands = /* @__PURE__ */ __name((commands22) => {
    for (const command of commands22) {
      registerCommand(command);
    }
  }, "registerCommands");
  const getCommand = /* @__PURE__ */ __name((command) => {
    return commandsById.value[command];
  }, "getCommand");
  const { wrapWithErrorHandlingAsync } = useErrorHandling();
  const execute = /* @__PURE__ */ __name(async (commandId, options) => {
    const command = getCommand(commandId);
    if (command) {
      await wrapWithErrorHandlingAsync(
        () => command.function(options?.metadata),
        options?.errorHandler
      )();
    } else {
      throw new Error(`Command ${commandId} not found`);
    }
  }, "execute");
  const isRegistered = /* @__PURE__ */ __name((command) => {
    return !!commandsById.value[command];
  }, "isRegistered");
  const loadExtensionCommands = /* @__PURE__ */ __name((extension) => {
    if (extension.commands) {
      for (const command of extension.commands) {
        registerCommand({
          ...command,
          source: extension.name
        });
      }
    }
  }, "loadExtensionCommands");
  const formatKeySequence = /* @__PURE__ */ __name((command) => {
    const sequences = command.keybinding?.combo.getKeySequences() || [];
    return sequences.map((seq) => seq.replace(/Control/g, "Ctrl").replace(/Shift/g, "Shift")).join(" + ");
  }, "formatKeySequence");
  return {
    commands: commands2,
    execute,
    getCommand,
    registerCommand,
    registerCommands,
    isRegistered,
    loadExtensionCommands,
    formatKeySequence
  };
});
const useCurrentUser = /* @__PURE__ */ __name(() => {
  const authStore = useFirebaseAuthStore();
  const commandStore = useCommandStore();
  const apiKeyStore = useApiKeyAuthStore();
  const dialogService = useDialogService();
  const { deleteAccount } = useFirebaseAuthActions();
  const firebaseUser = computed(() => authStore.currentUser);
  const isApiKeyLogin = computed(() => apiKeyStore.isAuthenticated);
  const isLoggedIn = computed(
    () => !!isApiKeyLogin.value || firebaseUser.value !== null
  );
  const resolvedUserInfo = computed(() => {
    if (isApiKeyLogin.value && apiKeyStore.currentUser) {
      return { id: apiKeyStore.currentUser.id };
    }
    if (firebaseUser.value) {
      return { id: firebaseUser.value.uid };
    }
    return null;
  });
  const onUserResolved = /* @__PURE__ */ __name((callback) => whenever(resolvedUserInfo, callback, { immediate: true }), "onUserResolved");
  const onTokenRefreshed = /* @__PURE__ */ __name((callback) => whenever(() => authStore.tokenRefreshTrigger, callback), "onTokenRefreshed");
  const onUserLogout = /* @__PURE__ */ __name((callback) => {
    watch(resolvedUserInfo, (user) => {
      if (!user) callback();
    });
  }, "onUserLogout");
  const userDisplayName = computed(() => {
    if (isApiKeyLogin.value) {
      return apiKeyStore.currentUser?.name;
    }
    return firebaseUser.value?.displayName;
  });
  const userEmail = computed(() => {
    if (isApiKeyLogin.value) {
      return apiKeyStore.currentUser?.email;
    }
    return firebaseUser.value?.email;
  });
  const providerName = computed(() => {
    if (isApiKeyLogin.value) {
      return "Comfy API Key";
    }
    const providerId = firebaseUser.value?.providerData[0]?.providerId;
    if (providerId?.includes("google")) {
      return "Google";
    }
    if (providerId?.includes("github")) {
      return "GitHub";
    }
    return providerId;
  });
  const providerIcon = computed(() => {
    if (isApiKeyLogin.value) {
      return "pi pi-key";
    }
    const providerId = firebaseUser.value?.providerData[0]?.providerId;
    if (providerId?.includes("google")) {
      return "pi pi-google";
    }
    if (providerId?.includes("github")) {
      return "pi pi-github";
    }
    return "pi pi-user";
  });
  const isEmailProvider = computed(() => {
    if (isApiKeyLogin.value) {
      return false;
    }
    const providerId = firebaseUser.value?.providerData[0]?.providerId;
    return providerId === "password";
  });
  const userPhotoUrl = computed(() => {
    if (isApiKeyLogin.value) return null;
    return firebaseUser.value?.photoURL;
  });
  const handleSignOut = /* @__PURE__ */ __name(async () => {
    if (isApiKeyLogin.value) {
      await apiKeyStore.clearStoredApiKey();
    } else {
      await commandStore.execute("Comfy.User.SignOut");
    }
  }, "handleSignOut");
  const handleSignIn = /* @__PURE__ */ __name(async () => {
    await commandStore.execute("Comfy.User.OpenSignInDialog");
  }, "handleSignIn");
  const handleDeleteAccount = /* @__PURE__ */ __name(async () => {
    const confirmed = await dialogService.confirm({
      title: t("auth.deleteAccount.confirmTitle"),
      message: t("auth.deleteAccount.confirmMessage"),
      type: "delete"
    });
    if (confirmed) {
      await deleteAccount();
    }
  }, "handleDeleteAccount");
  return {
    loading: authStore.loading,
    isLoggedIn,
    isApiKeyLogin,
    isEmailProvider,
    userDisplayName,
    userEmail,
    userPhotoUrl,
    providerName,
    providerIcon,
    resolvedUserInfo,
    handleSignOut,
    handleSignIn,
    handleDeleteAccount,
    onUserResolved,
    onTokenRefreshed,
    onUserLogout
  };
}, "useCurrentUser");
const zRendererType = z.enum(["LG", "Vue"]);
const zNodeId = z.union([z.number().int(), z.string()]);
const zNodeInputName = z.string();
const zSlotIndex = z.union([
  z.number().int(),
  z.string().transform((val) => parseInt(val)).refine((val) => !isNaN(val), {
    message: "Invalid number"
  })
]);
const zDataType = z.union([z.string(), z.array(z.string()), z.number()]);
const zVector2 = z.union([
  z.object({ 0: z.number(), 1: z.number() }).passthrough().transform((v2) => [v2[0], v2[1]]),
  z.tuple([z.number(), z.number()])
]);
const zModelFile = z.object({
  name: z.string(),
  url: z.string().url(),
  hash: z.string().optional(),
  hash_type: z.string().optional(),
  directory: z.string()
});
const zGraphState = z.object({
  lastGroupId: z.number(),
  lastNodeId: z.number(),
  lastLinkId: z.number(),
  lastRerouteId: z.number()
}).passthrough();
const zComfyLink = z.tuple([
  z.number(),
  // Link id
  zNodeId,
  // Node id of source node
  zSlotIndex,
  // Output slot# of source node
  zNodeId,
  // Node id of destination node
  zSlotIndex,
  // Input slot# of destination node
  zDataType
  // Data type
]);
const zComfyLinkExtension = z.object({
  id: z.number(),
  parentId: z.number()
}).passthrough();
const zComfyLinkObject = z.object({
  id: z.number(),
  origin_id: zNodeId,
  origin_slot: zSlotIndex,
  target_id: zNodeId,
  target_slot: zSlotIndex,
  type: zDataType,
  parentId: z.number().optional()
}).passthrough();
const zReroute = z.object({
  id: z.number(),
  parentId: z.number().optional(),
  pos: zVector2,
  linkIds: z.array(z.number()).nullish(),
  floating: z.object({
    slotType: z.enum(["input", "output"])
  }).optional()
}).passthrough();
const zNodeOutput = z.object({
  name: z.string(),
  type: zDataType,
  links: z.array(z.number()).nullable().optional(),
  slot_index: zSlotIndex.optional()
}).passthrough();
const zNodeInput = z.object({
  name: zNodeInputName,
  type: zDataType,
  link: z.number().nullable().optional(),
  slot_index: zSlotIndex.optional()
}).passthrough();
const zFlags = z.object({
  collapsed: z.boolean().optional(),
  pinned: z.boolean().optional(),
  allow_interaction: z.boolean().optional(),
  horizontal: z.boolean().optional(),
  skip_repeated_outputs: z.boolean().optional()
}).passthrough();
const repoLikeIdPattern = /^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$/;
const githubUsernamePattern = /^(?!-)(?!.*--)[a-zA-Z0-9-]+(?<!-)$/;
const gitHashPattern = /^[0-9a-f]{4,40}$/i;
const semverPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-z-]+(?:\.[\da-z-]+)*))?(?:\+([\da-z-]+(?:\.[\da-z-]+)*))?$/;
const zRepoLikeId = z.string().min(1).max(100).regex(repoLikeIdPattern, {
  message: "ID can only contain ASCII letters, digits, '_', '-', and '.'"
}).refine((id2) => !/^[_\-.]|[_\-.]$/.test(id2), {
  message: "ID must not start or end with '_', '-', or '.'"
});
const zCnrId = zRepoLikeId;
const zGithubRepoName = zRepoLikeId;
const zGithubUsername = z.string().min(1).max(39).regex(githubUsernamePattern, "Invalid GitHub username/org");
const zAuxId = z.string().regex(/^[^/]+\/[^/]+$/, "Invalid format. Must be 'github-user/repo-name'").transform((id2) => id2.split("/")).refine(
  ([username, repo]) => zGithubUsername.safeParse(username).success && zGithubRepoName.safeParse(repo).success,
  "Invalid aux_id: Must be valid 'github-username/github-repo-name'"
).transform(([username, repo]) => `${username}/${repo}`);
const zGitHash = z.string().superRefine((val, ctx) => {
  if (!gitHashPattern.test(val)) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: `Node pack version has invalid Git commit hash: "${val}"`
    });
  }
});
const zSemVer = z.string().superRefine((val, ctx) => {
  if (!semverPattern.test(val)) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      message: `Node pack version has invalid semantic version: "${val}"`
    });
  }
});
const zVersion = z.union([
  z.string().transform((ver) => ver.replace(/^v/, "")).pipe(z.union([zSemVer, zGitHash])),
  z.literal("unknown")
]);
const zProperties = z.object({
  ["Node name for S&R"]: z.string().optional(),
  cnr_id: zCnrId.optional(),
  aux_id: zAuxId.optional(),
  ver: zVersion.optional(),
  models: z.array(zModelFile).optional()
}).passthrough();
const zWidgetValues = z.union([z.array(z.any()), z.record(z.any())]);
const zComfyNode = z.object({
  id: zNodeId,
  type: z.string(),
  pos: zVector2,
  size: zVector2,
  flags: zFlags,
  order: z.number(),
  mode: z.number(),
  inputs: z.array(zNodeInput).optional(),
  outputs: z.array(zNodeOutput).optional(),
  properties: zProperties,
  widgets_values: zWidgetValues.optional(),
  color: z.string().optional(),
  bgcolor: z.string().optional()
}).passthrough();
const zSubgraphIO = zNodeInput.extend({
  /** Slot ID (internal; never changes once instantiated). */
  id: z.string().uuid(),
  /** The data type this slot uses. Unlike nodes, this does not support legacy numeric types. */
  type: z.string(),
  /** Links connected to this slot, or `undefined` if not connected. An output slot should only ever have one link. */
  linkIds: z.array(z.number()).optional()
});
const zSubgraphInstance = z.object({
  id: zNodeId,
  type: z.string().uuid(),
  pos: zVector2,
  size: zVector2,
  flags: zFlags,
  order: z.number(),
  mode: z.number(),
  inputs: z.array(zSubgraphIO).optional(),
  outputs: z.array(zSubgraphIO).optional(),
  widgets_values: zWidgetValues.optional(),
  color: z.string().optional(),
  bgcolor: z.string().optional()
}).passthrough();
const zGroup = z.object({
  id: z.number().optional(),
  title: z.string(),
  bounding: z.tuple([z.number(), z.number(), z.number(), z.number()]),
  color: z.string().optional(),
  font_size: z.number().optional(),
  locked: z.boolean().optional()
}).passthrough();
const zDS = z.object({
  scale: z.number(),
  offset: zVector2
}).passthrough();
const zConfig = z.object({
  links_ontop: z.boolean().optional(),
  align_to_grid: z.boolean().optional()
}).passthrough();
const zExtra = z.object({
  ds: zDS.optional(),
  frontendVersion: z.string().optional(),
  linkExtensions: z.array(zComfyLinkExtension).optional(),
  reroutes: z.array(zReroute).optional(),
  workflowRendererVersion: zRendererType.optional()
}).passthrough();
const zGraphDefinitions = z.object({
  subgraphs: z.lazy(() => z.array(zSubgraphDefinition))
});
const zBaseExportableGraph = z.object({
  /** Unique graph ID.  Automatically generated if not provided. */
  id: z.string().uuid().optional(),
  revision: z.number().optional(),
  config: zConfig.optional().nullable(),
  /** Details of the appearance and location of subgraphs shown in this graph. Similar to */
  subgraphs: z.array(zSubgraphInstance).optional()
});
const zComfyWorkflow = zBaseExportableGraph.extend({
  id: z.string().uuid().optional(),
  revision: z.number().optional(),
  last_node_id: zNodeId,
  last_link_id: z.number(),
  nodes: z.array(zComfyNode),
  links: z.array(zComfyLink),
  floatingLinks: z.array(zComfyLinkObject).optional(),
  groups: z.array(zGroup).optional(),
  config: zConfig.optional().nullable(),
  extra: zExtra.optional().nullable(),
  version: z.number(),
  models: z.array(zModelFile).optional(),
  definitions: zGraphDefinitions.optional()
}).passthrough();
const zComfyWorkflow1 = zBaseExportableGraph.extend({
  id: z.string().uuid().optional(),
  revision: z.number().optional(),
  version: z.literal(1),
  config: zConfig.optional().nullable(),
  state: zGraphState,
  groups: z.array(zGroup).optional(),
  nodes: z.array(zComfyNode),
  links: z.array(zComfyLinkObject).optional(),
  floatingLinks: z.array(zComfyLinkObject).optional(),
  reroutes: z.array(zReroute).optional(),
  extra: zExtra.optional().nullable(),
  models: z.array(zModelFile).optional(),
  definitions: z.object({
    subgraphs: z.lazy(
      () => z.array(zSubgraphDefinition)
    )
  }).optional()
}).passthrough();
const zExportedSubgraphIONode = z.object({
  id: zNodeId,
  bounding: z.tuple([z.number(), z.number(), z.number(), z.number()]),
  pinned: z.boolean().optional()
});
const zExposedWidget = z.object({
  id: z.string(),
  name: z.string()
});
const zSubgraphDefinition = zComfyWorkflow1.extend({
  /** Unique graph ID.  Automatically generated if not provided. */
  id: z.string().uuid(),
  revision: z.number(),
  name: z.string(),
  inputNode: zExportedSubgraphIONode,
  outputNode: zExportedSubgraphIONode,
  /** Ordered list of inputs to the subgraph itself. Similar to a reroute, with the input side in the graph, and the output side in the subgraph. */
  inputs: z.array(zSubgraphIO).optional(),
  /** Ordered list of outputs from the subgraph itself. Similar to a reroute, with the input side in the subgraph, and the output side in the graph. */
  outputs: z.array(zSubgraphIO).optional(),
  /** A list of node widgets displayed in the parent graph, on the subgraph object. */
  widgets: z.array(zExposedWidget).optional(),
  definitions: z.object({
    subgraphs: z.lazy(
      () => zSubgraphDefinition.array()
    )
  }).optional()
}).passthrough();
function isSubgraphDefinition(obj) {
  return obj !== null && typeof obj === "object" && "id" in obj && "name" in obj && "nodes" in obj && Array.isArray(obj.nodes) && "inputNode" in obj && "outputNode" in obj;
}
__name(isSubgraphDefinition, "isSubgraphDefinition");
const zWorkflowVersion = z.object({
  version: z.number()
});
async function validateComfyWorkflow(data, onError = console.warn) {
  const versionResult = zWorkflowVersion.safeParse(data);
  let result;
  if (!versionResult.success) {
    const error2 = fromZodError(versionResult.error);
    onError(`Workflow does not contain a valid version.  Zod error:
${error2}`);
    return null;
  } else if (versionResult.data.version === 1) {
    result = await zComfyWorkflow1.safeParseAsync(data);
  } else {
    result = await zComfyWorkflow.safeParseAsync(data);
  }
  if (result.success) return result.data;
  const error = fromZodError(result.error);
  onError(`Invalid workflow against zod schema:
${error}`);
  return null;
}
__name(validateComfyWorkflow, "validateComfyWorkflow");
const zNodeInputValue = z.union([
  // For widget values (can be any type)
  z.any(),
  // For node links [nodeId, slotIndex]
  z.tuple([zNodeId, zSlotIndex])
]);
const zNodeData = z.object({
  inputs: z.record(zNodeInputName, zNodeInputValue),
  class_type: z.string(),
  _meta: z.object({
    title: z.string()
  })
});
z.record(zNodeId, zNodeData);
const nodeSlotSchema = z.object({
  CLIP: z.string(),
  CLIP_VISION: z.string(),
  CLIP_VISION_OUTPUT: z.string(),
  CONDITIONING: z.string(),
  CONTROL_NET: z.string(),
  IMAGE: z.string(),
  LATENT: z.string(),
  MASK: z.string(),
  MODEL: z.string(),
  STYLE_MODEL: z.string(),
  VAE: z.string(),
  NOISE: z.string(),
  GUIDER: z.string(),
  SAMPLER: z.string(),
  SIGMAS: z.string(),
  TAESD: z.string()
});
const litegraphBaseSchema = z.object({
  BACKGROUND_IMAGE: z.string(),
  CLEAR_BACKGROUND_COLOR: z.string(),
  NODE_TITLE_COLOR: z.string(),
  NODE_SELECTED_TITLE_COLOR: z.string(),
  NODE_TEXT_SIZE: z.number(),
  NODE_TEXT_COLOR: z.string(),
  NODE_TEXT_HIGHLIGHT_COLOR: z.string(),
  NODE_SUBTEXT_SIZE: z.number(),
  NODE_DEFAULT_COLOR: z.string(),
  NODE_DEFAULT_BGCOLOR: z.string(),
  NODE_DEFAULT_BOXCOLOR: z.string(),
  NODE_DEFAULT_SHAPE: z.union([
    z.literal(LiteGraph.BOX_SHAPE),
    z.literal(LiteGraph.ROUND_SHAPE),
    z.literal(LiteGraph.CARD_SHAPE),
    // Legacy palettes have string field for NODE_DEFAULT_SHAPE.
    z.string()
  ]),
  NODE_BOX_OUTLINE_COLOR: z.string(),
  NODE_BYPASS_BGCOLOR: z.string(),
  NODE_ERROR_COLOUR: z.string(),
  DEFAULT_SHADOW_COLOR: z.string(),
  DEFAULT_GROUP_FONT: z.number(),
  WIDGET_BGCOLOR: z.string(),
  WIDGET_OUTLINE_COLOR: z.string(),
  WIDGET_TEXT_COLOR: z.string(),
  WIDGET_SECONDARY_TEXT_COLOR: z.string(),
  WIDGET_DISABLED_TEXT_COLOR: z.string(),
  LINK_COLOR: z.string(),
  EVENT_LINK_COLOR: z.string(),
  CONNECTING_LINK_COLOR: z.string(),
  BADGE_FG_COLOR: z.string(),
  BADGE_BG_COLOR: z.string()
});
const comfyBaseSchema = z.object({
  ["fg-color"]: z.string(),
  ["bg-color"]: z.string(),
  ["bg-img"]: z.string().optional(),
  ["comfy-menu-bg"]: z.string(),
  ["comfy-menu-secondary-bg"]: z.string(),
  ["comfy-input-bg"]: z.string(),
  ["input-text"]: z.string(),
  ["descrip-text"]: z.string(),
  ["drag-text"]: z.string(),
  ["error-text"]: z.string(),
  ["border-color"]: z.string(),
  ["tr-even-bg-color"]: z.string(),
  ["tr-odd-bg-color"]: z.string(),
  ["content-bg"]: z.string(),
  ["content-fg"]: z.string(),
  ["content-hover-bg"]: z.string(),
  ["content-hover-fg"]: z.string(),
  ["bar-shadow"]: z.string(),
  ["contrast-mix-color"]: z.string().optional(),
  ["interface-stroke"]: z.string().optional(),
  ["interface-panel-surface"]: z.string().optional(),
  ["interface-panel-box-shadow"]: z.string().optional(),
  ["interface-panel-drop-shadow"]: z.string().optional(),
  ["interface-panel-hover-surface"]: z.string().optional(),
  ["interface-panel-selected-surface"]: z.string().optional(),
  ["interface-button-hover-surface"]: z.string().optional()
});
const colorsSchema = z.object({
  node_slot: nodeSlotSchema,
  litegraph_base: litegraphBaseSchema,
  comfy_base: comfyBaseSchema
});
const partialColorsSchema = z.object({
  node_slot: nodeSlotSchema.partial(),
  litegraph_base: litegraphBaseSchema.partial(),
  comfy_base: comfyBaseSchema.partial()
});
const paletteSchema = z.object({
  id: z.string(),
  name: z.string(),
  colors: partialColorsSchema,
  light_theme: z.boolean().optional()
}).passthrough();
z.object({
  id: z.string(),
  name: z.string(),
  colors: colorsSchema
}).passthrough();
const colorPalettesSchema = z.record(paletteSchema);
const zKeyCombo = z.object({
  key: z.string(),
  ctrl: z.boolean().optional(),
  alt: z.boolean().optional(),
  shift: z.boolean().optional(),
  meta: z.boolean().optional()
});
const zKeybinding = z.object({
  commandId: z.string(),
  combo: zKeyCombo,
  // Optional target element ID to limit keybinding to.
  // Note: Currently only used to distinguish between global keybindings
  // and litegraph canvas keybindings.
  // Do NOT use this field in extensions as it has no effect.
  targetElementId: z.string().optional()
});
var NodeSourceType = /* @__PURE__ */ ((NodeSourceType2) => {
  NodeSourceType2["Core"] = "core";
  NodeSourceType2["CustomNodes"] = "custom_nodes";
  NodeSourceType2["Blueprint"] = "blueprint";
  NodeSourceType2["Unknown"] = "unknown";
  return NodeSourceType2;
})(NodeSourceType || {});
const UNKNOWN_NODE_SOURCE = {
  type: "unknown",
  className: "comfy-unknown",
  displayText: "Unknown",
  badgeText: "?"
};
const shortenNodeName = /* @__PURE__ */ __name((name2) => {
  return name2.replace(/^(ComfyUI-|ComfyUI_|Comfy-|Comfy_)/, "").replace(/(-ComfyUI|_ComfyUI|-Comfy|_Comfy)$/, "");
}, "shortenNodeName");
const getNodeSource = /* @__PURE__ */ __name((python_module) => {
  if (!python_module) {
    return UNKNOWN_NODE_SOURCE;
  }
  const modules = python_module.split(".");
  if (["nodes", "comfy_extras", "comfy_api_nodes"].includes(modules[0])) {
    return {
      type: "core",
      className: "comfy-core",
      displayText: "Comfy Core",
      badgeText: "🦊"
    };
  } else if (modules[0] === "blueprint") {
    return {
      type: "blueprint",
      className: "blueprint",
      displayText: "Blueprint",
      badgeText: "bp"
    };
  } else if (modules[0] === "custom_nodes") {
    const moduleName = modules[1];
    const customNodeName = moduleName.split("@")[0];
    const displayName = shortenNodeName(customNodeName);
    return {
      type: "custom_nodes",
      className: "comfy-custom-nodes",
      displayText: displayName,
      badgeText: displayName
    };
  } else {
    return UNKNOWN_NODE_SOURCE;
  }
}, "getNodeSource");
var NodeBadgeMode = /* @__PURE__ */ ((NodeBadgeMode2) => {
  NodeBadgeMode2["None"] = "None";
  NodeBadgeMode2["ShowAll"] = "Show all";
  NodeBadgeMode2["HideBuiltIn"] = "Hide built-in";
  return NodeBadgeMode2;
})(NodeBadgeMode || {});
var LinkReleaseTriggerAction = /* @__PURE__ */ ((LinkReleaseTriggerAction2) => {
  LinkReleaseTriggerAction2["CONTEXT_MENU"] = "context menu";
  LinkReleaseTriggerAction2["SEARCH_BOX"] = "search box";
  LinkReleaseTriggerAction2["NO_ACTION"] = "no action";
  return LinkReleaseTriggerAction2;
})(LinkReleaseTriggerAction || {});
const zNodeType = z.string();
const zQueueIndex = z.number();
const zPromptId = z.string();
const resultItemType = z.enum(["input", "output", "temp"]);
const zResultItem = z.object({
  filename: z.string().optional(),
  subfolder: z.string().optional(),
  type: resultItemType.optional()
});
const zOutputs = z.object({
  audio: z.array(zResultItem).optional(),
  images: z.array(zResultItem).optional(),
  video: z.array(zResultItem).optional(),
  animated: z.array(z.boolean()).optional(),
  text: z.union([z.string(), z.array(z.string())]).optional()
}).passthrough();
const zStatusWsMessageStatus = z.object({
  exec_info: z.object({
    queue_remaining: z.number().int()
  })
});
z.object({
  status: zStatusWsMessageStatus.nullish(),
  sid: z.string().nullish()
});
z.object({
  value: z.number().int(),
  max: z.number().int(),
  prompt_id: zPromptId,
  node: zNodeId
});
const zNodeProgressState = z.object({
  value: z.number(),
  max: z.number(),
  state: z.enum(["pending", "running", "finished", "error"]),
  node_id: zNodeId,
  prompt_id: zPromptId,
  display_node_id: zNodeId.optional(),
  parent_node_id: zNodeId.optional(),
  real_node_id: zNodeId.optional()
});
z.object({
  prompt_id: zPromptId,
  nodes: z.record(zNodeId, zNodeProgressState)
});
const zExecutingWsMessage = z.object({
  node: zNodeId,
  display_node: zNodeId,
  prompt_id: zPromptId
});
zExecutingWsMessage.extend({
  output: zOutputs,
  merge: z.boolean().optional()
});
const zExecutionWsMessageBase = z.object({
  prompt_id: zPromptId,
  timestamp: z.number().int()
});
const zExecutionStartWsMessage = zExecutionWsMessageBase;
const zExecutionSuccessWsMessage = zExecutionWsMessageBase;
const zExecutionCachedWsMessage = zExecutionWsMessageBase.extend({
  nodes: z.array(zNodeId)
});
const zExecutionInterruptedWsMessage = zExecutionWsMessageBase.extend({
  node_id: zNodeId,
  node_type: zNodeType,
  executed: z.array(zNodeId)
});
const zExecutionErrorWsMessage = zExecutionWsMessageBase.extend({
  node_id: zNodeId,
  node_type: zNodeType,
  executed: z.array(zNodeId),
  exception_message: z.string(),
  exception_type: z.string(),
  traceback: z.array(z.string()),
  current_inputs: z.any(),
  current_outputs: z.any()
});
z.object({
  nodeId: zNodeId,
  text: z.string()
});
z.object({
  value: z.string(),
  id: z.string().optional()
});
const zTerminalSize = z.object({
  cols: z.number(),
  row: z.number()
});
const zLogEntry = z.object({
  t: z.string(),
  m: z.string()
});
z.object({
  size: zTerminalSize.optional(),
  entries: z.array(zLogEntry)
});
z.object({
  size: zTerminalSize,
  entries: z.array(zLogEntry)
});
z.record(z.string(), z.any());
const zPromptInputItem = z.object({
  inputs: z.record(z.string(), z.any()),
  class_type: zNodeType
});
const zPromptInputs = z.record(zPromptInputItem);
const zExtraPngInfo = z.object({
  workflow: zComfyWorkflow
}).passthrough();
const zExtraData = z.object({
  /** extra_pnginfo can be missing is backend execution gets a validation error. */
  extra_pnginfo: zExtraPngInfo.optional(),
  client_id: z.string().optional(),
  // Cloud/Adapters: creation time in milliseconds when available
  create_time: z.number().int().optional()
}).passthrough();
const zOutputsToExecute = z.array(zNodeId);
const zExecutionStartMessage = z.tuple([
  z.literal("execution_start"),
  zExecutionStartWsMessage
]);
const zExecutionSuccessMessage = z.tuple([
  z.literal("execution_success"),
  zExecutionSuccessWsMessage
]);
const zExecutionCachedMessage = z.tuple([
  z.literal("execution_cached"),
  zExecutionCachedWsMessage
]);
const zExecutionInterruptedMessage = z.tuple([
  z.literal("execution_interrupted"),
  zExecutionInterruptedWsMessage
]);
const zExecutionErrorMessage = z.tuple([
  z.literal("execution_error"),
  zExecutionErrorWsMessage
]);
const zStatusMessage = z.union([
  zExecutionStartMessage,
  zExecutionSuccessMessage,
  zExecutionCachedMessage,
  zExecutionInterruptedMessage,
  zExecutionErrorMessage
]);
const zStatus = z.object({
  status_str: z.enum(["success", "error"]),
  completed: z.boolean(),
  messages: z.array(zStatusMessage)
});
const zTaskPrompt = z.tuple([
  zQueueIndex,
  zPromptId,
  zPromptInputs,
  zExtraData,
  zOutputsToExecute
]);
const zRunningTaskItem = z.object({
  taskType: z.literal("Running"),
  prompt: zTaskPrompt,
  // @Deprecated
  remove: z.object({
    name: z.literal("Cancel"),
    cb: z.function()
  })
});
const zPendingTaskItem = z.object({
  taskType: z.literal("Pending"),
  prompt: zTaskPrompt
});
const zTaskOutput = z.record(zNodeId, zOutputs);
const zNodeOutputsMeta = z.object({
  node_id: zNodeId,
  display_node: zNodeId,
  prompt_id: zPromptId.optional(),
  read_node_id: zNodeId.optional()
});
const zTaskMeta = z.record(zNodeId, zNodeOutputsMeta);
const zHistoryTaskItem = z.object({
  taskType: z.literal("History"),
  prompt: zTaskPrompt,
  status: zStatus.optional(),
  outputs: zTaskOutput,
  meta: zTaskMeta.optional()
});
z.union([
  zRunningTaskItem,
  zPendingTaskItem,
  zHistoryTaskItem
]);
z.union([
  z.literal("Running"),
  z.literal("Pending"),
  z.literal("History")
]);
z.array(z.string());
z.array(z.string());
const zError = z.object({
  type: z.string(),
  message: z.string(),
  details: z.string(),
  extra_info: z.object({
    input_name: z.string().optional()
  }).passthrough().optional()
});
const zNodeError = z.object({
  errors: z.array(zError),
  class_type: z.string(),
  dependent_outputs: z.array(z.any())
});
z.object({
  node_errors: z.record(zNodeId, zNodeError).optional(),
  prompt_id: z.string().optional(),
  exec_info: z.object({
    queue_remaining: z.number().optional()
  }).optional(),
  error: z.union([z.string(), zError])
});
const zDeviceStats = z.object({
  name: z.string(),
  type: z.string(),
  index: z.number(),
  vram_total: z.number(),
  vram_free: z.number(),
  torch_vram_total: z.number(),
  torch_vram_free: z.number()
});
z.object({
  system: z.object({
    os: z.string(),
    python_version: z.string(),
    embedded_python: z.boolean(),
    comfyui_version: z.string(),
    pytorch_version: z.string(),
    required_frontend_version: z.string().optional(),
    argv: z.array(z.string()),
    ram_total: z.number(),
    ram_free: z.number(),
    // Cloud-specific fields
    cloud_version: z.string().optional(),
    comfyui_frontend_version: z.string().optional(),
    workflow_templates_version: z.string().optional()
  }),
  devices: z.array(zDeviceStats)
});
z.object({
  storage: z.enum(["server"]),
  // `migrated` is only available in single-user mode.
  migrated: z.boolean().optional(),
  // `users` is only available in multi-user server mode.
  users: z.record(z.string(), z.string()).optional()
});
z.array(z.array(z.string(), z.string()));
z.object({
  path: z.string(),
  size: z.number(),
  modified: z.number()
});
const zBookmarkCustomization = z.object({
  icon: z.string().optional(),
  color: z.string().optional()
});
const zLinkReleaseTriggerAction = z.enum(
  Object.values(LinkReleaseTriggerAction)
);
const zNodeBadgeMode = z.enum(
  Object.values(NodeBadgeMode)
);
const zPreviewMethod = z.enum([
  "default",
  "none",
  "auto",
  "latent2rgb",
  "taesd"
]);
z.object({
  "Comfy.ColorPalette": z.string(),
  "Comfy.CustomColorPalettes": colorPalettesSchema,
  "Comfy.Canvas.BackgroundImage": z.string().optional(),
  "Comfy.ConfirmClear": z.boolean(),
  "Comfy.DevMode": z.boolean(),
  "Comfy.Workflow.ShowMissingNodesWarning": z.boolean(),
  "Comfy.Workflow.ShowMissingModelsWarning": z.boolean(),
  "Comfy.Workflow.WarnBlueprintOverwrite": z.boolean(),
  "Comfy.DisableFloatRounding": z.boolean(),
  "Comfy.DisableSliders": z.boolean(),
  "Comfy.DOMClippingEnabled": z.boolean(),
  "Comfy.EditAttention.Delta": z.number(),
  "Comfy.EnableTooltips": z.boolean(),
  "Comfy.EnableWorkflowViewRestore": z.boolean(),
  "Comfy.FloatRoundingPrecision": z.number(),
  "Comfy.Graph.CanvasInfo": z.boolean(),
  "Comfy.Graph.CanvasMenu": z.boolean(),
  "Comfy.Graph.CtrlShiftZoom": z.boolean(),
  "Comfy.Graph.LiveSelection": z.boolean(),
  "Comfy.Graph.LinkMarkers": z.nativeEnum(LinkMarkerShape),
  "Comfy.Graph.ZoomSpeed": z.number(),
  "Comfy.Group.DoubleClickTitleToEdit": z.boolean(),
  "Comfy.GroupSelectedNodes.Padding": z.number(),
  "Comfy.Locale": z.string(),
  "Comfy.NodeLibrary.Bookmarks": z.array(z.string()),
  "Comfy.NodeLibrary.Bookmarks.V2": z.array(z.string()),
  "Comfy.NodeLibrary.BookmarksCustomization": z.record(
    z.string(),
    zBookmarkCustomization
  ),
  "Comfy.LinkRelease.Action": zLinkReleaseTriggerAction,
  "Comfy.LinkRelease.ActionShift": zLinkReleaseTriggerAction,
  "Comfy.ModelLibrary.AutoLoadAll": z.boolean(),
  "Comfy.ModelLibrary.NameFormat": z.enum(["filename", "title"]),
  "Comfy.NodeSearchBoxImpl.NodePreview": z.boolean(),
  "Comfy.NodeSearchBoxImpl": z.enum(["default", "simple"]),
  "Comfy.NodeSearchBoxImpl.ShowCategory": z.boolean(),
  "Comfy.NodeSearchBoxImpl.ShowIdName": z.boolean(),
  "Comfy.NodeSearchBoxImpl.ShowNodeFrequency": z.boolean(),
  "Comfy.NodeSuggestions.number": z.number(),
  "Comfy.Node.BypassAllLinksOnDelete": z.boolean(),
  "Comfy.Node.Opacity": z.number(),
  "Comfy.Node.MiddleClickRerouteNode": z.boolean(),
  "Comfy.Node.ShowDeprecated": z.boolean(),
  "Comfy.Node.ShowExperimental": z.boolean(),
  "Comfy.Pointer.ClickBufferTime": z.number(),
  "Comfy.Pointer.ClickDrift": z.number(),
  "Comfy.Pointer.DoubleClickTime": z.number(),
  "Comfy.PreviewFormat": z.string(),
  "Comfy.PromptFilename": z.boolean(),
  "Comfy.Sidebar.Location": z.enum(["left", "right"]),
  "Comfy.Sidebar.Size": z.enum(["small", "normal"]),
  "Comfy.Sidebar.UnifiedWidth": z.boolean(),
  "Comfy.Sidebar.Style": z.enum(["floating", "connected"]),
  "Comfy.SnapToGrid.GridSize": z.number(),
  "Comfy.TextareaWidget.FontSize": z.number(),
  "Comfy.TextareaWidget.Spellcheck": z.boolean(),
  "Comfy.UseNewMenu": z.enum(["Disabled", "Top"]),
  "Comfy.TreeExplorer.ItemPadding": z.number(),
  "Comfy.Validation.Workflows": z.boolean(),
  "Comfy.Workflow.SortNodeIdOnSave": z.boolean(),
  "Comfy.Execution.PreviewMethod": zPreviewMethod,
  "Comfy.Workflow.WorkflowTabsPosition": z.enum(["Sidebar", "Topbar"]),
  "Comfy.Node.DoubleClickTitleToEdit": z.boolean(),
  "Comfy.WidgetControlMode": z.enum(["before", "after"]),
  "Comfy.Window.UnloadConfirmation": z.boolean(),
  "Comfy.NodeBadge.NodeSourceBadgeMode": zNodeBadgeMode,
  "Comfy.NodeBadge.NodeIdBadgeMode": zNodeBadgeMode,
  "Comfy.NodeBadge.NodeLifeCycleBadgeMode": zNodeBadgeMode,
  "Comfy.NodeBadge.ShowApiPricing": z.boolean(),
  "Comfy.Notification.ShowVersionUpdates": z.boolean(),
  "Comfy.QueueButton.BatchCountLimit": z.number(),
  "Comfy.Queue.MaxHistoryItems": z.number(),
  "Comfy.Keybinding.UnsetBindings": z.array(zKeybinding),
  "Comfy.Keybinding.NewBindings": z.array(zKeybinding),
  "Comfy.Extension.Disabled": z.array(z.string()),
  "Comfy.LinkRenderMode": z.number(),
  "Comfy.Node.AutoSnapLinkToSlot": z.boolean(),
  "Comfy.Node.SnapHighlightsNode": z.boolean(),
  "Comfy.Server.ServerConfigValues": z.record(z.string(), z.any()),
  "Comfy.Server.LaunchArgs": z.record(z.string(), z.string()),
  "LiteGraph.Canvas.MaximumFps": z.number(),
  "Comfy.Workflow.ConfirmDelete": z.boolean(),
  "Comfy.Workflow.AutoSaveDelay": z.number(),
  "Comfy.Workflow.AutoSave": z.enum(["off", "after delay"]),
  "Comfy.RerouteBeta": z.boolean(),
  "LiteGraph.Canvas.MinFontSizeForLOD": z.number(),
  "Comfy.Canvas.SelectionToolbox": z.boolean(),
  "LiteGraph.Node.TooltipDelay": z.number(),
  "LiteGraph.ContextMenu.Scaling": z.boolean(),
  "LiteGraph.Reroute.SplineOffset": z.number(),
  "LiteGraph.Canvas.LowQualityRenderingZoomThreshold": z.number(),
  "Comfy.Toast.DisableReconnectingToast": z.boolean(),
  "Comfy.Workflow.Persist": z.boolean(),
  "Comfy.TutorialCompleted": z.boolean(),
  "Comfy.InstalledVersion": z.string().nullable(),
  "Comfy.Node.AllowImageSizeDraw": z.boolean(),
  "Comfy.Minimap.Visible": z.boolean(),
  "Comfy.Minimap.NodeColors": z.boolean(),
  "Comfy.Minimap.ShowLinks": z.boolean(),
  "Comfy.Minimap.ShowGroups": z.boolean(),
  "Comfy.Minimap.RenderBypassState": z.boolean(),
  "Comfy.Minimap.RenderErrorState": z.boolean(),
  "Comfy.Canvas.NavigationMode": z.string(),
  "Comfy.Canvas.LeftMouseClickBehavior": z.string(),
  "Comfy.Canvas.MouseWheelScroll": z.string(),
  "Comfy.VueNodes.Enabled": z.boolean(),
  "Comfy.VueNodes.AutoScaleLayout": z.boolean(),
  "Comfy.Assets.UseAssetAPI": z.boolean(),
  "Comfy-Desktop.AutoUpdate": z.boolean(),
  "Comfy-Desktop.SendStatistics": z.boolean(),
  "Comfy-Desktop.WindowStyle": z.string(),
  "Comfy-Desktop.UV.PythonInstallMirror": z.string(),
  "Comfy-Desktop.UV.PypiInstallMirror": z.string(),
  "Comfy-Desktop.UV.TorchInstallMirror": z.string(),
  "Comfy.MaskEditor.BrushAdjustmentSpeed": z.number(),
  "Comfy.MaskEditor.UseDominantAxis": z.boolean(),
  "Comfy.Load3D.ShowGrid": z.boolean(),
  "Comfy.Load3D.BackgroundColor": z.string(),
  "Comfy.Load3D.LightIntensity": z.number(),
  "Comfy.Load3D.LightIntensityMaximum": z.number(),
  "Comfy.Load3D.LightIntensityMinimum": z.number(),
  "Comfy.Load3D.LightAdjustmentIncrement": z.number(),
  "Comfy.Load3D.CameraType": z.enum(["perspective", "orthographic"]),
  "Comfy.Load3D.3DViewerEnable": z.boolean(),
  "Comfy.Load3D.PLYEngine": z.enum(["threejs", "fastply", "sparkjs"]),
  "Comfy.Memory.AllowManualUnload": z.boolean(),
  "pysssss.SnapToGrid": z.boolean(),
  /** VHS setting is used for queue video preview support. */
  "VHS.AdvancedPreviews": z.string(),
  /** Release data settings */
  "Comfy.Release.Version": z.string(),
  "Comfy.Release.Status": z.enum([
    "skipped",
    "changelog seen",
    "what's new seen"
  ]),
  "Comfy.Release.Timestamp": z.number(),
  /** Template library filter settings */
  "Comfy.Templates.SelectedModels": z.array(z.string()),
  "Comfy.Templates.SelectedUseCases": z.array(z.string()),
  "Comfy.Templates.SelectedRunsOn": z.array(z.string()),
  "Comfy.Templates.SortBy": z.enum([
    "default",
    "alphabetical",
    "newest",
    "vram-low-to-high",
    "model-size-low-to-high"
  ]),
  /** Settings used for testing */
  "test.setting": z.any(),
  "main.sub.setting.name": z.any(),
  "single.setting": z.any(),
  "LiteGraph.Node.DefaultPadding": z.boolean(),
  "LiteGraph.Pointer.TrackpadGestures": z.boolean(),
  "Comfy.VersionCompatibility.DisableWarnings": z.boolean()
});
const zComboOption = z.union([z.string(), z.number()]);
const zRemoteWidgetConfig = z.object({
  route: z.string().url().or(z.string().startsWith("/")),
  refresh: z.number().gte(128).safe().or(z.number().lte(0).safe()).optional(),
  response_key: z.string().optional(),
  query_params: z.record(z.string(), z.string()).optional(),
  refresh_button: z.boolean().optional(),
  control_after_refresh: z.enum(["first", "last"]).optional(),
  timeout: z.number().gte(0).optional(),
  max_retries: z.number().gte(0).optional()
});
const zMultiSelectOption = z.object({
  placeholder: z.string().optional(),
  chip: z.boolean().optional()
});
const zBaseInputOptions = z.object({
  default: z.any().optional(),
  defaultInput: z.boolean().optional(),
  display_name: z.string().optional(),
  forceInput: z.boolean().optional(),
  tooltip: z.string().optional(),
  socketless: z.boolean().optional(),
  hidden: z.boolean().optional(),
  advanced: z.boolean().optional(),
  widgetType: z.string().optional(),
  /** Backend-only properties. */
  rawLink: z.boolean().optional(),
  lazy: z.boolean().optional()
}).passthrough();
const zNumericInputOptions = zBaseInputOptions.extend({
  min: z.number().optional(),
  max: z.number().optional(),
  step: z.number().optional(),
  /** Note: Many node authors are using INT/FLOAT to pass list of INT/FLOAT. */
  default: z.union([z.number(), z.array(z.number())]).optional(),
  display: z.enum(["slider", "number", "knob"]).optional()
});
const zIntInputOptions = zNumericInputOptions.extend({
  /**
   * If true, a linked widget will be added to the node to select the mode
   * of `control_after_generate`.
   */
  control_after_generate: z.boolean().optional()
});
const zFloatInputOptions = zNumericInputOptions.extend({
  round: z.union([z.number(), z.literal(false)]).optional()
});
const zBooleanInputOptions = zBaseInputOptions.extend({
  label_on: z.string().optional(),
  label_off: z.string().optional(),
  default: z.boolean().optional()
});
const zStringInputOptions = zBaseInputOptions.extend({
  default: z.string().optional(),
  multiline: z.boolean().optional(),
  dynamicPrompts: z.boolean().optional(),
  // Multiline-only fields
  defaultVal: z.string().optional(),
  placeholder: z.string().optional()
});
const zComboInputOptions = zBaseInputOptions.extend({
  control_after_generate: z.boolean().optional(),
  image_upload: z.boolean().optional(),
  image_folder: resultItemType.optional(),
  allow_batch: z.boolean().optional(),
  video_upload: z.boolean().optional(),
  audio_upload: z.boolean().optional(),
  animated_image_upload: z.boolean().optional(),
  options: z.array(zComboOption).optional(),
  remote: zRemoteWidgetConfig.optional(),
  /** Whether the widget is a multi-select widget. */
  multi_select: zMultiSelectOption.optional()
});
const zIntInputSpec$1 = z.tuple([z.literal("INT"), zIntInputOptions.optional()]);
const zFloatInputSpec$1 = z.tuple([
  z.literal("FLOAT"),
  zFloatInputOptions.optional()
]);
const zBooleanInputSpec$1 = z.tuple([
  z.literal("BOOLEAN"),
  zBooleanInputOptions.optional()
]);
const zStringInputSpec$1 = z.tuple([
  z.literal("STRING"),
  zStringInputOptions.optional()
]);
const zComboInputSpec$1 = z.tuple([
  z.array(zComboOption),
  zComboInputOptions.optional()
]);
const zComboInputSpecV2 = z.tuple([
  z.literal("COMBO"),
  zComboInputOptions.optional()
]);
function isComboInputSpecV1(inputSpec) {
  return Array.isArray(inputSpec[0]);
}
__name(isComboInputSpecV1, "isComboInputSpecV1");
function isIntInputSpec$1(inputSpec) {
  return inputSpec[0] === "INT";
}
__name(isIntInputSpec$1, "isIntInputSpec$1");
function isFloatInputSpec$1(inputSpec) {
  return inputSpec[0] === "FLOAT";
}
__name(isFloatInputSpec$1, "isFloatInputSpec$1");
function isComboInputSpecV2(inputSpec) {
  return inputSpec[0] === "COMBO";
}
__name(isComboInputSpecV2, "isComboInputSpecV2");
function isComboInputSpec$1(inputSpec) {
  return isComboInputSpecV1(inputSpec) || isComboInputSpecV2(inputSpec);
}
__name(isComboInputSpec$1, "isComboInputSpec$1");
function getInputSpecType(inputSpec) {
  return isComboInputSpec$1(inputSpec) ? "COMBO" : inputSpec[0];
}
__name(getInputSpecType, "getInputSpecType");
function getComboSpecComboOptions(inputSpec) {
  return (isComboInputSpecV2(inputSpec) ? inputSpec[1]?.options : inputSpec[0]) ?? [];
}
__name(getComboSpecComboOptions, "getComboSpecComboOptions");
const excludedLiterals = /* @__PURE__ */ new Set(["INT", "FLOAT", "BOOLEAN", "STRING", "COMBO"]);
const zCustomInputSpec$1 = z.tuple([
  z.string().refine((value) => !excludedLiterals.has(value)),
  zBaseInputOptions.optional()
]);
const zInputSpec$1 = z.union([
  zIntInputSpec$1,
  zFloatInputSpec$1,
  zBooleanInputSpec$1,
  zStringInputSpec$1,
  zComboInputSpec$1,
  zComboInputSpecV2,
  zCustomInputSpec$1
]);
const zComfyInputsSpec = z.object({
  required: z.record(zInputSpec$1).optional(),
  optional: z.record(zInputSpec$1).optional(),
  // Frontend repo is not using it, but some custom nodes are using the
  // hidden field to pass various values.
  hidden: z.record(z.any()).optional()
});
const zComfyNodeDataType = z.string();
const zComfyComboOutput = z.array(zComboOption);
const zComfyOutputTypesSpec = z.array(
  z.union([zComfyNodeDataType, zComfyComboOutput])
);
z.object({
  input: zComfyInputsSpec.optional(),
  output: zComfyOutputTypesSpec.optional(),
  output_is_list: z.array(z.boolean()).optional(),
  output_name: z.array(z.string()).optional(),
  output_tooltips: z.array(z.string()).optional(),
  output_matchtypes: z.array(z.string().optional()).optional(),
  name: z.string(),
  display_name: z.string(),
  description: z.string(),
  help: z.string().optional(),
  category: z.string(),
  output_node: z.boolean(),
  python_module: z.string(),
  deprecated: z.boolean().optional(),
  experimental: z.boolean().optional(),
  /**
   * Whether the node is an API node. Running API nodes requires login to
   * Comfy Org account.
   * https://docs.comfy.org/tutorials/api-nodes/overview
   */
  api_node: z.boolean().optional(),
  /**
   * Specifies the order of inputs for each input category.
   * Used to ensure consistent widget ordering regardless of JSON serialization.
   * Keys are 'required', 'optional', etc., values are arrays of input names.
   */
  input_order: z.record(z.array(z.string())).optional()
});
const zAutogrowOptions = z.object({
  ...zBaseInputOptions.shape,
  template: z.object({
    input: zComfyInputsSpec,
    names: z.array(z.string()).optional(),
    max: z.number().optional(),
    //Backend defines as mandatory with min 1, Frontend is more forgiving
    min: z.number().optional(),
    prefix: z.string().optional()
  })
});
const zDynamicComboInputSpec = z.tuple([
  z.literal("COMFY_DYNAMICCOMBO_V3"),
  zBaseInputOptions.extend({
    options: z.array(
      z.object({
        inputs: zComfyInputsSpec,
        key: z.string()
      })
    )
  })
]);
function transformNodeDefV1ToV2(nodeDefV1) {
  const inputs = {};
  if (nodeDefV1.input?.required) {
    Object.entries(nodeDefV1.input.required).forEach(([name2, inputSpecV1]) => {
      inputs[name2] = transformInputSpecV1ToV2(inputSpecV1, {
        name: name2,
        isOptional: false
      });
    });
  }
  if (nodeDefV1.input?.optional) {
    Object.entries(nodeDefV1.input.optional).forEach(([name2, inputSpecV1]) => {
      inputs[name2] = transformInputSpecV1ToV2(inputSpecV1, {
        name: name2,
        isOptional: true
      });
    });
  }
  const outputs = [];
  if (nodeDefV1.output) {
    if (Array.isArray(nodeDefV1.output)) {
      nodeDefV1.output.forEach((outputType, index2) => {
        const outputSpec = {
          index: index2,
          name: nodeDefV1.output_name?.[index2] || `output_${index2}`,
          type: Array.isArray(outputType) ? "COMBO" : outputType,
          is_list: nodeDefV1.output_is_list?.[index2] || false,
          tooltip: nodeDefV1.output_tooltips?.[index2]
        };
        if (Array.isArray(outputType)) {
          outputSpec.options = outputType;
        }
        outputs.push(outputSpec);
      });
    } else {
      console.warn("nodeDefV1.output is not an array:", nodeDefV1.output);
    }
  }
  return {
    inputs,
    outputs,
    hidden: nodeDefV1.input?.hidden,
    name: nodeDefV1.name,
    display_name: nodeDefV1.display_name,
    description: nodeDefV1.description,
    category: nodeDefV1.category,
    output_node: nodeDefV1.output_node,
    python_module: nodeDefV1.python_module,
    deprecated: nodeDefV1.deprecated,
    experimental: nodeDefV1.experimental
  };
}
__name(transformNodeDefV1ToV2, "transformNodeDefV1ToV2");
function transformInputSpecV1ToV2(inputSpecV1, kwargs) {
  const { name: name2, isOptional = false } = kwargs;
  const options = inputSpecV1[1] || {};
  const baseProps = {
    name: name2,
    isOptional,
    ...options
  };
  if (isComboInputSpec$1(inputSpecV1)) {
    return {
      type: "COMBO",
      ...baseProps,
      options: isComboInputSpecV1(inputSpecV1) ? inputSpecV1[0] : getComboSpecComboOptions(inputSpecV1)
    };
  } else if (typeof inputSpecV1[0] === "string") {
    return {
      type: inputSpecV1[0],
      ...baseProps
    };
  }
  return {
    type: "UNKNOWN",
    ...baseProps
  };
}
__name(transformInputSpecV1ToV2, "transformInputSpecV1ToV2");
function transformInputSpecV2ToV1(inputSpecV2) {
  return [inputSpecV2.type, inputSpecV2];
}
__name(transformInputSpecV2ToV1, "transformInputSpecV2ToV1");
class FuseFilter {
  static {
    __name(this, "FuseFilter");
  }
  fuseSearch;
  /** The unique identifier for the filter. */
  id;
  /** The name of the filter for display purposes. */
  name;
  /** The sequence of characters to invoke the filter. */
  invokeSequence;
  /** A function that returns the options for the filter. */
  getItemOptions;
  constructor(data, options) {
    this.id = options.id;
    this.name = options.name;
    this.invokeSequence = options.invokeSequence;
    this.getItemOptions = options.getItemOptions;
    this.fuseSearch = new FuseSearch(this.getAllNodeOptions(data), {
      fuseOptions: options.fuseOptions
    });
  }
  getAllNodeOptions(data) {
    const options = /* @__PURE__ */ new Set();
    for (const item of data) {
      for (const option of this.getItemOptions(item)) {
        options.add(option);
      }
    }
    return Array.from(options);
  }
  matches(item, value, extraOptions = {}) {
    const { wildcard } = extraOptions;
    if (wildcard && value === wildcard) {
      return true;
    }
    const options = this.getItemOptions(item);
    if (wildcard) return options.some((option) => option === wildcard);
    if (typeof value !== "string" || !value.includes(","))
      return options.includes(value);
    const values = value.split(",");
    return values.some((v2) => options.includes(v2));
  }
}
function isFuseSearchable(item) {
  return "postProcessSearchScores" in item;
}
__name(isFuseSearchable, "isFuseSearchable");
class FuseSearch {
  static {
    __name(this, "FuseSearch");
  }
  fuse;
  keys;
  data;
  advancedScoring;
  constructor(data, options) {
    const { fuseOptions, createIndex = true, advancedScoring = false } = options;
    this.data = data;
    this.keys = fuseOptions?.keys ?? [];
    this.advancedScoring = advancedScoring;
    const index2 = createIndex && this.keys.length ? Fuse.createIndex(this.keys, data) : void 0;
    this.fuse = new Fuse(data, fuseOptions, index2);
  }
  search(query, options) {
    const fuseResult = !query ? this.data.map((x2) => ({ item: x2, score: 0 })) : this.fuse.search(query, options);
    if (!this.advancedScoring) {
      return fuseResult.map((x2) => x2.item);
    }
    const aux = fuseResult.map((x2) => ({
      item: x2.item,
      scores: this.calcAuxScores(
        query.toLocaleLowerCase(),
        x2.item,
        x2.score ?? 0
      )
    })).sort((a, b) => this.compareAux(a.scores, b.scores));
    return aux.map((x2) => x2.item);
  }
  calcAuxScores(query, entry, score) {
    let values = [];
    if (typeof entry === "string") {
      values = [entry];
    } else if (typeof entry === "object" && entry !== null) {
      values = this.keys.map((x2) => entry[x2]).filter((x2) => typeof x2 === "string");
    }
    const scores = values.map((x2) => this.calcAuxSingle(query, x2, score));
    let result = scores.sort(this.compareAux)[0];
    const deprecated = values.some(
      (x2) => x2.toLocaleLowerCase().includes("deprecated")
    );
    result[0] += deprecated && result[0] !== 0 ? 5 : 0;
    if (isFuseSearchable(entry)) {
      result = entry.postProcessSearchScores(result);
    }
    return result;
  }
  calcAuxSingle(query, item, score) {
    const itemWords = item.split(/ |\b|(?<=[a-z])(?=[A-Z])|(?=[A-Z][a-z])/).map((x2) => x2.toLocaleLowerCase());
    const queryParts = query.split(" ");
    item = item.toLocaleLowerCase();
    let main = 9;
    let aux1 = 0;
    let aux2 = 0;
    if (item == query) {
      main = 0;
    } else if (item.startsWith(query)) {
      main = 1;
      aux2 = item.length;
    } else if (itemWords.includes(query)) {
      main = 2;
      aux1 = item.indexOf(query) + item.length * 0.5;
      aux2 = item.length;
    } else if (item.includes(query)) {
      main = 3;
      aux1 = item.indexOf(query) + item.length * 0.5;
      aux2 = item.length;
    } else if (queryParts.every((x2) => itemWords.includes(x2))) {
      const indexes = queryParts.map((x2) => itemWords.indexOf(x2));
      const min = Math.min(...indexes);
      const max = Math.max(...indexes);
      main = 4;
      aux1 = max - min + max * 0.5 + item.length * 0.5;
      aux2 = item.length;
    } else if (queryParts.every((x2) => item.includes(x2))) {
      const min = Math.min(...queryParts.map((x2) => item.indexOf(x2)));
      const max = Math.max(...queryParts.map((x2) => item.indexOf(x2) + x2.length));
      main = 5;
      aux1 = max - min + max * 0.5 + item.length * 0.5;
      aux2 = item.length;
    }
    const lengthPenalty = 0.2 * (1 - Math.min(item.length, query.length) / Math.max(item.length, query.length));
    return [main, aux1, aux2, score + lengthPenalty];
  }
  compareAux(a, b) {
    for (let i = 0; i < Math.min(a.length, b.length); i++) {
      if (a[i] !== b[i]) {
        return a[i] - b[i];
      }
    }
    return a.length - b.length;
  }
}
class NodeSearchService {
  static {
    __name(this, "NodeSearchService");
  }
  nodeFuseSearch;
  inputTypeFilter;
  outputTypeFilter;
  nodeCategoryFilter;
  nodeSourceFilter;
  constructor(data) {
    this.nodeFuseSearch = new FuseSearch(data, {
      fuseOptions: {
        keys: ["name", "display_name"],
        includeScore: true,
        threshold: 0.3,
        shouldSort: false,
        useExtendedSearch: true
      },
      createIndex: true,
      advancedScoring: true
    });
    const fuseOptions = {
      includeScore: true,
      threshold: 0.3,
      shouldSort: true
    };
    this.inputTypeFilter = new FuseFilter(data, {
      id: "input",
      name: "Input Type",
      invokeSequence: "i",
      getItemOptions: /* @__PURE__ */ __name((node2) => Object.values(node2.inputs ?? []).flatMap(
        (input) => input.type.split(",")
      ), "getItemOptions"),
      fuseOptions
    });
    this.outputTypeFilter = new FuseFilter(data, {
      id: "output",
      name: "Output Type",
      invokeSequence: "o",
      getItemOptions: /* @__PURE__ */ __name((node2) => node2.outputs.flatMap((output) => output.type.split(",")), "getItemOptions"),
      fuseOptions
    });
    this.nodeCategoryFilter = new FuseFilter(data, {
      id: "category",
      name: "Category",
      invokeSequence: "c",
      getItemOptions: /* @__PURE__ */ __name((node2) => [node2.category], "getItemOptions"),
      fuseOptions
    });
    this.nodeSourceFilter = new FuseFilter(data, {
      id: "source",
      name: "Source",
      invokeSequence: "s",
      getItemOptions: /* @__PURE__ */ __name((node2) => [node2.nodeSource.displayText], "getItemOptions"),
      fuseOptions
    });
  }
  searchNode(query, filters = [], options, extraOptions = {}) {
    const { matchWildcards = true } = extraOptions;
    const wildcard = matchWildcards ? "*" : void 0;
    const matchedNodes = this.nodeFuseSearch.search(query);
    const results = matchedNodes.filter((node2) => {
      return filters.every((filterAndValue) => {
        const { filterDef, value } = filterAndValue;
        return filterDef.matches(node2, value);
      });
    });
    if (matchWildcards) {
      const alreadyValid = new Set(results.map((result) => result.name));
      results.push(
        ...matchedNodes.filter((node2) => !alreadyValid.has(node2.name)).filter((node2) => {
          return filters.every((filterAndValue) => {
            const { filterDef, value } = filterAndValue;
            return filterDef.matches(node2, value, { wildcard });
          });
        })
      );
    }
    return options?.limit ? results.slice(0, options.limit) : results;
  }
  get nodeFilters() {
    return [
      this.inputTypeFilter,
      this.outputTypeFilter,
      this.nodeCategoryFilter,
      this.nodeSourceFilter
    ];
  }
}
const getSettingInfo = /* @__PURE__ */ __name((setting) => {
  const parts2 = setting.category || setting.id.split(".");
  return {
    category: parts2[0] ?? "Other",
    subCategory: parts2[1] ?? "Other"
  };
}, "getSettingInfo");
function tryMigrateDeprecatedValue(setting, value) {
  return setting?.migrateDeprecatedValue?.(value) ?? value;
}
__name(tryMigrateDeprecatedValue, "tryMigrateDeprecatedValue");
function onChange(setting, newValue2, oldValue) {
  if (setting?.onChange) {
    setting.onChange(newValue2, oldValue);
  }
  if (setting) {
    app$1.ui.settings.dispatchChange(setting.id, newValue2, oldValue);
  }
}
__name(onChange, "onChange");
const useSettingStore = defineStore("setting", () => {
  const settingValues = ref({});
  const settingsById = ref({});
  function exists(key) {
    return settingValues.value[key] !== void 0;
  }
  __name(exists, "exists");
  async function set(key, value) {
    const clonedValue = toolkit.cloneDeep(value);
    const newValue2 = tryMigrateDeprecatedValue(
      settingsById.value[key],
      clonedValue
    );
    const oldValue = get2(key);
    if (newValue2 === oldValue) return;
    onChange(settingsById.value[key], newValue2, oldValue);
    settingValues.value[key] = newValue2;
    await api.storeSetting(key, newValue2);
  }
  __name(set, "set");
  function get2(key) {
    return toolkit.cloneDeep(settingValues.value[key] ?? getDefaultValue2(key));
  }
  __name(get2, "get");
  function getSettingById(key) {
    return settingsById.value[key];
  }
  __name(getSettingById, "getSettingById");
  function getDefaultValue2(key) {
    const param = getSettingById(key);
    if (param === void 0) return;
    const versionedDefault = getVersionedDefaultValue(key, param);
    if (versionedDefault) {
      return versionedDefault;
    }
    const defaultValue = param.defaultValue;
    return typeof defaultValue === "function" ? defaultValue() : defaultValue;
  }
  __name(getDefaultValue2, "getDefaultValue");
  function getVersionedDefaultValue(key, param) {
    const defaultsByInstallVersion = param?.defaultsByInstallVersion;
    if (defaultsByInstallVersion && key !== "Comfy.InstalledVersion") {
      const installedVersion = get2("Comfy.InstalledVersion");
      if (installedVersion) {
        const sortedVersions = Object.keys(defaultsByInstallVersion).sort(
          (a, b) => semverExports.compare(b, a)
        );
        for (const version of sortedVersions) {
          if (!semverExports.valid(version)) {
            continue;
          }
          if (semverExports.compare(installedVersion, version) >= 0) {
            const versionedDefault = defaultsByInstallVersion[version];
            if (versionedDefault !== void 0) {
              return typeof versionedDefault === "function" ? versionedDefault() : versionedDefault;
            }
          }
        }
      }
    }
    return null;
  }
  __name(getVersionedDefaultValue, "getVersionedDefaultValue");
  function addSetting(setting) {
    if (!setting.id) {
      throw new Error("Settings must have an ID");
    }
    if (setting.id in settingsById.value) {
      throw new Error(`Setting ${setting.id} must have a unique ID.`);
    }
    settingsById.value[setting.id] = setting;
    if (settingValues.value[setting.id] !== void 0) {
      settingValues.value[setting.id] = tryMigrateDeprecatedValue(
        setting,
        settingValues.value[setting.id]
      );
    }
    onChange(setting, get2(setting.id), void 0);
  }
  __name(addSetting, "addSetting");
  async function loadSettingValues() {
    if (Object.keys(settingsById.value).length) {
      throw new Error(
        "Setting values must be loaded before any setting is registered."
      );
    }
    settingValues.value = await api.getSettings();
    await migrateZoomThresholdToFontSize();
  }
  __name(loadSettingValues, "loadSettingValues");
  async function migrateZoomThresholdToFontSize() {
    const oldKey = "LiteGraph.Canvas.LowQualityRenderingZoomThreshold";
    const newKey = "LiteGraph.Canvas.MinFontSizeForLOD";
    if (settingValues.value[oldKey] !== void 0 && settingValues.value[newKey] === void 0) {
      const oldValue = settingValues.value[oldKey];
      const mappedFontSize = Math.round(oldValue * 14);
      const clampedFontSize = Math.max(1, Math.min(24, mappedFontSize));
      settingValues.value[newKey] = clampedFontSize;
      delete settingValues.value[oldKey];
      await api.storeSetting(newKey, clampedFontSize);
      await api.storeSetting(oldKey, void 0);
    }
  }
  __name(migrateZoomThresholdToFontSize, "migrateZoomThresholdToFontSize");
  return {
    settingValues,
    settingsById,
    addSetting,
    loadSettingValues,
    set,
    get: get2,
    exists,
    getDefaultValue: getDefaultValue2
  };
});
function calculateNodeBounds(nodes) {
  if (!nodes || nodes.length === 0) {
    return null;
  }
  let minX = Infinity;
  let minY = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;
  for (const node2 of nodes) {
    const x2 = node2.pos[0];
    const y = node2.pos[1];
    const width2 = node2.size[0];
    const height = node2.size[1];
    minX = Math.min(minX, x2);
    minY = Math.min(minY, y);
    maxX = Math.max(maxX, x2 + width2);
    maxY = Math.max(maxY, y + height);
  }
  return {
    minX,
    minY,
    maxX,
    maxY,
    width: maxX - minX,
    height: maxY - minY
  };
}
__name(calculateNodeBounds, "calculateNodeBounds");
function enforceMinimumBounds(bounds, minWidth = 2500, minHeight = 2e3) {
  let { minX, minY, maxX, maxY, width: width2, height } = bounds;
  if (width2 < minWidth) {
    const padding = (minWidth - width2) / 2;
    minX -= padding;
    maxX += padding;
    width2 = minWidth;
  }
  if (height < minHeight) {
    const padding = (minHeight - height) / 2;
    minY -= padding;
    maxY += padding;
    height = minHeight;
  }
  return { minX, minY, maxX, maxY, width: width2, height };
}
__name(enforceMinimumBounds, "enforceMinimumBounds");
function calculateMinimapScale(bounds, viewportWidth, viewportHeight, padding = 0.9) {
  if (bounds.width === 0 || bounds.height === 0) {
    return 1;
  }
  const scaleX = viewportWidth / bounds.width;
  const scaleY = viewportHeight / bounds.height;
  return Math.min(scaleX, scaleY) * padding;
}
__name(calculateMinimapScale, "calculateMinimapScale");
const id$5 = "arc";
const name$5 = "Arc";
const colors$5 = { "node_slot": { "BOOLEAN": "", "CLIP": "#eacb8b", "CLIP_VISION": "#A8DADC", "CLIP_VISION_OUTPUT": "#ad7452", "CONDITIONING": "#cf876f", "CONTROL_NET": "#00d78d", "CONTROL_NET_WEIGHTS": "", "FLOAT": "", "GLIGEN": "", "IMAGE": "#80a1c0", "IMAGEUPLOAD": "", "INT": "", "LATENT": "#b38ead", "LATENT_KEYFRAME": "", "MASK": "#a3bd8d", "MODEL": "#8978a7", "SAMPLER": "", "SIGMAS": "", "STRING": "", "STYLE_MODEL": "#C2FFAE", "T2I_ADAPTER_WEIGHTS": "", "TAESD": "#DCC274", "TIMESTEP_KEYFRAME": "", "UPSCALE_MODEL": "", "VAE": "#be616b" }, "litegraph_base": { "BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABcklEQVR4nO3YMUoDARgF4RfxBqZI6/0vZqFn0MYtrLIQMFN8U6V4LAtD+Jm9XG/v30OGl2e/AP7yevz4+vx45nvgF/+QGITEICQGITEIiUFIjNNC3q43u3/YnRJyPOzeQ+0e220nhRzReC8e7R7bbdvl+Jal1Bs46jEIiUFIDEJiEBKDkBhKPbZT6qHdptRTu02p53DUYxASg5AYhMQgJAYhMZR6bKfUQ7tNqad2m1LP4ajHICQGITEIiUFIDEJiKPXYTqmHdptST+02pZ7DUY9BSAxCYhASg5AYhMRQ6rGdUg/tNqWe2m1KPYejHoOQGITEICQGITEIiaHUYzulHtptSj2125R6Dkc9BiExCIlBSAxCYhASQ6nHdko9tNuUemq3KfUcjnoMQmIQEoOQGITEICSGUo/tlHpotyn11G5T6jkc9RiExCAkBiExCIlBSAylHtsp9dBuU+qp3abUczjqMQiJQUgMQmIQEoOQGITE+AHFISNQrFTGuwAAAABJRU5ErkJggg==", "CLEAR_BACKGROUND_COLOR": "#2b2f38", "NODE_TITLE_COLOR": "#b2b7bd", "NODE_SELECTED_TITLE_COLOR": "#FFF", "NODE_TEXT_SIZE": 14, "NODE_TEXT_COLOR": "#AAA", "NODE_SUBTEXT_SIZE": 12, "NODE_DEFAULT_COLOR": "#2b2f38", "NODE_DEFAULT_BGCOLOR": "#242730", "NODE_DEFAULT_BOXCOLOR": "#6e7581", "NODE_DEFAULT_SHAPE": 2, "NODE_BOX_OUTLINE_COLOR": "#FFF", "NODE_BYPASS_BGCOLOR": "#FF00FF", "NODE_ERROR_COLOUR": "#E00", "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", "DEFAULT_GROUP_FONT": 22, "WIDGET_BGCOLOR": "#2b2f38", "WIDGET_OUTLINE_COLOR": "#6e7581", "WIDGET_TEXT_COLOR": "#DDD", "WIDGET_SECONDARY_TEXT_COLOR": "#b2b7bd", "WIDGET_DISABLED_TEXT_COLOR": "#5c626d", "LINK_COLOR": "#9A9", "EVENT_LINK_COLOR": "#A86", "CONNECTING_LINK_COLOR": "#AFA" }, "comfy_base": { "fg-color": "#fff", "bg-color": "#2b2f38", "comfy-menu-bg": "#242730", "comfy-menu-secondary-bg": "#22252E", "comfy-input-bg": "#2b2f38", "input-text": "#ddd", "descrip-text": "#b2b7bd", "drag-text": "#ccc", "error-text": "#ff4444", "border-color": "#6e7581", "tr-even-bg-color": "#2b2f38", "tr-odd-bg-color": "#242730", "content-bg": "#6e7581", "content-fg": "#fff", "content-hover-bg": "#2b2f38", "content-hover-fg": "#fff", "bar-shadow": "rgba(8, 8, 8, 0.75) 0 0 0.5rem" } };
const arc = {
  id: id$5,
  name: name$5,
  colors: colors$5
};
const id$4 = "dark";
const name$4 = "Dark (Default)";
const colors$4 = { "node_slot": { "CLIP": "#FFD500", "CLIP_VISION": "#A8DADC", "CLIP_VISION_OUTPUT": "#ad7452", "CONDITIONING": "#FFA931", "CONTROL_NET": "#6EE7B7", "IMAGE": "#64B5F6", "LATENT": "#FF9CF9", "MASK": "#81C784", "MODEL": "#B39DDB", "STYLE_MODEL": "#C2FFAE", "VAE": "#FF6E6E", "NOISE": "#B0B0B0", "GUIDER": "#66FFFF", "SAMPLER": "#ECB4B4", "SIGMAS": "#CDFFCD", "TAESD": "#DCC274" }, "litegraph_base": { "BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=", "CLEAR_BACKGROUND_COLOR": "#141414", "NODE_TITLE_COLOR": "#999", "NODE_SELECTED_TITLE_COLOR": "#FFF", "NODE_TEXT_SIZE": 14, "NODE_TEXT_COLOR": "#AAA", "NODE_TEXT_HIGHLIGHT_COLOR": "#FFF", "NODE_SUBTEXT_SIZE": 12, "NODE_DEFAULT_COLOR": "#333", "NODE_DEFAULT_BGCOLOR": "#353535", "NODE_DEFAULT_BOXCOLOR": "#666", "NODE_DEFAULT_SHAPE": 2, "NODE_BOX_OUTLINE_COLOR": "#FFF", "NODE_BYPASS_BGCOLOR": "#FF00FF", "NODE_ERROR_COLOUR": "#E00", "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", "DEFAULT_GROUP_FONT": 24, "WIDGET_BGCOLOR": "#222", "WIDGET_OUTLINE_COLOR": "#666", "WIDGET_TEXT_COLOR": "#DDD", "WIDGET_SECONDARY_TEXT_COLOR": "#999", "WIDGET_DISABLED_TEXT_COLOR": "#666", "LINK_COLOR": "#9A9", "EVENT_LINK_COLOR": "#A86", "CONNECTING_LINK_COLOR": "#AFA", "BADGE_FG_COLOR": "#FFF", "BADGE_BG_COLOR": "#0F1F0F" }, "comfy_base": { "fg-color": "#fff", "bg-color": "#202020", "comfy-menu-bg": "#171718", "comfy-menu-secondary-bg": "#303030", "comfy-input-bg": "#222", "input-text": "#ddd", "descrip-text": "#999", "drag-text": "#ccc", "error-text": "#ff4444", "border-color": "#4e4e4e", "tr-even-bg-color": "#222", "tr-odd-bg-color": "#353535", "content-bg": "#4e4e4e", "content-fg": "#fff", "content-hover-bg": "#222", "content-hover-fg": "#fff", "bar-shadow": "rgba(16, 16, 16, 0.5) 0 0 0.5rem" } };
const dark = {
  id: id$4,
  name: name$4,
  colors: colors$4
};
const id$3 = "github";
const name$3 = "Github";
const colors$3 = { "node_slot": { "BOOLEAN": "", "CLIP": "#eacb8b", "CLIP_VISION": "#A8DADC", "CLIP_VISION_OUTPUT": "#ad7452", "CONDITIONING": "#cf876f", "CONTROL_NET": "#00d78d", "CONTROL_NET_WEIGHTS": "", "FLOAT": "", "GLIGEN": "", "IMAGE": "#80a1c0", "IMAGEUPLOAD": "", "INT": "", "LATENT": "#b38ead", "LATENT_KEYFRAME": "", "MASK": "#a3bd8d", "MODEL": "#8978a7", "SAMPLER": "", "SIGMAS": "", "STRING": "", "STYLE_MODEL": "#C2FFAE", "T2I_ADAPTER_WEIGHTS": "", "TAESD": "#DCC274", "TIMESTEP_KEYFRAME": "", "UPSCALE_MODEL": "", "VAE": "#be616b" }, "litegraph_base": { "BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmIyYzRhNjA5LWJmYTctYTg0MC1iOGFlLTk3MzE2ZjM1ZGIyNyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk0ZmNlZGU4LTE1MTctZmQ0MC04ZGU3LWYzOTgxM2E3ODk5ZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjMxYjEwYjAtYjRmYi0wMjRlLWIxMmUtMzA1MzAzY2QwN2M4IiBzdEV2dDp3aGVuPSIyMDIzLTExLTEzVDAwOjE4OjAyKzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ4OWY1NzlmLTJkNjUtZWQ0Zi04OTg0LTA4NGE2MGE1ZTMzNSIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xNVQwMjowNDo1OSswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMmM0YTYwOS1iZmE3LWE4NDAtYjhhZS05NzMxNmYzNWRiMjciIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4OTe6GAAAAx0lEQVR42u3WMQoAIQxFwRzJys77X8vSLiRgITif7bYbgrwYc/mKXyBoY4VVBgsWLFiwYFmOlTv+9jfDOjHmr8u6eVkGCxYsWLBgmc5S8ApewXvgYRksWLBgKXidpeBdloL3wMOCBctgwVLwCl7BuyyDBQsWLFiwTGcpeAWv4D3wsAwWLFiwFLzOUvAuS8F74GHBgmWwYCl4Ba/gXZbBggULFixYprMUvIJX8B54WAYLFixYCl5nKXiXpeA98LBgwTJYsGC9tg1o8f4TTtqzNQAAAABJRU5ErkJggg==", "CLEAR_BACKGROUND_COLOR": "#040506", "NODE_TITLE_COLOR": "#999", "NODE_SELECTED_TITLE_COLOR": "#e5eaf0", "NODE_TEXT_SIZE": 14, "NODE_TEXT_COLOR": "#bcc2c8", "NODE_SUBTEXT_SIZE": 12, "NODE_DEFAULT_COLOR": "#161b22", "NODE_DEFAULT_BGCOLOR": "#13171d", "NODE_DEFAULT_BOXCOLOR": "#30363d", "NODE_DEFAULT_SHAPE": 2, "NODE_BOX_OUTLINE_COLOR": "#e5eaf0", "NODE_BYPASS_BGCOLOR": "#FF00FF", "NODE_ERROR_COLOUR": "#E00", "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", "DEFAULT_GROUP_FONT": 24, "WIDGET_BGCOLOR": "#161b22", "WIDGET_OUTLINE_COLOR": "#30363d", "WIDGET_TEXT_COLOR": "#bcc2c8", "WIDGET_SECONDARY_TEXT_COLOR": "#999", "WIDGET_DISABLED_TEXT_COLOR": "#30363d", "LINK_COLOR": "#9A9", "EVENT_LINK_COLOR": "#A86", "CONNECTING_LINK_COLOR": "#AFA" }, "comfy_base": { "fg-color": "#e5eaf0", "bg-color": "#161b22", "comfy-menu-bg": "#13171d", "comfy-menu-secondary-bg": "#11141A", "comfy-input-bg": "#161b22", "input-text": "#bcc2c8", "descrip-text": "#999", "drag-text": "#ccc", "error-text": "#ff4444", "border-color": "#30363d", "tr-even-bg-color": "#161b22", "tr-odd-bg-color": "#13171d", "content-bg": "#30363d", "content-fg": "#e5eaf0", "content-hover-bg": "#161b22", "content-hover-fg": "#e5eaf0", "bar-shadow": "rgba(0, 0, 0, 1) 0 0 0.5rem" } };
const github = {
  id: id$3,
  name: name$3,
  colors: colors$3
};
const id$2 = "light";
const name$2 = "Light";
const light_theme = true;
const colors$2 = { "node_slot": { "CLIP": "#FFA726", "CLIP_VISION": "#5C6BC0", "CLIP_VISION_OUTPUT": "#8D6E63", "CONDITIONING": "#EF5350", "CONTROL_NET": "#66BB6A", "IMAGE": "#42A5F5", "LATENT": "#AB47BC", "MASK": "#9CCC65", "MODEL": "#7E57C2", "STYLE_MODEL": "#D4E157", "VAE": "#FF7043", "NOISE": "#B0B0B0", "GUIDER": "#66FFFF", "SAMPLER": "#ECB4B4", "SIGMAS": "#CDFFCD", "TAESD": "#DCC274" }, "litegraph_base": { "BACKGROUND_IMAGE": "data:image/gif;base64,R0lGODlhZABkALMAAAAAAP///+vr6+rq6ujo6Ofn5+bm5uXl5d3d3f///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAABkAGQAAAT/UMhJq7046827HkcoHkYxjgZhnGG6si5LqnIM0/fL4qwwIMAg0CAsEovBIxKhRDaNy2GUOX0KfVFrssrNdpdaqTeKBX+dZ+jYvEaTf+y4W66mC8PUdrE879f9d2mBeoNLfH+IhYBbhIx2jkiHiomQlGKPl4uZe3CaeZifnnijgkESBqipqqusra6vsLGys62SlZO4t7qbuby7CLa+wqGWxL3Gv3jByMOkjc2lw8vOoNSi0czAncXW3Njdx9Pf48/Z4Kbbx+fQ5evZ4u3k1fKR6cn03vHlp7T9/v8A/8Gbp4+gwXoFryXMB2qgwoMMHyKEqA5fxX322FG8tzBcRnMW/zlulPbRncmQGidKjMjyYsOSKEF2FBlJQMCbOHP6c9iSZs+UnGYCdbnSo1CZI5F64kn0p1KnTH02nSoV3dGTV7FFHVqVq1dtWcMmVQZTbNGu72zqXMuW7danVL+6e4t1bEy6MeueBYLXrNO5Ze36jQtWsOG97wIj1vt3St/DjTEORss4nNq2mDP3e7w4r1bFkSET5hy6s2TRlD2/mSxXtSHQhCunXo26NevCpmvD/UU6tuullzULH76q92zdZG/Ltv1a+W+osI/nRmyc+fRi1Xdbh+68+0vv10dH3+77KD/i6IdnX669/frn5Zsjh4/2PXju8+8bzc9/6fj27LFnX11/+IUnXWl7BJfegm79FyB9JOl3oHgSklefgxAC+FmFGpqHIYcCfkhgfCohSKKJVo044YUMttggiBkmp6KFXw1oII24oYhjiDByaKOOHcp3Y5BD/njikSkO+eBREQAAOw==", "CLEAR_BACKGROUND_COLOR": "lightgray", "NODE_TITLE_COLOR": "#222", "NODE_SELECTED_TITLE_COLOR": "#000", "NODE_TEXT_SIZE": 14, "NODE_TEXT_COLOR": "#444", "NODE_TEXT_HIGHLIGHT_COLOR": "#1e293b", "NODE_SUBTEXT_SIZE": 12, "NODE_DEFAULT_COLOR": "#F7F7F7", "NODE_DEFAULT_BGCOLOR": "#F5F5F5", "NODE_DEFAULT_BOXCOLOR": "#CCC", "NODE_DEFAULT_SHAPE": 2, "NODE_BOX_OUTLINE_COLOR": "#000", "NODE_BYPASS_BGCOLOR": "#FF00FF", "NODE_ERROR_COLOUR": "#E00", "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.1)", "DEFAULT_GROUP_FONT": 24, "WIDGET_BGCOLOR": "#D4D4D4", "WIDGET_OUTLINE_COLOR": "#999", "WIDGET_TEXT_COLOR": "#222", "WIDGET_SECONDARY_TEXT_COLOR": "#555", "WIDGET_DISABLED_TEXT_COLOR": "#999", "LINK_COLOR": "#4CAF50", "EVENT_LINK_COLOR": "#FF9800", "CONNECTING_LINK_COLOR": "#2196F3", "BADGE_FG_COLOR": "#000", "BADGE_BG_COLOR": "#FFF" }, "comfy_base": { "fg-color": "#222", "bg-color": "#DDD", "comfy-menu-bg": "#FFFFFF", "comfy-menu-hover-bg": "#ccc", "comfy-menu-secondary-bg": "#EEE", "comfy-input-bg": "#C9C9C9", "input-text": "#222", "descrip-text": "#444", "drag-text": "#555", "error-text": "#F44336", "border-color": "#888", "tr-even-bg-color": "#f9f9f9", "tr-odd-bg-color": "#fff", "content-bg": "#e0e0e0", "content-fg": "#222", "content-hover-bg": "#adadad", "content-hover-fg": "#222", "bar-shadow": "rgba(16, 16, 16, 0.25) 0 0 0.5rem", "interface-panel-box-shadow": "1px 1px 8px 0 rgba(0, 0, 0, 0.2)", "interface-panel-drop-shadow": "1px 1px 4px rgba(0, 0, 0, 0.4)", "interface-panel-hover-surface": "var(--color-gray-200)", "interface-panel-selected-surface": "color-mix(in srgb, var(--interface-panel-surface) 78%, var(--contrast-mix-color))", "contrast-mix-color": "#000" } };
const light = {
  id: id$2,
  name: name$2,
  light_theme,
  colors: colors$2
};
const id$1 = "nord";
const name$1 = "Nord";
const colors$1 = { "node_slot": { "BOOLEAN": "", "CLIP": "#eacb8b", "CLIP_VISION": "#A8DADC", "CLIP_VISION_OUTPUT": "#ad7452", "CONDITIONING": "#cf876f", "CONTROL_NET": "#00d78d", "CONTROL_NET_WEIGHTS": "", "FLOAT": "", "GLIGEN": "", "IMAGE": "#80a1c0", "IMAGEUPLOAD": "", "INT": "", "LATENT": "#b38ead", "LATENT_KEYFRAME": "", "MASK": "#a3bd8d", "MODEL": "#8978a7", "SAMPLER": "", "SIGMAS": "", "STRING": "", "STYLE_MODEL": "#C2FFAE", "T2I_ADAPTER_WEIGHTS": "", "TAESD": "#DCC274", "TIMESTEP_KEYFRAME": "", "UPSCALE_MODEL": "", "VAE": "#be616b" }, "litegraph_base": { "BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFu2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjUwNDFhMmZjLTEzNzQtMTk0ZC1hZWY4LTYxMzM1MTVmNjUwMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyMzFiMTBiMC1iNGZiLTAyNGUtYjEyZS0zMDUzMDNjZDA3YzgiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyMzFiMTBiMC1iNGZiLTAyNGUtYjEyZS0zMDUzMDNjZDA3YzgiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDQxYTJmYy0xMzc0LTE5NGQtYWVmOC02MTMzNTE1ZjY1MDAiIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDE6MjA6NDUrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz73jWg/AAAAyUlEQVR42u3WKwoAIBRFQRdiMb1idv9Lsxn9gEFw4Dbb8JCTojbbXEJwjJVL2HKwYMGCBQuWLbDmjr+9zrBGjHl1WVcvy2DBggULFizTWQpewSt4HzwsgwULFiwFr7MUvMtS8D54WLBgGSxYCl7BK3iXZbBgwYIFC5bpLAWv4BW8Dx6WwYIFC5aC11kK3mUpeB88LFiwDBYsBa/gFbzLMliwYMGCBct0loJX8AreBw/LYMGCBUvB6ywF77IUvA8eFixYBgsWrNfWAZPltufdad+1AAAAAElFTkSuQmCC", "CLEAR_BACKGROUND_COLOR": "#212732", "NODE_TITLE_COLOR": "#999", "NODE_SELECTED_TITLE_COLOR": "#e5eaf0", "NODE_TEXT_SIZE": 14, "NODE_TEXT_COLOR": "#bcc2c8", "NODE_SUBTEXT_SIZE": 12, "NODE_DEFAULT_COLOR": "#2e3440", "NODE_DEFAULT_BGCOLOR": "#161b22", "NODE_DEFAULT_BOXCOLOR": "#545d70", "NODE_DEFAULT_SHAPE": 2, "NODE_BOX_OUTLINE_COLOR": "#e5eaf0", "NODE_BYPASS_BGCOLOR": "#FF00FF", "NODE_ERROR_COLOUR": "#E00", "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", "DEFAULT_GROUP_FONT": 24, "WIDGET_BGCOLOR": "#2e3440", "WIDGET_OUTLINE_COLOR": "#545d70", "WIDGET_TEXT_COLOR": "#bcc2c8", "WIDGET_SECONDARY_TEXT_COLOR": "#999", "WIDGET_DISABLED_TEXT_COLOR": "#545d70", "LINK_COLOR": "#9A9", "EVENT_LINK_COLOR": "#A86", "CONNECTING_LINK_COLOR": "#AFA" }, "comfy_base": { "fg-color": "#e5eaf0", "bg-color": "#2e3440", "comfy-menu-bg": "#161b22", "comfy-menu-secondary-bg": "#14181F", "comfy-input-bg": "#2e3440", "input-text": "#bcc2c8", "descrip-text": "#999", "drag-text": "#ccc", "error-text": "#ff4444", "border-color": "#545d70", "tr-even-bg-color": "#2e3440", "tr-odd-bg-color": "#161b22", "content-bg": "#545d70", "content-fg": "#e5eaf0", "content-hover-bg": "#2e3440", "content-hover-fg": "#e5eaf0", "bar-shadow": "rgba(0, 0, 0, 0.75) 0 0 0.5rem" } };
const nord = {
  id: id$1,
  name: name$1,
  colors: colors$1
};
const id = "solarized";
const name = "Solarized";
const colors = { "node_slot": { "CLIP": "#2AB7CA", "CLIP_VISION": "#6c71c4", "CLIP_VISION_OUTPUT": "#859900", "CONDITIONING": "#d33682", "CONTROL_NET": "#d1ffd7", "IMAGE": "#5940bb", "LATENT": "#268bd2", "MASK": "#CCC9E7", "MODEL": "#dc322f", "STYLE_MODEL": "#1a998a", "UPSCALE_MODEL": "#054A29", "VAE": "#facfad" }, "litegraph_base": { "NODE_TITLE_COLOR": "#fdf6e3", "NODE_SELECTED_TITLE_COLOR": "#A9D400", "NODE_TEXT_SIZE": 14, "NODE_TEXT_COLOR": "#657b83", "NODE_SUBTEXT_SIZE": 12, "NODE_DEFAULT_COLOR": "#094656", "NODE_DEFAULT_BGCOLOR": "#073642", "NODE_DEFAULT_BOXCOLOR": "#839496", "NODE_DEFAULT_SHAPE": 2, "NODE_BOX_OUTLINE_COLOR": "#fdf6e3", "NODE_BYPASS_BGCOLOR": "#FF00FF", "NODE_ERROR_COLOUR": "#E00", "DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0.5)", "DEFAULT_GROUP_FONT": 24, "WIDGET_BGCOLOR": "#002b36", "WIDGET_OUTLINE_COLOR": "#839496", "WIDGET_TEXT_COLOR": "#fdf6e3", "WIDGET_SECONDARY_TEXT_COLOR": "#93a1a1", "WIDGET_DISABLED_TEXT_COLOR": "#657b83", "LINK_COLOR": "#2aa198", "EVENT_LINK_COLOR": "#268bd2", "CONNECTING_LINK_COLOR": "#859900" }, "comfy_base": { "fg-color": "#fdf6e3", "bg-color": "#002b36", "comfy-menu-bg": "#073642", "comfy-menu-secondary-bg": "#06323D", "comfy-input-bg": "#002b36", "input-text": "#93a1a1", "descrip-text": "#586e75", "drag-text": "#839496", "error-text": "#dc322f", "border-color": "#657b83", "tr-even-bg-color": "#002b36", "tr-odd-bg-color": "#073642", "content-bg": "#657b83", "content-fg": "#fdf6e3", "content-hover-bg": "#002b36", "content-hover-fg": "#fdf6e3", "bar-shadow": "rgba(16, 16, 16, 0.5) 0 0 0.5rem" } };
const solarized = {
  id,
  name,
  colors
};
const CORE_COLOR_PALETTES = {
  dark,
  light,
  solarized,
  arc,
  nord,
  github
};
const DEFAULT_COLOR_PALETTE = dark;
const DEFAULT_DARK_COLOR_PALETTE = dark;
const DEFAULT_LIGHT_COLOR_PALETTE = light;
const useColorPaletteStore = defineStore("colorPalette", () => {
  const customPalettes = ref({});
  const activePaletteId = ref(DEFAULT_COLOR_PALETTE.id);
  const palettesLookup = computed(() => ({
    ...CORE_COLOR_PALETTES,
    ...customPalettes.value
  }));
  const palettes = computed(() => Object.values(palettesLookup.value));
  const completedActivePalette = computed(
    () => completePalette(palettesLookup.value[activePaletteId.value])
  );
  const addCustomPalette = /* @__PURE__ */ __name((palette) => {
    if (palette.id in palettesLookup.value) {
      throw new Error(`Palette with id ${palette.id} already exists`);
    }
    customPalettes.value[palette.id] = palette;
    activePaletteId.value = palette.id;
  }, "addCustomPalette");
  const deleteCustomPalette = /* @__PURE__ */ __name((id2) => {
    if (!(id2 in customPalettes.value)) {
      throw new Error(`Palette with id ${id2} does not exist`);
    }
    delete customPalettes.value[id2];
    activePaletteId.value = CORE_COLOR_PALETTES.dark.id;
  }, "deleteCustomPalette");
  const isCustomPalette = /* @__PURE__ */ __name((id2) => {
    return id2 in customPalettes.value;
  }, "isCustomPalette");
  const completePalette = /* @__PURE__ */ __name((palette) => {
    if (palette.colors.comfy_base["comfy-menu-bg"] && !palette.colors.comfy_base["comfy-menu-secondary-bg"]) {
      palette.colors.comfy_base["comfy-menu-secondary-bg"] = palette.colors.comfy_base["comfy-menu-bg"];
    }
    const defaultPalette = palette.light_theme ? DEFAULT_LIGHT_COLOR_PALETTE : DEFAULT_DARK_COLOR_PALETTE;
    return {
      ...palette,
      colors: {
        ...palette.colors,
        node_slot: {
          ...defaultPalette.colors.node_slot,
          ...palette.colors.node_slot
        },
        litegraph_base: {
          ...defaultPalette.colors.litegraph_base,
          ...palette.colors.litegraph_base
        },
        comfy_base: {
          ...defaultPalette.colors.comfy_base,
          ...palette.colors.comfy_base
        }
      }
    };
  }, "completePalette");
  return {
    // State
    customPalettes,
    activePaletteId,
    // Getters
    palettesLookup,
    palettes,
    completedActivePalette,
    // Actions
    isCustomPalette,
    addCustomPalette,
    deleteCustomPalette,
    completePalette
  };
});
class AbstractMinimapDataSource {
  static {
    __name(this, "AbstractMinimapDataSource");
  }
  constructor(graph) {
    this.graph = graph;
  }
  // Shared implementation using calculateNodeBounds
  getBounds() {
    const nodes = this.getNodes();
    if (nodes.length === 0) {
      return { minX: 0, minY: 0, maxX: 100, maxY: 100, width: 100, height: 100 };
    }
    const compatibleNodes = nodes.map(
      (node2) => ({
        pos: [node2.x, node2.y],
        size: [node2.width, node2.height]
      })
    );
    const bounds = calculateNodeBounds(compatibleNodes);
    if (!bounds) {
      return { minX: 0, minY: 0, maxX: 100, maxY: 100, width: 100, height: 100 };
    }
    return bounds;
  }
  // Shared implementation for groups
  getGroups() {
    if (!this.graph?._groups) return [];
    return this.graph._groups.map((group) => ({
      x: group.pos[0],
      y: group.pos[1],
      width: group.size[0],
      height: group.size[1],
      color: group.color
    }));
  }
  // TODO: update when Layoutstore supports links
  getLinks() {
    if (!this.graph) return [];
    return this.extractLinksFromGraph(this.graph);
  }
  extractLinksFromGraph(graph) {
    const links = [];
    const nodeMap = new Map(this.getNodes().map((n) => [n.id, n]));
    for (const node2 of graph._nodes) {
      if (!node2.outputs) continue;
      const sourceNodeData = nodeMap.get(String(node2.id));
      if (!sourceNodeData) continue;
      for (const output of node2.outputs) {
        if (!output.links) continue;
        for (const linkId of output.links) {
          const link = graph.links[linkId];
          if (!link) continue;
          const targetNodeData = nodeMap.get(String(link.target_id));
          if (!targetNodeData) continue;
          links.push({
            sourceNode: sourceNodeData,
            targetNode: targetNodeData,
            sourceSlot: link.origin_slot,
            targetSlot: link.target_slot
          });
        }
      }
    }
    return links;
  }
}
class LayoutStoreDataSource extends AbstractMinimapDataSource {
  static {
    __name(this, "LayoutStoreDataSource");
  }
  getNodes() {
    const allNodes = layoutStore.getAllNodes().value;
    if (allNodes.size === 0) return [];
    const nodes = [];
    for (const [nodeId, layout] of allNodes) {
      const graphNode = this.graph?._nodes?.find((n) => String(n.id) === nodeId);
      nodes.push({
        id: nodeId,
        x: layout.position.x,
        y: layout.position.y,
        width: layout.size.width,
        height: layout.size.height,
        bgcolor: graphNode?.bgcolor,
        mode: graphNode?.mode,
        hasErrors: graphNode?.has_errors
      });
    }
    return nodes;
  }
  getNodeCount() {
    return layoutStore.getAllNodes().value.size;
  }
  hasData() {
    return this.getNodeCount() > 0;
  }
}
class LiteGraphDataSource extends AbstractMinimapDataSource {
  static {
    __name(this, "LiteGraphDataSource");
  }
  getNodes() {
    if (!this.graph?._nodes) return [];
    return this.graph._nodes.map((node2) => ({
      id: String(node2.id),
      x: node2.pos[0],
      y: node2.pos[1],
      width: node2.size[0],
      height: node2.size[1],
      bgcolor: node2.bgcolor,
      mode: node2.mode,
      hasErrors: node2.has_errors
    }));
  }
  getNodeCount() {
    return this.graph?._nodes?.length ?? 0;
  }
  hasData() {
    return this.getNodeCount() > 0;
  }
}
class MinimapDataSourceFactory {
  static {
    __name(this, "MinimapDataSourceFactory");
  }
  static create(graph) {
    const layoutStoreHasData = layoutStore.getAllNodes().value.size > 0;
    if (layoutStoreHasData) {
      return new LayoutStoreDataSource(graph);
    }
    return new LiteGraphDataSource(graph);
  }
}
function getMinimapColors() {
  const colorPaletteStore = useColorPaletteStore();
  const isLightTheme = colorPaletteStore.completedActivePalette.light_theme;
  return {
    nodeColor: isLightTheme ? "#3DA8E099" : "#0B8CE999",
    nodeColorDefault: isLightTheme ? "#D9D9D9" : "#353535",
    linkColor: isLightTheme ? "#616161" : "#B3B3B3",
    slotColor: isLightTheme ? "#616161" : "#B3B3B3",
    groupColor: isLightTheme ? "#A2D3EC" : "#1F547A",
    groupColorDefault: isLightTheme ? "#283640" : "#B3C1CB",
    bypassColor: isLightTheme ? "#DBDBDB" : "#4B184B",
    errorColor: "#FF0000",
    isLightTheme
  };
}
__name(getMinimapColors, "getMinimapColors");
function getNodeColor(node2, settings, colors2) {
  if (settings.renderBypass && node2.mode === LGraphEventMode.BYPASS) {
    return colors2.bypassColor;
  }
  if (settings.nodeColors) {
    if (node2.bgcolor) {
      return colors2.isLightTheme ? adjustColor(node2.bgcolor, { lightness: 0.5 }) : node2.bgcolor;
    }
    return colors2.nodeColorDefault;
  }
  return colors2.nodeColor;
}
__name(getNodeColor, "getNodeColor");
function renderGroups(ctx, dataSource, offsetX, offsetY, context, colors2) {
  const groups = dataSource.getGroups();
  if (groups.length === 0) return;
  for (const group of groups) {
    const x2 = (group.x - context.bounds.minX) * context.scale + offsetX;
    const y = (group.y - context.bounds.minY) * context.scale + offsetY;
    const w = group.width * context.scale;
    const h2 = group.height * context.scale;
    let color2 = colors2.groupColor;
    if (context.settings.nodeColors) {
      color2 = group.color ?? colors2.groupColorDefault;
      if (colors2.isLightTheme) {
        color2 = adjustColor(color2, { opacity: 0.5 });
      }
    }
    ctx.fillStyle = color2;
    ctx.fillRect(x2, y, w, h2);
  }
}
__name(renderGroups, "renderGroups");
function renderNodes(ctx, dataSource, offsetX, offsetY, context, colors2) {
  const nodes = dataSource.getNodes();
  if (nodes.length === 0) return;
  const nodesByColor = /* @__PURE__ */ new Map();
  for (const node2 of nodes) {
    const x2 = (node2.x - context.bounds.minX) * context.scale + offsetX;
    const y = (node2.y - context.bounds.minY) * context.scale + offsetY;
    const w = node2.width * context.scale;
    const h2 = node2.height * context.scale;
    const color2 = getNodeColor(node2, context.settings, colors2);
    if (!nodesByColor.has(color2)) {
      nodesByColor.set(color2, []);
    }
    nodesByColor.get(color2).push({ x: x2, y, w, h: h2, hasErrors: node2.hasErrors });
  }
  for (const [color2, nodes2] of nodesByColor) {
    ctx.fillStyle = color2;
    for (const node2 of nodes2) {
      ctx.fillRect(node2.x, node2.y, node2.w, node2.h);
    }
  }
  if (context.settings.renderError) {
    ctx.strokeStyle = colors2.errorColor;
    ctx.lineWidth = 0.3;
    for (const nodes2 of nodesByColor.values()) {
      for (const node2 of nodes2) {
        if (node2.hasErrors) {
          ctx.strokeRect(node2.x, node2.y, node2.w, node2.h);
        }
      }
    }
  }
}
__name(renderNodes, "renderNodes");
function renderConnections(ctx, dataSource, offsetX, offsetY, context, colors2) {
  const links = dataSource.getLinks();
  if (links.length === 0) return;
  ctx.strokeStyle = colors2.linkColor;
  ctx.lineWidth = 0.3;
  const slotRadius = Math.max(context.scale, 0.5);
  const connections = [];
  for (const link of links) {
    const x1 = (link.sourceNode.x - context.bounds.minX) * context.scale + offsetX;
    const y1 = (link.sourceNode.y - context.bounds.minY) * context.scale + offsetY;
    const x2 = (link.targetNode.x - context.bounds.minX) * context.scale + offsetX;
    const y2 = (link.targetNode.y - context.bounds.minY) * context.scale + offsetY;
    const outputX = x1 + link.sourceNode.width * context.scale;
    const outputY = y1 + link.sourceNode.height * context.scale * 0.2;
    const inputX = x2;
    const inputY = y2 + link.targetNode.height * context.scale * 0.2;
    ctx.beginPath();
    ctx.moveTo(outputX, outputY);
    ctx.lineTo(inputX, inputY);
    ctx.stroke();
    connections.push({ x1: outputX, y1: outputY, x2: inputX, y2: inputY });
  }
  ctx.fillStyle = colors2.slotColor;
  for (const conn of connections) {
    ctx.beginPath();
    ctx.arc(conn.x1, conn.y1, slotRadius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(conn.x2, conn.y2, slotRadius, 0, Math.PI * 2);
    ctx.fill();
  }
}
__name(renderConnections, "renderConnections");
function renderMinimapToCanvas(canvas2, graph, context) {
  const ctx = canvas2.getContext("2d");
  if (!ctx) return;
  ctx.clearRect(0, 0, context.width, context.height);
  const dataSource = MinimapDataSourceFactory.create(graph);
  if (!dataSource.hasData()) {
    return;
  }
  const colors2 = getMinimapColors();
  const offsetX = (context.width - context.bounds.width * context.scale) / 2;
  const offsetY = (context.height - context.bounds.height * context.scale) / 2;
  if (context.settings.showGroups) {
    renderGroups(ctx, dataSource, offsetX, offsetY, context, colors2);
  }
  if (context.settings.showLinks) {
    renderConnections(ctx, dataSource, offsetX, offsetY, context, colors2);
  }
  renderNodes(ctx, dataSource, offsetX, offsetY, context, colors2);
}
__name(renderMinimapToCanvas, "renderMinimapToCanvas");
function createGraphThumbnail() {
  const canvasStore = useCanvasStore();
  const workflowStore = useWorkflowStore();
  const graph = workflowStore.activeSubgraph || canvasStore.canvas?.graph;
  if (!graph || !graph._nodes || graph._nodes.length === 0) {
    return null;
  }
  const width2 = 250;
  const height = 200;
  const bounds = calculateNodeBounds(graph._nodes);
  if (!bounds) {
    return null;
  }
  const scale = calculateMinimapScale(bounds, width2, height);
  const canvas2 = document.createElement("canvas");
  canvas2.width = width2;
  canvas2.height = height;
  renderMinimapToCanvas(canvas2, graph, {
    bounds,
    scale,
    settings: {
      nodeColors: true,
      showLinks: false,
      showGroups: true,
      renderBypass: false,
      renderError: false
    },
    width: width2,
    height
  });
  const dataUrl = canvas2.toDataURL();
  const ctx = canvas2.getContext("2d");
  if (ctx) {
    ctx.clearRect(0, 0, width2, height);
  }
  return dataUrl;
}
__name(createGraphThumbnail, "createGraphThumbnail");
const workflowThumbnails = ref(/* @__PURE__ */ new Map());
const useWorkflowThumbnail = /* @__PURE__ */ __name(() => {
  const createMinimapPreview = /* @__PURE__ */ __name(() => {
    try {
      const thumbnailDataUrl = createGraphThumbnail();
      return Promise.resolve(thumbnailDataUrl);
    } catch (error) {
      console.error("Failed to capture canvas thumbnail:", error);
      return Promise.resolve(null);
    }
  }, "createMinimapPreview");
  const storeThumbnail = /* @__PURE__ */ __name(async (workflow) => {
    const thumbnail = await createMinimapPreview();
    if (thumbnail) {
      const existingThumbnail = workflowThumbnails.value.get(workflow.key);
      if (existingThumbnail) {
        URL.revokeObjectURL(existingThumbnail);
      }
      workflowThumbnails.value.set(workflow.key, thumbnail);
    }
  }, "storeThumbnail");
  const getThumbnail = /* @__PURE__ */ __name((workflowKey) => {
    return workflowThumbnails.value.get(workflowKey);
  }, "getThumbnail");
  const clearThumbnail = /* @__PURE__ */ __name((workflowKey) => {
    const thumbnail = workflowThumbnails.value.get(workflowKey);
    if (thumbnail) {
      URL.revokeObjectURL(thumbnail);
    }
    workflowThumbnails.value.delete(workflowKey);
  }, "clearThumbnail");
  const clearAllThumbnails = /* @__PURE__ */ __name(() => {
    for (const thumbnail of workflowThumbnails.value.values()) {
      URL.revokeObjectURL(thumbnail);
    }
    workflowThumbnails.value.clear();
  }, "clearAllThumbnails");
  const moveWorkflowThumbnail = /* @__PURE__ */ __name((oldKey, newKey) => {
    if (oldKey === newKey) return;
    const thumbnail = workflowThumbnails.value.get(oldKey);
    if (thumbnail) {
      workflowThumbnails.value.set(newKey, thumbnail);
      workflowThumbnails.value.delete(oldKey);
    }
  }, "moveWorkflowThumbnail");
  return {
    createMinimapPreview,
    storeThumbnail,
    getThumbnail,
    clearThumbnail,
    clearAllThumbnails,
    moveWorkflowThumbnail,
    workflowThumbnails
  };
}, "useWorkflowThumbnail");
const defaultGraph = {
  last_node_id: 9,
  last_link_id: 9,
  nodes: [
    {
      id: 7,
      type: "CLIPTextEncode",
      pos: [413, 389],
      size: [425.27801513671875, 180.6060791015625],
      flags: {},
      order: 3,
      mode: 0,
      inputs: [{ name: "clip", type: "CLIP", link: 5 }],
      outputs: [
        {
          name: "CONDITIONING",
          type: "CONDITIONING",
          links: [6],
          slot_index: 0
        }
      ],
      properties: {},
      widgets_values: ["text, watermark"]
    },
    {
      id: 6,
      type: "CLIPTextEncode",
      pos: [415, 186],
      size: [422.84503173828125, 164.31304931640625],
      flags: {},
      order: 2,
      mode: 0,
      inputs: [{ name: "clip", type: "CLIP", link: 3 }],
      outputs: [
        {
          name: "CONDITIONING",
          type: "CONDITIONING",
          links: [4],
          slot_index: 0
        }
      ],
      properties: {},
      widgets_values: [
        "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
      ]
    },
    {
      id: 5,
      type: "EmptyLatentImage",
      pos: [473, 609],
      size: [315, 106],
      flags: {},
      order: 1,
      mode: 0,
      outputs: [{ name: "LATENT", type: "LATENT", links: [2], slot_index: 0 }],
      properties: {},
      widgets_values: [512, 512, 1]
    },
    {
      id: 3,
      type: "KSampler",
      pos: [863, 186],
      size: [315, 262],
      flags: {},
      order: 4,
      mode: 0,
      inputs: [
        { name: "model", type: "MODEL", link: 1 },
        { name: "positive", type: "CONDITIONING", link: 4 },
        { name: "negative", type: "CONDITIONING", link: 6 },
        { name: "latent_image", type: "LATENT", link: 2 }
      ],
      outputs: [{ name: "LATENT", type: "LATENT", links: [7], slot_index: 0 }],
      properties: {},
      widgets_values: [156680208700286, true, 20, 8, "euler", "normal", 1]
    },
    {
      id: 8,
      type: "VAEDecode",
      pos: [1209, 188],
      size: [210, 46],
      flags: {},
      order: 5,
      mode: 0,
      inputs: [
        { name: "samples", type: "LATENT", link: 7 },
        { name: "vae", type: "VAE", link: 8 }
      ],
      outputs: [{ name: "IMAGE", type: "IMAGE", links: [9], slot_index: 0 }],
      properties: {}
    },
    {
      id: 9,
      type: "SaveImage",
      pos: [1451, 189],
      size: [210, 26],
      flags: {},
      order: 6,
      mode: 0,
      inputs: [{ name: "images", type: "IMAGE", link: 9 }],
      properties: {}
    },
    {
      id: 4,
      type: "CheckpointLoaderSimple",
      pos: [26, 474],
      size: [315, 98],
      flags: {},
      order: 0,
      mode: 0,
      outputs: [
        { name: "MODEL", type: "MODEL", links: [1], slot_index: 0 },
        { name: "CLIP", type: "CLIP", links: [3, 5], slot_index: 1 },
        { name: "VAE", type: "VAE", links: [8], slot_index: 2 }
      ],
      properties: {},
      widgets_values: ["v1-5-pruned-emaonly-fp16.safetensors"]
    }
  ],
  links: [
    [1, 4, 0, 3, 0, "MODEL"],
    [2, 5, 0, 3, 3, "LATENT"],
    [3, 4, 1, 6, 0, "CLIP"],
    [4, 6, 0, 3, 1, "CONDITIONING"],
    [5, 4, 1, 7, 0, "CLIP"],
    [6, 7, 0, 3, 2, "CONDITIONING"],
    [7, 3, 0, 8, 0, "LATENT"],
    [8, 4, 2, 8, 1, "VAE"],
    [9, 8, 0, 9, 0, "IMAGE"]
  ],
  groups: [],
  config: {},
  extra: {
    ds: {
      offset: [0, 0],
      scale: 1
    }
  },
  version: 0.4
};
const defaultGraphJSON = JSON.stringify(defaultGraph);
const blankGraph = {
  last_node_id: 0,
  last_link_id: 0,
  nodes: [],
  links: [],
  groups: [],
  config: {},
  extra: {},
  version: 0.4
};
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.defaultGraph = window.comfyAPI.defaultGraph || {};
window.comfyAPI.defaultGraph.defaultGraph = defaultGraph;
window.comfyAPI.defaultGraph.defaultGraphJSON = defaultGraphJSON;
window.comfyAPI.defaultGraph.blankGraph = blankGraph;
function applyTextReplacements$1(graph, value) {
  const allNodes = collectAllNodes(graph);
  return value.replace(/%([^%]+)%/g, function(match, text) {
    const split = text.split(".");
    if (split.length !== 2) {
      if (split[0].startsWith("date:")) {
        return formatDate(split[0].substring(5), /* @__PURE__ */ new Date());
      }
      if (text !== "width" && text !== "height") {
        console.warn("Invalid replacement pattern", text);
      }
      return match;
    }
    let nodes = allNodes.filter(
      (n) => n.properties?.["Node name for S&R"] === split[0]
    );
    if (!nodes.length) {
      nodes = allNodes.filter((n) => n.title === split[0]);
    }
    if (!nodes.length) {
      console.warn("Unable to find node", split[0]);
      return match;
    }
    if (nodes.length > 1) {
      console.warn("Multiple nodes matched", split[0], "using first match");
    }
    const node2 = nodes[0];
    const widget = node2.widgets?.find((w) => w.name === split[1]);
    if (!widget) {
      console.warn("Unable to find widget", split[1], "on node", split[0], node2);
      return match;
    }
    return ((widget.value ?? "") + "").replaceAll(
      // eslint-disable-next-line no-control-regex
      /[/?<>\\:*|"\x00-\x1F\x7F]/g,
      "_"
    );
  });
}
__name(applyTextReplacements$1, "applyTextReplacements$1");
const IMAGE_WORKFLOW_FORMATS = {
  extensions: [".png", ".webp", ".svg", ".avif"],
  mimeTypes: ["image/png", "image/webp", "image/svg+xml", "image/avif"]
};
const AUDIO_WORKFLOW_FORMATS = {
  extensions: [".mp3", ".ogg", ".flac"],
  mimeTypes: ["audio/mpeg", "audio/ogg", "audio/flac", "audio/x-flac"]
};
const VIDEO_WORKFLOW_FORMATS = {
  extensions: [".mp4", ".mov", ".m4v", ".webm"],
  mimeTypes: ["video/mp4", "video/quicktime", "video/x-m4v", "video/webm"]
};
const MODEL_WORKFLOW_FORMATS = {
  extensions: [".glb"],
  mimeTypes: ["model/gltf-binary"]
};
const DATA_WORKFLOW_FORMATS = {
  extensions: [".json", ".latent", ".safetensors"],
  mimeTypes: ["application/json"]
};
const ALL_WORKFLOW_FORMATS = {
  extensions: [
    ...IMAGE_WORKFLOW_FORMATS.extensions,
    ...AUDIO_WORKFLOW_FORMATS.extensions,
    ...VIDEO_WORKFLOW_FORMATS.extensions,
    ...MODEL_WORKFLOW_FORMATS.extensions,
    ...DATA_WORKFLOW_FORMATS.extensions
  ],
  mimeTypes: [
    ...IMAGE_WORKFLOW_FORMATS.mimeTypes,
    ...AUDIO_WORKFLOW_FORMATS.mimeTypes,
    ...VIDEO_WORKFLOW_FORMATS.mimeTypes,
    ...MODEL_WORKFLOW_FORMATS.mimeTypes,
    ...DATA_WORKFLOW_FORMATS.mimeTypes
  ]
};
const WORKFLOW_ACCEPT_STRING = [
  ...ALL_WORKFLOW_FORMATS.extensions,
  ...ALL_WORKFLOW_FORMATS.mimeTypes
].join(",");
let ComfyDialog$1 = class ComfyDialog2 extends EventTarget {
  static {
    __name(this, "ComfyDialog");
  }
  element;
  // @ts-expect-error fixme ts strict error
  textElement;
  #buttons;
  constructor(type = "div", buttons = null) {
    super();
    this.#buttons = buttons;
    this.element = $el(type + ".comfy-modal", { parent: document.body }, [
      $el("div.comfy-modal-content", [
        $el("p", { $: /* @__PURE__ */ __name((p) => this.textElement = p, "$") }),
        ...this.createButtons()
      ])
    ]);
  }
  createButtons() {
    return this.#buttons ?? [
      $el("button", {
        type: "button",
        textContent: "Close",
        onclick: /* @__PURE__ */ __name(() => this.close(), "onclick")
      })
    ];
  }
  close() {
    this.element.style.display = "none";
  }
  // @ts-expect-error fixme ts strict error
  show(html) {
    if (typeof html === "string") {
      this.textElement.innerHTML = html;
    } else {
      this.textElement.replaceChildren(
        ...html instanceof Array ? html : [html]
      );
    }
    this.element.style.display = "flex";
  }
};
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.dialog = window.comfyAPI.dialog || {};
window.comfyAPI.dialog.ComfyDialog = ComfyDialog$1;
class ComfySettingsDialog extends ComfyDialog$1 {
  static {
    __name(this, "ComfySettingsDialog");
  }
  app;
  constructor(app2) {
    super();
    this.app = app2;
  }
  dispatchChange(id2, value, oldValue) {
    this.dispatchEvent(
      new CustomEvent(id2 + ".change", {
        detail: {
          value,
          oldValue
        }
      })
    );
  }
  /**
   * @deprecated Use `settingStore.settingValues` instead.
   */
  get settingsValues() {
    return useSettingStore().settingValues;
  }
  /**
   * @deprecated Use `settingStore.settingsById` instead.
   */
  get settingsLookup() {
    return useSettingStore().settingsById;
  }
  /**
   * @deprecated Use `settingStore.settingsById` instead.
   */
  get settingsParamLookup() {
    return useSettingStore().settingsById;
  }
  /**
   * @deprecated Use `settingStore.get` instead.
   */
  getSettingValue(id2, defaultValue) {
    if (defaultValue !== void 0) {
      console.warn(
        `Parameter defaultValue is deprecated. The default value in settings definition will be used instead.`
      );
    }
    return useSettingStore().get(id2);
  }
  /**
   * @deprecated Use `settingStore.getDefaultValue` instead.
   */
  getSettingDefaultValue(id2) {
    return useSettingStore().getDefaultValue(id2);
  }
  /**
   * @deprecated Use `settingStore.set` instead.
   */
  async setSettingValueAsync(id2, value) {
    await useSettingStore().set(id2, value);
  }
  /**
   * @deprecated Use `settingStore.set` instead.
   */
  setSettingValue(id2, value) {
    useSettingStore().set(id2, value).catch((err) => {
      useToastStore().addAlert(
        t("toastMessages.errorSaveSetting", { id: id2, err })
      );
    });
  }
  /**
   * @deprecated Deprecated for external callers/extensions. Use
   * `ComfyExtension.settings` field instead.
   *
   * Example:
   * ```ts
   * app.registerExtension({
   *   name: 'My Extension',
   *   settings: [
   *     {
   *       id: 'My.Setting',
   *       name: 'My Setting',
   *       type: 'text',
   *       defaultValue: 'Hello, world!'
   *     }
   *   ]
   * })
   * ```
   */
  addSetting(params) {
    const settingStore = useSettingStore();
    settingStore.addSetting(params);
    return {
      get value() {
        return settingStore.get(params.id);
      },
      set value(v2) {
        settingStore.set(params.id, v2);
      }
    };
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.settings = window.comfyAPI.settings || {};
window.comfyAPI.settings.ComfySettingsDialog = ComfySettingsDialog;
function toggleSwitch(name2, items, e2) {
  const onChange2 = e2?.onChange;
  let selectedIndex;
  let elements;
  function updateSelected(index2) {
    if (selectedIndex != null) {
      elements[selectedIndex].classList.remove("comfy-toggle-selected");
    }
    onChange2?.({
      item: items[index2],
      // @ts-expect-error fixme ts strict error
      prev: selectedIndex == null ? void 0 : items[selectedIndex]
    });
    selectedIndex = index2;
    elements[selectedIndex].classList.add("comfy-toggle-selected");
  }
  __name(updateSelected, "updateSelected");
  elements = items.map((item, i) => {
    if (typeof item === "string") item = { text: item };
    if (!item.value) item.value = item.text;
    const toggle = $el(
      "label",
      {
        textContent: item.text,
        title: item.tooltip ?? ""
      },
      $el("input", {
        name: name2,
        type: "radio",
        value: item.value ?? item.text,
        checked: item.selected,
        onchange: /* @__PURE__ */ __name(() => {
          updateSelected(i);
        }, "onchange")
      })
    );
    if (item.selected) {
      updateSelected(i);
    }
    return toggle;
  });
  const container = $el("div.comfy-toggle-switch", elements);
  if (selectedIndex == null) {
    elements[0].children[0].checked = true;
    updateSelected(0);
  }
  return container;
}
__name(toggleSwitch, "toggleSwitch");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.toggleSwitch = window.comfyAPI.toggleSwitch || {};
window.comfyAPI.toggleSwitch.toggleSwitch = toggleSwitch;
const ComfyDialog = ComfyDialog$1;
function $el(tag, propsOrChildren, children) {
  const split = tag.split(".");
  const element = document.createElement(split.shift());
  if (split.length > 0) {
    element.classList.add(...split);
  }
  if (propsOrChildren) {
    if (typeof propsOrChildren === "string") {
      propsOrChildren = { textContent: propsOrChildren };
    } else if (propsOrChildren instanceof Element) {
      propsOrChildren = [propsOrChildren];
    }
    if (Array.isArray(propsOrChildren)) {
      element.append(...propsOrChildren);
    } else {
      const {
        parent,
        $: cb,
        dataset,
        style,
        ...rest
      } = propsOrChildren;
      if (rest.for) {
        element.setAttribute("for", rest.for);
      }
      if (style) {
        Object.assign(element.style, style);
      }
      if (dataset) {
        Object.assign(element.dataset, dataset);
      }
      Object.assign(element, rest);
      if (children) {
        element.append(...Array.isArray(children) ? children : [children]);
      }
      if (parent) {
        parent.append(element);
      }
      if (cb) {
        cb(element);
      }
    }
  }
  return element;
}
__name($el, "$el");
function dragElement(dragEl) {
  var posDiffX = 0, posDiffY = 0, posStartX = 0, posStartY = 0, newPosX = 0, newPosY = 0;
  if (dragEl.getElementsByClassName("drag-handle")[0]) {
    dragEl.getElementsByClassName("drag-handle")[0].onmousedown = dragMouseDown;
  } else {
    dragEl.onmousedown = dragMouseDown;
  }
  new ResizeObserver(() => {
    ensureInBounds();
  }).observe(dragEl);
  function ensureInBounds() {
    try {
      newPosX = Math.min(
        document.body.clientWidth - dragEl.clientWidth,
        Math.max(0, dragEl.offsetLeft)
      );
      newPosY = Math.min(
        document.body.clientHeight - dragEl.clientHeight,
        Math.max(0, dragEl.offsetTop)
      );
      positionElement();
    } catch (exception) {
    }
  }
  __name(ensureInBounds, "ensureInBounds");
  function positionElement() {
    if (dragEl.style.display === "none") return;
    const halfWidth = document.body.clientWidth / 2;
    const anchorRight = newPosX + dragEl.clientWidth / 2 > halfWidth;
    if (anchorRight) {
      dragEl.style.left = "unset";
      dragEl.style.right = document.body.clientWidth - newPosX - dragEl.clientWidth + "px";
    } else {
      dragEl.style.left = newPosX + "px";
      dragEl.style.right = "unset";
    }
    dragEl.style.top = newPosY + "px";
    dragEl.style.bottom = "unset";
    if (savePos) {
      localStorage.setItem(
        "Comfy.MenuPosition",
        JSON.stringify({
          x: dragEl.offsetLeft,
          y: dragEl.offsetTop
        })
      );
    }
  }
  __name(positionElement, "positionElement");
  function restorePos() {
    let posString = localStorage.getItem("Comfy.MenuPosition");
    if (posString) {
      const pos = JSON.parse(posString);
      newPosX = pos.x;
      newPosY = pos.y;
      positionElement();
      ensureInBounds();
    }
  }
  __name(restorePos, "restorePos");
  let savePos = void 0;
  restorePos();
  savePos = true;
  function dragMouseDown(e2) {
    e2 = e2 || window.event;
    e2.preventDefault();
    posStartX = e2.clientX;
    posStartY = e2.clientY;
    document.onmouseup = closeDragElement;
    document.onmousemove = elementDrag;
  }
  __name(dragMouseDown, "dragMouseDown");
  function elementDrag(e2) {
    e2 = e2 || window.event;
    e2.preventDefault();
    dragEl.classList.add("comfy-menu-manual-pos");
    posDiffX = e2.clientX - posStartX;
    posDiffY = e2.clientY - posStartY;
    posStartX = e2.clientX;
    posStartY = e2.clientY;
    newPosX = Math.min(
      document.body.clientWidth - dragEl.clientWidth,
      Math.max(0, dragEl.offsetLeft + posDiffX)
    );
    newPosY = Math.min(
      document.body.clientHeight - dragEl.clientHeight,
      Math.max(0, dragEl.offsetTop + posDiffY)
    );
    positionElement();
  }
  __name(elementDrag, "elementDrag");
  window.addEventListener("resize", () => {
    ensureInBounds();
  });
  function closeDragElement() {
    document.onmouseup = null;
    document.onmousemove = null;
  }
  __name(closeDragElement, "closeDragElement");
  return restorePos;
}
__name(dragElement, "dragElement");
class ComfyList {
  static {
    __name(this, "ComfyList");
  }
  #type;
  #text;
  #reverse;
  element;
  button;
  // @ts-expect-error fixme ts strict error
  constructor(text, type, reverse) {
    this.#text = text;
    this.#type = type || text.toLowerCase();
    this.#reverse = reverse || false;
    this.element = $el("div.comfy-list");
    this.element.style.display = "none";
  }
  get visible() {
    return this.element.style.display !== "none";
  }
  async load() {
    const items = await api.getItems(this.#type);
    this.element.replaceChildren(
      ...Object.keys(items).flatMap((section) => [
        $el("h4", {
          textContent: section
        }),
        $el("div.comfy-list-items", [
          // @ts-expect-error fixme ts strict error
          ...(this.#reverse ? items[section].reverse() : items[section]).map(
            (item) => {
              const removeAction = "remove" in item ? item.remove : {
                name: "Delete",
                cb: /* @__PURE__ */ __name(() => api.deleteItem(this.#type, item.prompt[1]), "cb")
              };
              return $el("div", { textContent: item.prompt[0] + ": " }, [
                $el("button", {
                  textContent: "Load",
                  onclick: /* @__PURE__ */ __name(async () => {
                    await app$1.loadGraphData(
                      // @ts-expect-error fixme ts strict error
                      item.prompt[3].extra_pnginfo.workflow,
                      true,
                      false
                    );
                    if ("outputs" in item) {
                      app$1.nodeOutputs = {};
                      for (const [key, value] of Object.entries(item.outputs)) {
                        const realKey = item["meta"]?.[key]?.display_node ?? key;
                        app$1.nodeOutputs[realKey] = value;
                      }
                    }
                  }, "onclick")
                }),
                $el("button", {
                  textContent: removeAction.name,
                  onclick: /* @__PURE__ */ __name(async () => {
                    await removeAction.cb();
                    await this.update();
                  }, "onclick")
                })
              ]);
            }
          )
        ])
      ]),
      $el("div.comfy-list-actions", [
        $el("button", {
          textContent: "Clear " + this.#text,
          onclick: /* @__PURE__ */ __name(async () => {
            await api.clearItems(this.#type);
            await this.load();
          }, "onclick")
        }),
        $el("button", { textContent: "Refresh", onclick: /* @__PURE__ */ __name(() => this.load(), "onclick") })
      ])
    );
  }
  async update() {
    if (this.visible) {
      await this.load();
    }
  }
  async show() {
    this.element.style.display = "block";
    this.button.textContent = "Close";
    await this.load();
  }
  hide() {
    this.element.style.display = "none";
    this.button.textContent = "View " + this.#text;
  }
  toggle() {
    if (this.visible) {
      this.hide();
      return false;
    } else {
      this.show();
      return true;
    }
  }
}
class ComfyUI {
  static {
    __name(this, "ComfyUI");
  }
  app;
  dialog;
  settings;
  batchCount;
  lastQueueSize;
  queue;
  history;
  // @ts-expect-error fixme ts strict error
  autoQueueMode;
  // @ts-expect-error fixme ts strict error
  graphHasChanged;
  // @ts-expect-error fixme ts strict error
  autoQueueEnabled;
  // @ts-expect-error fixme ts strict error
  menuContainer;
  // @ts-expect-error fixme ts strict error
  queueSize;
  // @ts-expect-error fixme ts strict error
  restoreMenuPosition;
  // @ts-expect-error fixme ts strict error
  loadFile;
  // @ts-expect-error fixme ts strict error
  constructor(app2) {
    this.app = app2;
    this.dialog = new ComfyDialog();
    this.settings = new ComfySettingsDialog(app2);
    this.batchCount = 1;
    this.lastQueueSize = 0;
    this.queue = new ComfyList("Queue");
    this.history = new ComfyList("History", "history", true);
    api.addEventListener("status", () => {
      this.queue.update();
      this.history.update();
    });
    this.setup(document.body);
  }
  setup(containerElement) {
    const fileInput = $el("input", {
      id: "comfy-file-input",
      type: "file",
      accept: WORKFLOW_ACCEPT_STRING,
      style: { display: "none" },
      parent: document.body,
      onchange: /* @__PURE__ */ __name(async () => {
        const file = fileInput.files?.[0];
        if (file) {
          await app$1.handleFile(file, "file_button");
          fileInput.value = "";
        }
      }, "onchange")
    });
    this.loadFile = () => fileInput.click();
    const autoQueueModeEl = toggleSwitch(
      "autoQueueMode",
      [
        {
          text: "instant",
          tooltip: "A new prompt will be queued as soon as the queue reaches 0"
        },
        {
          text: "change",
          tooltip: "A new prompt will be queued when the queue is at 0 and the graph is/has changed"
        }
      ],
      {
        // @ts-expect-error fixme ts strict error
        onChange: /* @__PURE__ */ __name((value) => {
          this.autoQueueMode = value.item.value;
        }, "onChange")
      }
    );
    autoQueueModeEl.style.display = "none";
    api.addEventListener("graphChanged", () => {
      if (this.autoQueueMode === "change" && this.autoQueueEnabled === true) {
        if (this.lastQueueSize === 0) {
          this.graphHasChanged = false;
          app$1.queuePrompt(0, this.batchCount);
        } else {
          this.graphHasChanged = true;
        }
      }
    });
    this.menuContainer = $el(
      "div.comfy-menu.no-drag",
      { parent: containerElement },
      [
        $el(
          "div.drag-handle.comfy-menu-header",
          {
            style: {
              overflow: "hidden",
              position: "relative",
              width: "100%",
              cursor: "default"
            }
          },
          [
            $el("span.drag-handle"),
            $el("span.comfy-menu-queue-size", {
              $: /* @__PURE__ */ __name((q) => this.queueSize = q, "$")
            }),
            $el("div.comfy-menu-actions", [
              $el("button.comfy-settings-btn", {
                textContent: "⚙️",
                onclick: /* @__PURE__ */ __name(() => {
                  useDialogService().showSettingsDialog();
                }, "onclick")
              }),
              $el("button.comfy-close-menu-btn", {
                textContent: "×",
                onclick: /* @__PURE__ */ __name(() => {
                  useWorkspaceStore().focusMode = true;
                }, "onclick")
              })
            ])
          ]
        ),
        $el("button.comfy-queue-btn", {
          id: "queue-button",
          textContent: "Queue Prompt",
          onclick: /* @__PURE__ */ __name(() => {
            app$1.queuePrompt(0, this.batchCount);
          }, "onclick")
        }),
        $el("div", {}, [
          $el("label", { innerHTML: "Extra options" }, [
            $el("input", {
              type: "checkbox",
              // @ts-expect-error fixme ts strict error
              onchange: /* @__PURE__ */ __name((i) => {
                document.getElementById("extraOptions").style.display = i.srcElement.checked ? "block" : "none";
                this.batchCount = i.srcElement.checked ? Number.parseInt(
                  document.getElementById(
                    "batchCountInputRange"
                  ).value
                ) : 1;
                document.getElementById(
                  "autoQueueCheckbox"
                ).checked = false;
                this.autoQueueEnabled = false;
              }, "onchange")
            })
          ])
        ]),
        $el(
          "div",
          { id: "extraOptions", style: { width: "100%", display: "none" } },
          [
            $el("div", [
              $el("label", { innerHTML: "Batch count" }),
              $el("input", {
                id: "batchCountInputNumber",
                type: "number",
                value: this.batchCount,
                min: "1",
                style: { width: "35%", marginLeft: "0.4em" },
                // @ts-expect-error fixme ts strict error
                oninput: /* @__PURE__ */ __name((i) => {
                  this.batchCount = i.target.value;
                  document.getElementById(
                    "batchCountInputRange"
                  ).value = this.batchCount.toString();
                }, "oninput")
              }),
              $el("input", {
                id: "batchCountInputRange",
                type: "range",
                min: "1",
                max: "100",
                value: this.batchCount,
                // @ts-expect-error fixme ts strict error
                oninput: /* @__PURE__ */ __name((i) => {
                  this.batchCount = i.srcElement.value;
                  document.getElementById(
                    "batchCountInputNumber"
                  ).value = i.srcElement.value;
                }, "oninput")
              })
            ]),
            $el("div", [
              $el("label", {
                for: "autoQueueCheckbox",
                innerHTML: "Auto Queue"
              }),
              $el("input", {
                id: "autoQueueCheckbox",
                type: "checkbox",
                checked: false,
                title: "Automatically queue prompt when the queue size hits 0",
                // @ts-expect-error fixme ts strict error
                onchange: /* @__PURE__ */ __name((e2) => {
                  this.autoQueueEnabled = e2.target.checked;
                  autoQueueModeEl.style.display = this.autoQueueEnabled ? "" : "none";
                }, "onchange")
              }),
              autoQueueModeEl
            ])
          ]
        ),
        $el("div.comfy-menu-btns", [
          $el("button", {
            id: "queue-front-button",
            textContent: "Queue Front",
            onclick: /* @__PURE__ */ __name(() => {
              app$1.queuePrompt(-1, this.batchCount);
            }, "onclick")
          }),
          $el("button", {
            $: /* @__PURE__ */ __name((b) => this.queue.button = b, "$"),
            id: "comfy-view-queue-button",
            textContent: "View Queue",
            onclick: /* @__PURE__ */ __name(() => {
              this.history.hide();
              this.queue.toggle();
            }, "onclick")
          }),
          $el("button", {
            $: /* @__PURE__ */ __name((b) => this.history.button = b, "$"),
            id: "comfy-view-history-button",
            textContent: "View History",
            onclick: /* @__PURE__ */ __name(() => {
              this.queue.hide();
              this.history.toggle();
            }, "onclick")
          })
        ]),
        this.queue.element,
        this.history.element,
        $el("button", {
          id: "comfy-save-button",
          textContent: "Save",
          onclick: /* @__PURE__ */ __name(() => {
            useCommandStore().execute("Comfy.ExportWorkflow");
          }, "onclick")
        }),
        $el("button", {
          id: "comfy-dev-save-api-button",
          textContent: "Save (API Format)",
          style: { width: "100%", display: "none" },
          onclick: /* @__PURE__ */ __name(() => {
            useCommandStore().execute("Comfy.ExportWorkflowAPI");
          }, "onclick")
        }),
        $el("button", {
          id: "comfy-load-button",
          textContent: "Load",
          onclick: /* @__PURE__ */ __name(() => fileInput.click(), "onclick")
        }),
        $el("button", {
          id: "comfy-refresh-button",
          textContent: "Refresh",
          onclick: /* @__PURE__ */ __name(() => app$1.refreshComboInNodes(), "onclick")
        }),
        $el("button", {
          id: "comfy-clipspace-button",
          textContent: "Clipspace",
          onclick: /* @__PURE__ */ __name(() => app$1.openClipspace(), "onclick")
        }),
        $el("button", {
          id: "comfy-clear-button",
          textContent: "Clear",
          onclick: /* @__PURE__ */ __name(() => {
            if (!useSettingStore().get("Comfy.ConfirmClear") || confirm("Clear workflow?")) {
              app$1.clean();
              useLitegraphService().resetView();
              api.dispatchCustomEvent("graphCleared");
            }
          }, "onclick")
        }),
        $el("button", {
          id: "comfy-load-default-button",
          textContent: "Load Default",
          onclick: /* @__PURE__ */ __name(async () => {
            if (!useSettingStore().get("Comfy.ConfirmClear") || confirm("Load default workflow?")) {
              useLitegraphService().resetView();
              await app$1.loadGraphData();
            }
          }, "onclick")
        }),
        $el("button", {
          id: "comfy-reset-view-button",
          textContent: "Reset View",
          onclick: /* @__PURE__ */ __name(async () => {
            useLitegraphService().resetView();
          }, "onclick")
        })
      ]
    );
    this.menuContainer.style.display = "none";
    this.restoreMenuPosition = dragElement(this.menuContainer);
    this.setStatus({ exec_info: { queue_remaining: "X" } });
  }
  setStatus(status) {
    this.queueSize.textContent = "Queue size: " + (status ? status.exec_info.queue_remaining : "ERR");
    if (status) {
      if (this.lastQueueSize != 0 && status.exec_info.queue_remaining == 0 && this.autoQueueEnabled && (this.autoQueueMode === "instant" || this.graphHasChanged) && !app$1.lastExecutionError) {
        app$1.queuePrompt(0, this.batchCount);
        status.exec_info.queue_remaining += this.batchCount;
        this.graphHasChanged = false;
      }
      this.lastQueueSize = status.exec_info.queue_remaining;
    }
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.ui = window.comfyAPI.ui || {};
window.comfyAPI.ui.ComfyDialog = ComfyDialog;
window.comfyAPI.ui.$el = $el;
window.comfyAPI.ui.ComfyUI = ComfyUI;
function clone$1(obj) {
  try {
    if (typeof structuredClone !== "undefined") {
      return structuredClone(obj);
    }
  } catch (error) {
  }
  return JSON.parse(JSON.stringify(obj));
}
__name(clone$1, "clone$1");
function applyTextReplacements(app2, value) {
  return applyTextReplacements$1(app2.rootGraph, value);
}
__name(applyTextReplacements, "applyTextReplacements");
async function addStylesheet(urlOrFile, relativeTo) {
  return new Promise((res, rej) => {
    let url;
    if (urlOrFile.endsWith(".js")) {
      url = urlOrFile.substr(0, urlOrFile.length - 2) + "css";
    } else {
      url = new URL(
        urlOrFile,
        relativeTo ?? `${window.location.protocol}//${window.location.host}`
      ).toString();
    }
    $el("link", {
      parent: document.head,
      rel: "stylesheet",
      type: "text/css",
      href: url,
      onload: res,
      onerror: rej
    });
  });
}
__name(addStylesheet, "addStylesheet");
function uploadFile$1(accept) {
  return new Promise((resolve, reject) => {
    const input = document.createElement("input");
    input.type = "file";
    input.accept = accept;
    input.onchange = (e2) => {
      const file = e2.target.files?.[0];
      if (!file) return reject(new Error("No file selected"));
      resolve(file);
    };
    input.click();
  });
}
__name(uploadFile$1, "uploadFile$1");
function prop(target, name2, defaultValue, onChanged) {
  let currentValue;
  Object.defineProperty(target, name2, {
    get() {
      return currentValue;
    },
    set(newValue2) {
      const prevValue = currentValue;
      currentValue = newValue2;
      onChanged?.(currentValue, prevValue, target, name2);
    }
  });
  return defaultValue;
}
__name(prop, "prop");
function getStorageValue(id2) {
  const clientId = api.clientId ?? api.initialClientId;
  return (clientId && sessionStorage.getItem(`${id2}:${clientId}`)) ?? localStorage.getItem(id2);
}
__name(getStorageValue, "getStorageValue");
function setStorageValue(id2, value) {
  const clientId = api.clientId ?? api.initialClientId;
  if (clientId) {
    sessionStorage.setItem(`${id2}:${clientId}`, value);
  }
  localStorage.setItem(id2, value);
}
__name(setStorageValue, "setStorageValue");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.utils = window.comfyAPI.utils || {};
window.comfyAPI.utils.clone = clone$1;
window.comfyAPI.utils.applyTextReplacements = applyTextReplacements;
window.comfyAPI.utils.addStylesheet = addStylesheet;
window.comfyAPI.utils.uploadFile = uploadFile$1;
window.comfyAPI.utils.prop = prop;
window.comfyAPI.utils.getStorageValue = getStorageValue;
window.comfyAPI.utils.setStorageValue = setStorageValue;
const THEME_PROPERTY_MAP = {
  NODE_BOX_OUTLINE_COLOR: "component-node-border",
  NODE_DEFAULT_BGCOLOR: "component-node-background",
  NODE_DEFAULT_BOXCOLOR: "node-component-header-icon",
  NODE_DEFAULT_COLOR: "node-component-header-surface",
  NODE_TITLE_COLOR: "node-component-header",
  WIDGET_BGCOLOR: "component-node-widget-background",
  WIDGET_TEXT_COLOR: "component-node-foreground"
};
const useColorPaletteService = /* @__PURE__ */ __name(() => {
  const colorPaletteStore = useColorPaletteStore();
  const settingStore = useSettingStore();
  const nodeDefStore = useNodeDefStore();
  const { wrapWithErrorHandling, wrapWithErrorHandlingAsync } = useErrorHandling();
  const validateColorPalette = /* @__PURE__ */ __name((data) => {
    const result = paletteSchema.safeParse(data);
    if (result.success) return result.data;
    const error = fromZodError(result.error);
    throw new Error(`Invalid color palette against zod schema:
${error}`);
  }, "validateColorPalette");
  const persistCustomColorPalettes = /* @__PURE__ */ __name(async () => {
    await settingStore.set(
      "Comfy.CustomColorPalettes",
      colorPaletteStore.customPalettes
    );
  }, "persistCustomColorPalettes");
  const deleteCustomColorPalette = /* @__PURE__ */ __name(async (colorPaletteId) => {
    colorPaletteStore.deleteCustomPalette(colorPaletteId);
    await persistCustomColorPalettes();
  }, "deleteCustomColorPalette");
  const addCustomColorPalette = /* @__PURE__ */ __name(async (colorPalette) => {
    validateColorPalette(colorPalette);
    colorPaletteStore.addCustomPalette(colorPalette);
    await persistCustomColorPalettes();
  }, "addCustomColorPalette");
  const loadLinkColorPalette = /* @__PURE__ */ __name((linkColorPalette) => {
    const types = Object.fromEntries(
      Array.from(nodeDefStore.nodeDataTypes).map((type) => [type, ""])
    );
    Object.assign(
      app$1.canvas.default_connection_color_byType,
      types,
      linkColorPalette
    );
    Object.assign(LGraphCanvas.link_type_colors, types, linkColorPalette);
  }, "loadLinkColorPalette");
  function validThemeProp(propertyMaybe) {
    return propertyMaybe in THEME_PROPERTY_MAP;
  }
  __name(validThemeProp, "validThemeProp");
  function loadLinkColorPaletteForVueNodes(linkColorPalette) {
    if (!linkColorPalette) return;
    const rootStyle = document.body?.style;
    if (!rootStyle) return;
    for (const dataType of nodeDefStore.nodeDataTypes) {
      const cssVar = `color-datatype-${dataType}`;
      const valueMaybe = linkColorPalette[dataType];
      if (valueMaybe) {
        rootStyle.setProperty(`--${cssVar}`, valueMaybe);
      } else {
        rootStyle.removeProperty(`--${cssVar}`);
      }
    }
  }
  __name(loadLinkColorPaletteForVueNodes, "loadLinkColorPaletteForVueNodes");
  function loadLitegraphForVueNodes(palette, colorPaletteId) {
    if (!palette) return;
    const rootStyle = document.body?.style;
    if (!rootStyle) return;
    for (const themeVar of Object.keys(THEME_PROPERTY_MAP)) {
      if (!validThemeProp(themeVar)) {
        continue;
      }
      const cssVar = THEME_PROPERTY_MAP[themeVar];
      if (colorPaletteId === "dark" || colorPaletteId === "light") {
        rootStyle.removeProperty(`--${cssVar}`);
        continue;
      }
      const valueMaybe = palette[themeVar];
      if (valueMaybe) {
        rootStyle.setProperty(`--${cssVar}`, valueMaybe);
      } else {
        rootStyle.removeProperty(`--${cssVar}`);
      }
    }
  }
  __name(loadLitegraphForVueNodes, "loadLitegraphForVueNodes");
  const loadLiteGraphColorPalette = /* @__PURE__ */ __name((palette) => {
    app$1.canvas.node_title_color = palette.NODE_TITLE_COLOR;
    app$1.canvas.default_link_color = palette.LINK_COLOR;
    const backgroundImage = settingStore.get("Comfy.Canvas.BackgroundImage");
    if (backgroundImage) {
      app$1.canvas.clear_background_color = "transparent";
    } else {
      app$1.canvas.background_image = palette.BACKGROUND_IMAGE;
      app$1.canvas.clear_background_color = palette.CLEAR_BACKGROUND_COLOR;
    }
    app$1.canvas._pattern = void 0;
    for (const [key, value] of Object.entries(palette)) {
      if (Object.prototype.hasOwnProperty.call(LiteGraph, key)) {
        if (key === "NODE_DEFAULT_SHAPE" && typeof value === "string") {
          console.warn(
            `litegraph_base.NODE_DEFAULT_SHAPE only accepts [${[
              LiteGraph.BOX_SHAPE,
              LiteGraph.ROUND_SHAPE,
              LiteGraph.CARD_SHAPE
            ].join(", ")}] but got ${value}`
          );
          LiteGraph.NODE_DEFAULT_SHAPE = LiteGraph.ROUND_SHAPE;
        } else {
          LiteGraph[key] = value;
        }
      }
    }
  }, "loadLiteGraphColorPalette");
  const getOptionalKeys = /* @__PURE__ */ __name((schema) => {
    const optionalKeys = [];
    const shape2 = schema.shape;
    for (const [key, value] of Object.entries(shape2)) {
      if (value instanceof z.ZodOptional || value instanceof z.ZodDefault) {
        optionalKeys.push(key);
      }
    }
    return optionalKeys;
  }, "getOptionalKeys");
  const optionalComfyBaseKeys = getOptionalKeys(comfyBaseSchema);
  const loadComfyColorPalette = /* @__PURE__ */ __name((comfyColorPalette) => {
    if (!comfyColorPalette) return;
    const rootStyle = document.documentElement.style;
    for (const [key, value] of Object.entries(comfyColorPalette)) {
      rootStyle.setProperty("--" + key, value);
    }
    for (const optionalKey of optionalComfyBaseKeys) {
      if (!(optionalKey in comfyColorPalette)) {
        rootStyle.setProperty(
          "--" + optionalKey,
          `var(--palette-${optionalKey})`
        );
      }
    }
    const backgroundImage = settingStore.get("Comfy.Canvas.BackgroundImage");
    if (backgroundImage) {
      rootStyle.setProperty(
        "--bg-img",
        `url('${backgroundImage}') no-repeat center /cover`
      );
    } else {
      rootStyle.removeProperty("--bg-img");
    }
  }, "loadComfyColorPalette");
  const loadColorPalette = /* @__PURE__ */ __name(async (colorPaletteId) => {
    const colorPalette = colorPaletteStore.palettesLookup[colorPaletteId];
    if (!colorPalette) {
      throw new Error(`Color palette ${colorPaletteId} not found`);
    }
    const completedPalette = colorPaletteStore.completePalette(colorPalette);
    loadLinkColorPalette(completedPalette.colors.node_slot);
    loadLiteGraphColorPalette(completedPalette.colors.litegraph_base);
    loadLitegraphForVueNodes(
      completedPalette.colors.litegraph_base,
      colorPaletteId
    );
    loadLinkColorPaletteForVueNodes(completedPalette.colors.node_slot);
    loadComfyColorPalette(completedPalette.colors.comfy_base);
    app$1.canvas.setDirty(true, true);
    colorPaletteStore.activePaletteId = colorPaletteId;
  }, "loadColorPalette");
  const exportColorPalette = /* @__PURE__ */ __name((colorPaletteId) => {
    const colorPalette = colorPaletteStore.palettesLookup[colorPaletteId];
    if (!colorPalette) {
      throw new Error(`Color palette ${colorPaletteId} not found`);
    }
    downloadBlob(
      colorPalette.id + ".json",
      new Blob([JSON.stringify(toRaw(colorPalette), null, 2)], {
        type: "application/json"
      })
    );
  }, "exportColorPalette");
  const importColorPalette = /* @__PURE__ */ __name(async () => {
    const file = await uploadFile$1("application/json");
    const text = await file.text();
    const palette = JSON.parse(text);
    await addCustomColorPalette(palette);
    return palette;
  }, "importColorPalette");
  return {
    getActiveColorPalette: /* @__PURE__ */ __name(() => colorPaletteStore.completedActivePalette, "getActiveColorPalette"),
    addCustomColorPalette: wrapWithErrorHandlingAsync(addCustomColorPalette),
    deleteCustomColorPalette: wrapWithErrorHandlingAsync(
      deleteCustomColorPalette
    ),
    loadColorPalette: wrapWithErrorHandlingAsync(loadColorPalette),
    exportColorPalette: wrapWithErrorHandling(exportColorPalette),
    importColorPalette: wrapWithErrorHandlingAsync(importColorPalette)
  };
}, "useColorPaletteService");
function reconcileHistoryV1(serverHistory, clientHistory, maxItems, lastKnownQueueIndex) {
  const sortedServerHistory = serverHistory.sort(
    (a, b) => b.prompt[0] - a.prompt[0]
  );
  const serverPromptIds = new Set(
    sortedServerHistory.map((item) => item.prompt[1])
  );
  const itemsAddedSinceLastSync = lastKnownQueueIndex === void 0 ? sortedServerHistory : sortedServerHistory.filter(
    (item) => item.prompt[0] > lastKnownQueueIndex
  );
  const clientItemsStillOnServer = clientHistory.filter(
    (item) => serverPromptIds.has(item.prompt[1])
  );
  return [...itemsAddedSinceLastSync, ...clientItemsStillOnServer].sort((a, b) => b.prompt[0] - a.prompt[0]).slice(0, maxItems);
}
__name(reconcileHistoryV1, "reconcileHistoryV1");
function reconcileHistory(serverHistory, clientHistory, maxItems, lastKnownQueueIndex) {
  return reconcileHistoryV1(
    serverHistory,
    clientHistory,
    maxItems,
    lastKnownQueueIndex
  );
}
__name(reconcileHistory, "reconcileHistory");
class LegacyMenuCompat {
  static {
    __name(this, "LegacyMenuCompat");
  }
  originalMethods = /* @__PURE__ */ new Map();
  hasWarned = /* @__PURE__ */ new Set();
  currentExtension = null;
  isExtracting = false;
  wrapperMethods = /* @__PURE__ */ new Map();
  preWrapperMethods = /* @__PURE__ */ new Map();
  wrapperInstalled = /* @__PURE__ */ new Map();
  /**
   * Set the name of the extension that is currently being set up.
   * This allows us to track which extension is monkey-patching.
   * @param extensionName The name of the extension
   */
  setCurrentExtension(extensionName) {
    this.currentExtension = extensionName;
  }
  /**
   * Register a wrapper method that should NOT be treated as a legacy monkey-patch.
   * @param methodName The method name
   * @param wrapperFn The wrapper function
   * @param preWrapperFn The method that existed before the wrapper
   * @param prototype The prototype to verify wrapper installation
   */
  registerWrapper(methodName, wrapperFn, preWrapperFn, prototype) {
    this.wrapperMethods.set(
      methodName,
      wrapperFn
    );
    this.preWrapperMethods.set(
      methodName,
      preWrapperFn
    );
    const isInstalled = prototype && prototype[methodName] === wrapperFn;
    this.wrapperInstalled.set(methodName, !!isInstalled);
  }
  /**
   * Install compatibility layer to detect monkey-patching
   * @param prototype The prototype to install on
   * @param methodName The method name to track
   */
  install(prototype, methodName) {
    const originalMethod = prototype[methodName];
    this.originalMethods.set(
      methodName,
      originalMethod
    );
    let currentImpl = originalMethod;
    Object.defineProperty(prototype, methodName, {
      get() {
        return currentImpl;
      },
      set: /* @__PURE__ */ __name((newImpl) => {
        const fnKey = `${methodName}:${newImpl.toString().slice(0, 100)}`;
        if (!this.hasWarned.has(fnKey) && this.currentExtension) {
          this.hasWarned.add(fnKey);
          console.warn(
            `%c[DEPRECATED]%c Monkey-patching ${methodName} is deprecated. (Extension: "${this.currentExtension}")
Please use the new context menu API instead.

See: https://docs.comfy.org/custom-nodes/js/context-menu-migration`,
            "color: orange; font-weight: bold",
            "color: inherit"
          );
        }
        currentImpl = newImpl;
      }, "set")
    });
  }
  /**
   * Extract items that were added by legacy monkey patches.
   *
   * Uses set-based diffing by reference to reliably detect additions regardless
   * of item reordering or replacement. Items present in patchedItems but not in
   * originalItems (by reference equality) are considered additions.
   *
   * Note: If a monkey patch removes items (patchedItems has fewer unique items
   * than originalItems), a warning is logged but we still return any new items.
   *
   * @param methodName The method name that was monkey-patched
   * @param context The context to call methods with
   * @param args Arguments to pass to the methods
   * @returns Array of menu items added by monkey patches
   */
  extractLegacyItems(methodName, context, ...args) {
    if (this.isExtracting) return [];
    const originalMethod = this.originalMethods.get(methodName);
    if (!originalMethod) return [];
    try {
      this.isExtracting = true;
      const originalItems = originalMethod.apply(context, args);
      if (!originalItems) return [];
      const currentMethod = context.constructor.prototype[methodName];
      if (!currentMethod || currentMethod === originalMethod) return [];
      const registeredWrapper = this.wrapperMethods.get(methodName);
      if (registeredWrapper && currentMethod === registeredWrapper) return [];
      const preWrapperMethod = this.preWrapperMethods.get(methodName);
      const wrapperWasInstalled = this.wrapperInstalled.get(methodName);
      const shouldSkipWrapper = preWrapperMethod && wrapperWasInstalled && currentMethod !== preWrapperMethod;
      const methodToCall = shouldSkipWrapper ? preWrapperMethod : currentMethod;
      const patchedItems = methodToCall.apply(context, args);
      if (!patchedItems) return [];
      const originalSet = new Set(originalItems);
      const addedItems = patchedItems.filter((item) => !originalSet.has(item));
      const retainedOriginalCount = patchedItems.filter(
        (item) => originalSet.has(item)
      ).length;
      if (retainedOriginalCount < originalItems.length) {
        console.warn(
          `[Context Menu Compat] Monkey patch for ${methodName} removed ${originalItems.length - retainedOriginalCount} original menu item(s). This may cause unexpected behavior.`
        );
      }
      return addedItems;
    } catch (e2) {
      console.error("[Context Menu Compat] Failed to extract legacy items:", e2);
      return [];
    } finally {
      this.isExtracting = false;
    }
  }
}
const legacyMenuCompat = new LegacyMenuCompat();
const ALWAYS_ENABLED_EXTENSIONS = [];
const ALWAYS_DISABLED_EXTENSIONS = [
  // pysssss.Locking is replaced by pin/unpin in ComfyUI core.
  // https://github.com/Comfy-Org/litegraph.js/pull/117
  "pysssss.Locking",
  // pysssss.SnapToGrid is replaced by Comfy.Graph.AlwaysSnapToGrid in ComfyUI core.
  // pysssss.SnapToGrid tries to write global app.shiftDown state, which is no longer
  // allowed since v1.3.12.
  // https://github.com/Comfy-Org/ComfyUI_frontend/issues/1176
  "pysssss.SnapToGrid",
  // Favicon status is implemented in ComfyUI core in v1.20.
  // https://github.com/Comfy-Org/ComfyUI_frontend/pull/3880
  "pysssss.FaviconStatus",
  "KJNodes.browserstatus"
];
const useExtensionStore = defineStore("extension", () => {
  const extensionByName = ref({});
  const extensions = computed(() => Object.values(extensionByName.value));
  const disabledExtensionNames = ref(/* @__PURE__ */ new Set());
  const inactiveDisabledExtensionNames = computed(() => {
    return Array.from(disabledExtensionNames.value).filter(
      (name2) => !(name2 in extensionByName.value)
    );
  });
  const isExtensionInstalled = /* @__PURE__ */ __name((name2) => name2 in extensionByName.value, "isExtensionInstalled");
  const isExtensionEnabled = /* @__PURE__ */ __name((name2) => !disabledExtensionNames.value.has(name2), "isExtensionEnabled");
  const enabledExtensions = computed(() => {
    return extensions.value.filter((ext) => isExtensionEnabled(ext.name));
  });
  function isExtensionReadOnly(name2) {
    return ALWAYS_DISABLED_EXTENSIONS.includes(name2) || ALWAYS_ENABLED_EXTENSIONS.includes(name2);
  }
  __name(isExtensionReadOnly, "isExtensionReadOnly");
  function registerExtension(extension) {
    if (!extension.name) {
      throw new Error("Extensions must have a 'name' property.");
    }
    if (extensionByName.value[extension.name]) {
      throw new Error(`Extension named '${extension.name}' already registered.`);
    }
    if (disabledExtensionNames.value.has(extension.name)) {
      console.warn(`Extension ${extension.name} is disabled.`);
    }
    extensionByName.value[extension.name] = markRaw(extension);
  }
  __name(registerExtension, "registerExtension");
  function loadDisabledExtensionNames(names) {
    disabledExtensionNames.value = new Set(names);
    for (const name2 of ALWAYS_DISABLED_EXTENSIONS) {
      disabledExtensionNames.value.add(name2);
    }
    for (const name2 of ALWAYS_ENABLED_EXTENSIONS) {
      disabledExtensionNames.value.delete(name2);
    }
  }
  __name(loadDisabledExtensionNames, "loadDisabledExtensionNames");
  const coreExtensionNames = ref([]);
  function captureCoreExtensions() {
    coreExtensionNames.value = extensions.value.map((ext) => ext.name);
  }
  __name(captureCoreExtensions, "captureCoreExtensions");
  function isCoreExtension(name2) {
    return coreExtensionNames.value.includes(name2);
  }
  __name(isCoreExtension, "isCoreExtension");
  const hasThirdPartyExtensions = computed(() => {
    return extensions.value.some((ext) => !isCoreExtension(ext.name));
  });
  return {
    extensions,
    enabledExtensions,
    inactiveDisabledExtensionNames,
    isExtensionInstalled,
    isExtensionEnabled,
    isExtensionReadOnly,
    registerExtension,
    loadDisabledExtensionNames,
    captureCoreExtensions,
    isCoreExtension,
    hasThirdPartyExtensions
  };
});
const CORE_MENU_COMMANDS = [
  [[], ["Comfy.NewBlankWorkflow"]],
  [[], []],
  // Separator after New
  [["File"], ["Comfy.OpenWorkflow"]],
  [
    ["File"],
    [
      "Comfy.SaveWorkflow",
      "Comfy.SaveWorkflowAs",
      "Comfy.ExportWorkflow",
      "Comfy.ExportWorkflowAPI"
    ]
  ],
  [["Edit"], ["Comfy.Undo", "Comfy.Redo"]],
  [["Edit"], ["Comfy.ClearWorkflow"]],
  [["Edit"], ["Comfy.OpenClipspace"]],
  [
    ["Edit"],
    [
      "Comfy.RefreshNodeDefinitions",
      ...[
        "Comfy.Memory.UnloadModels",
        "Comfy.Memory.UnloadModelsAndExecutionCache"
      ]
    ]
  ],
  [["View"], []],
  [
    ["Help"],
    [
      "Comfy.Help.OpenComfyUIIssues",
      "Comfy.Help.OpenComfyUIDocs",
      "Comfy.Help.OpenComfyOrgDiscord",
      "Comfy.Help.OpenComfyUIForum"
    ]
  ],
  [["Help"], ["Comfy.Help.AboutComfyUI", "Comfy.ContactSupport"]]
];
const useMenuItemStore = defineStore("menuItem", () => {
  const commandStore = useCommandStore();
  const menuItems = ref([]);
  const menuItemHasActiveStateChildren = ref({});
  const registerMenuGroup = /* @__PURE__ */ __name((path, items) => {
    let currentLevel = menuItems.value;
    for (let i = 0; i < path.length; i++) {
      const segment = path[i];
      let found = currentLevel.find((item) => item.label === segment);
      if (!found) {
        found = {
          label: segment,
          key: segment,
          items: []
        };
        currentLevel.push(found);
      }
      if (!found.items) {
        found.items = [];
      }
      currentLevel = found.items;
    }
    if (currentLevel.length > 0) {
      currentLevel.push({
        separator: true
      });
    }
    currentLevel.push(...items);
    const parentPath = path.join(".");
    if (!menuItemHasActiveStateChildren.value[parentPath]) {
      menuItemHasActiveStateChildren.value[parentPath] = items.some(
        (item) => item.comfyCommand?.active
      );
    }
  }, "registerMenuGroup");
  const registerCommands = /* @__PURE__ */ __name((path, commandIds) => {
    const items = commandIds.map((commandId) => commandStore.getCommand(commandId)).map(
      (command) => ({
        command: /* @__PURE__ */ __name(() => commandStore.execute(command.id), "command"),
        label: command.menubarLabel,
        icon: command.icon,
        tooltip: command.tooltip,
        comfyCommand: command,
        parentPath: path.join(".")
      })
    );
    registerMenuGroup(path, items);
  }, "registerCommands");
  const loadExtensionMenuCommands = /* @__PURE__ */ __name((extension) => {
    if (!extension.menuCommands) {
      return;
    }
    const extensionCommandIds = new Set(
      extension.commands?.map((command) => command.id) ?? []
    );
    extension.menuCommands.forEach((menuCommand) => {
      const commands2 = menuCommand.commands.filter(
        (command) => extensionCommandIds.has(command)
      );
      if (commands2.length) {
        registerCommands(menuCommand.path, commands2);
      }
    });
  }, "loadExtensionMenuCommands");
  const registerCoreMenuCommands = /* @__PURE__ */ __name(() => {
    for (const [path, commands2] of CORE_MENU_COMMANDS) {
      registerCommands(path, commands2);
    }
  }, "registerCoreMenuCommands");
  return {
    menuItems,
    registerMenuGroup,
    registerCommands,
    loadExtensionMenuCommands,
    registerCoreMenuCommands,
    menuItemHasActiveStateChildren
  };
});
const INLINE_INPUTS = false;
function ensureWidgetForInput(node2, input) {
  node2.widgets ??= [];
  const { widget } = input;
  if (widget && node2.widgets.some((w) => w.name === widget.name)) return;
  node2.widgets.push({
    draw(ctx, _n, _w, y) {
      ctx.save();
      ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;
      ctx.fillText(input.label ?? input.name, 20, y + 15);
      ctx.restore();
    },
    name: input.name,
    options: {},
    serialize: false,
    type: "shim",
    y: 0
  });
  input.alwaysVisible = true;
  input.widget = { name: input.name };
}
__name(ensureWidgetForInput, "ensureWidgetForInput");
function dynamicComboWidget(node2, inputName, untypedInputData, appArg, widgetName) {
  const { addNodeInput } = useLitegraphService();
  const parseResult = zDynamicComboInputSpec.safeParse(untypedInputData);
  if (!parseResult.success) throw new Error("invalid DynamicCombo spec");
  const inputData = parseResult.data;
  const options = Object.fromEntries(
    inputData[1].options.map(({ key, inputs }) => [key, inputs])
  );
  const subSpec = [Object.keys(options), {}];
  const { widget, minWidth, minHeight } = app$1.widgets["COMBO"](
    node2,
    inputName,
    subSpec,
    appArg,
    widgetName
  );
  function isInGroup(e2) {
    return e2.name.startsWith(inputName + ".");
  }
  __name(isInGroup, "isInGroup");
  const updateWidgets = /* @__PURE__ */ __name((value) => {
    if (!node2.widgets) throw new Error("Not Reachable");
    const newSpec = value ? options[value] : void 0;
    const removedInputs = remove(node2.inputs, isInGroup);
    for (const widget2 of remove(node2.widgets, isInGroup)) widget2.onRemove?.();
    if (!newSpec) return;
    const insertionPoint = node2.widgets.findIndex((w) => w === widget) + 1;
    const startingLength = node2.widgets.length;
    const startingInputLength = node2.inputs.length;
    if (insertionPoint === 0)
      throw new Error("Dynamic widget doesn't exist on node");
    const inputTypes = [
      newSpec.required,
      newSpec.optional
    ];
    inputTypes.forEach((inputType, idx) => {
      for (const key in inputType ?? {}) {
        const name2 = `${widget.name}.${key}`;
        const specToAdd = transformInputSpecV1ToV2(inputType[key], {
          name: name2,
          isOptional: idx !== 0
        });
        specToAdd.display_name = key;
        addNodeInput(node2, specToAdd);
        const newInputs = node2.inputs.slice(startingInputLength).filter((inp) => inp.name.startsWith(name2));
        for (const newInput of newInputs) {
        }
      }
    });
    const inputInsertionPoint = node2.inputs.findIndex((i) => i.name === widget.name) + 1;
    const addedWidgets = node2.widgets.splice(startingLength);
    node2.widgets.splice(insertionPoint, 0, ...addedWidgets);
    if (inputInsertionPoint === 0) {
      if (addedWidgets.length === 0 && node2.inputs.length !== startingInputLength)
        throw new Error("Failed to find input socket for " + widget.name);
      return;
    }
    const addedInputs = spliceInputs(node2, startingInputLength).map(
      (addedInput) => {
        const existingInput = node2.inputs.findIndex(
          (existingInput2) => addedInput.name === existingInput2.name
        );
        return existingInput === -1 ? addedInput : spliceInputs(node2, existingInput, 1)[0];
      }
    );
    spliceInputs(node2, inputInsertionPoint, 0, ...addedInputs);
    for (const input of removedInputs) {
      const inputIndex = node2.inputs.findIndex((inp) => inp.name === input.name);
      if (inputIndex === -1) {
        node2.inputs.push(input);
        node2.removeInput(node2.inputs.length - 1);
      } else {
        node2.inputs[inputIndex].link = input.link;
        if (!input.link) continue;
        const link = node2.graph?.links?.[input.link];
        if (!link) continue;
        link.target_slot = inputIndex;
        node2.onConnectionsChange?.(
          LiteGraph.INPUT,
          inputIndex,
          true,
          link,
          node2.inputs[inputIndex]
        );
      }
    }
    node2.size[1] = node2.computeSize([...node2.size])[1];
    if (!node2.graph) return;
    node2._setConcreteSlots();
    node2.arrange();
    app$1.canvas?.setDirty(true, true);
  }, "updateWidgets");
  let widgetValue = widget.value;
  Object.defineProperty(widget, "value", {
    get() {
      return widgetValue;
    },
    set(value) {
      widgetValue = value;
      updateWidgets(value);
    }
  });
  widget.value = widgetValue;
  return { widget, minWidth, minHeight };
}
__name(dynamicComboWidget, "dynamicComboWidget");
const dynamicWidgets = { COMFY_DYNAMICCOMBO_V3: dynamicComboWidget };
const dynamicInputs = {
  COMFY_AUTOGROW_V3: applyAutogrow,
  COMFY_MATCHTYPE_V3: applyMatchType
};
function applyDynamicInputs(node2, inputSpec) {
  if (!(inputSpec.type in dynamicInputs)) return false;
  dynamicInputs[inputSpec.type](node2, inputSpec);
  return true;
}
__name(applyDynamicInputs, "applyDynamicInputs");
function spliceInputs(node2, startIndex, deleteCount = -1, ...toAdd) {
  if (deleteCount < 0) return node2.inputs.splice(startIndex);
  const ret = node2.inputs.splice(startIndex, deleteCount, ...toAdd);
  node2.inputs.slice(startIndex).forEach((input, index2) => {
    const link = input.link && node2.graph?.links?.get(input.link);
    if (link) link.target_slot = startIndex + index2;
  });
  return ret;
}
__name(spliceInputs, "spliceInputs");
function changeOutputType(node2, output, combinedType) {
  if (output.type === combinedType) return;
  output.type = combinedType;
  if (!node2.graph) return;
  for (const link_id of output.links ?? []) {
    const link = node2.graph.links[link_id];
    if (!link) continue;
    const { input, inputNode, subgraphOutput } = link.resolve(node2.graph);
    const inputType = (input ?? subgraphOutput)?.type;
    if (!inputType) continue;
    const keep = LiteGraph.isValidConnection(combinedType, inputType);
    if (!keep && subgraphOutput) subgraphOutput.disconnect();
    else if (!keep && inputNode) inputNode.disconnectInput(link.target_slot);
    if (input && inputNode?.onConnectionsChange)
      inputNode.onConnectionsChange(
        LiteGraph.INPUT,
        link.target_slot,
        keep,
        link,
        input
      );
  }
}
__name(changeOutputType, "changeOutputType");
function withComfyMatchType(node2) {
  if (node2.comfyDynamic?.matchType) return;
  node2.comfyDynamic ??= {};
  node2.comfyDynamic.matchType = {};
  const outputGroups = node2.constructor.nodeData?.output_matchtypes;
  node2.onConnectionsChange = useChainCallback(
    node2.onConnectionsChange,
    function(contype, slot, iscon, linf) {
      const input = this.inputs[slot];
      if (contype !== LiteGraph.INPUT || !this.graph || !input) return;
      const [matchKey, matchGroup] = Object.entries(
        this.comfyDynamic.matchType
      ).find(([, group]) => input.name in group) ?? ["", void 0];
      if (!matchGroup) return;
      if (iscon && linf) {
        const { output, subgraphInput } = linf.resolve(this.graph);
        const connectingType = (output ?? subgraphInput)?.type;
        if (connectingType) linf.type = connectingType;
      }
      const groupInputs = node2.inputs.filter(
        (inp) => inp.name in matchGroup
      );
      const connectedTypes = groupInputs.map((inp) => {
        if (!inp.link) return "*";
        const link = this.graph.links[inp.link];
        if (!link) return "*";
        const { output, subgraphInput } = link.resolve(this.graph);
        return (output ?? subgraphInput)?.type ?? "*";
      });
      groupInputs.forEach((input2, idx) => {
        const otherConnected = [
          ...connectedTypes.slice(0, idx),
          ...connectedTypes.slice(idx + 1)
        ];
        const combinedType = commonType(
          ...otherConnected,
          matchGroup[input2.name]
        );
        if (!combinedType) throw new Error("invalid connection");
        input2.type = combinedType;
      });
      const outputType = commonType(...connectedTypes);
      if (!outputType) throw new Error("invalid connection");
      this.outputs.forEach((output, idx) => {
        if (!(outputGroups?.[idx] == matchKey)) return;
        changeOutputType(this, output, outputType);
      });
      app$1.canvas?.setDirty(true, true);
    }
  );
}
__name(withComfyMatchType, "withComfyMatchType");
function applyMatchType(node2, inputSpec) {
  const { addNodeInput } = useLitegraphService();
  const name2 = inputSpec.name;
  const { allowed_types, template_id } = inputSpec.template;
  const typedSpec = { ...inputSpec, type: allowed_types };
  addNodeInput(node2, typedSpec);
  withComfyMatchType(node2);
  node2.comfyDynamic.matchType[template_id] ??= {};
  node2.comfyDynamic.matchType[template_id][name2] = allowed_types;
  const index2 = node2.inputs.length - 1;
  requestAnimationFrame(() => {
    const input = node2.inputs.at(index2);
    node2.onConnectionsChange?.(
      LiteGraph.INPUT,
      index2,
      !!input.link,
      input.link ? node2.graph?.links?.[input.link] : void 0,
      input
    );
  });
}
__name(applyMatchType, "applyMatchType");
function autogrowOrdinalToName(ordinal, key, groupName, node2) {
  const {
    names,
    prefix = "",
    inputSpecs
  } = node2.comfyDynamic.autogrow[groupName];
  const baseName = names ? names[ordinal] : (inputSpecs.length == 1 ? prefix : key) + ordinal;
  return { name: `${groupName}.${baseName}`, display_name: baseName };
}
__name(autogrowOrdinalToName, "autogrowOrdinalToName");
function addAutogrowGroup(ordinal, groupName, node2) {
  const { addNodeInput } = useLitegraphService();
  const { max, min, inputSpecs } = node2.comfyDynamic.autogrow[groupName];
  if (ordinal >= max) return;
  const namedSpecs = inputSpecs.map((input) => ({
    ...input,
    isOptional: ordinal >= (min ?? 0) || input.isOptional,
    ...autogrowOrdinalToName(ordinal, input.name, groupName, node2)
  }));
  const newInputs = namedSpecs.filter(
    (namedSpec) => !node2.inputs.some((inp) => inp.name === namedSpec.name)
  ).map((namedSpec) => {
    addNodeInput(node2, namedSpec);
    const input = spliceInputs(node2, node2.inputs.length - 1, 1)[0];
    if (inputSpecs.length !== 1 || INLINE_INPUTS)
      ensureWidgetForInput(node2, input);
    return input;
  });
  const lastIndex = node2.inputs.findLastIndex(
    (inp) => inp.name.startsWith(groupName)
  );
  const insertionIndex = lastIndex === -1 ? node2.inputs.length : lastIndex + 1;
  spliceInputs(node2, insertionIndex, 0, ...newInputs);
  app$1.canvas?.setDirty(true, true);
}
__name(addAutogrowGroup, "addAutogrowGroup");
const ORDINAL_REGEX = /\d+$/;
function resolveAutogrowOrdinal(inputName, groupName, node2) {
  const name2 = inputName.slice(groupName.length + 1);
  const { names } = node2.comfyDynamic.autogrow[groupName];
  if (names) {
    const ordinal2 = names.findIndex((s) => s === name2);
    return ordinal2 === -1 ? void 0 : ordinal2;
  }
  const match = name2.match(ORDINAL_REGEX);
  if (!match) return void 0;
  const ordinal = parseInt(match[0]);
  return ordinal !== ordinal ? void 0 : ordinal;
}
__name(resolveAutogrowOrdinal, "resolveAutogrowOrdinal");
function autogrowInputConnected(index2, node2) {
  const input = node2.inputs[index2];
  const groupName = input.name.slice(0, input.name.lastIndexOf("."));
  const lastInput = node2.inputs.findLast(
    (inp) => inp.name.startsWith(groupName)
  );
  const ordinal = resolveAutogrowOrdinal(input.name, groupName, node2);
  if (!lastInput || ordinal == void 0 || ordinal !== resolveAutogrowOrdinal(lastInput.name, groupName, node2))
    return;
  addAutogrowGroup(ordinal + 1, groupName, node2);
}
__name(autogrowInputConnected, "autogrowInputConnected");
function autogrowInputDisconnected(index2, node2) {
  const input = node2.inputs[index2];
  if (!input) return;
  const groupName = input.name.slice(0, input.name.lastIndexOf("."));
  const { min = 1, inputSpecs } = node2.comfyDynamic.autogrow[groupName];
  const ordinal = resolveAutogrowOrdinal(input.name, groupName, node2);
  if (ordinal == void 0 || ordinal + 1 < min) return;
  const groupInputs = node2.inputs.filter(
    (inp) => inp.name.startsWith(groupName + ".") && inp.name.lastIndexOf(".") === groupName.length
  );
  const stride = inputSpecs.length;
  if (groupInputs.length % stride !== 0) {
    console.error("Failed to group multi-input autogrow inputs");
    return;
  }
  app$1.canvas?.setDirty(true, true);
  for (let column = 0; column < stride; column++) {
    for (let bubbleOrdinal = ordinal * stride + column; bubbleOrdinal + stride < groupInputs.length; bubbleOrdinal += stride) {
      const curInput = groupInputs[bubbleOrdinal];
      curInput.link = groupInputs[bubbleOrdinal + stride].link;
      if (!curInput.link) continue;
      const link = node2.graph?.links[curInput.link];
      if (!link) continue;
      const curIndex = node2.inputs.findIndex((inp) => inp === curInput);
      if (curIndex === -1) throw new Error("missing input");
      link.target_slot = curIndex;
    }
    const lastInput = groupInputs.at(column - stride);
    if (!lastInput) continue;
    lastInput.link = null;
  }
  const removalChecks = groupInputs.slice((min - 1) * stride);
  let i;
  for (i = removalChecks.length - stride; i >= 0; i -= stride) {
    if (removalChecks.slice(i, i + stride).some((inp) => inp.link)) break;
  }
  const toRemove = removalChecks.slice(i + stride * 2);
  remove(node2.inputs, (inp) => toRemove.includes(inp));
  for (const input2 of toRemove) {
    const widgetName = input2?.widget?.name;
    if (!widgetName) continue;
    for (const widget of remove(node2.widgets, (w) => w.name === widgetName))
      widget.onRemove?.();
  }
  node2.size[1] = node2.computeSize([...node2.size])[1];
}
__name(autogrowInputDisconnected, "autogrowInputDisconnected");
function withComfyAutogrow(node2) {
  if (node2.comfyDynamic?.autogrow) return;
  node2.comfyDynamic ??= {};
  node2.comfyDynamic.autogrow = {};
  let pendingConnection;
  let swappingConnection = false;
  const originalOnConnectInput = node2.onConnectInput;
  node2.onConnectInput = function(slot, ...args) {
    pendingConnection = slot;
    requestAnimationFrame(() => pendingConnection = void 0);
    return originalOnConnectInput?.apply(this, [slot, ...args]) ?? true;
  };
  node2.onConnectionsChange = useChainCallback(
    node2.onConnectionsChange,
    function(contype, slot, iscon, linf) {
      const input = this.inputs[slot];
      if (contype !== LiteGraph.INPUT || !input) return;
      const key = input.name.slice(0, input.name.lastIndexOf("."));
      const autogrowGroup = this.comfyDynamic.autogrow[key];
      if (!autogrowGroup) return;
      if (app$1.configuringGraph && input.widget)
        ensureWidgetForInput(node2, input);
      if (iscon && linf) {
        if (swappingConnection || !linf) return;
        autogrowInputConnected(slot, this);
      } else {
        if (pendingConnection === slot) {
          swappingConnection = true;
          requestAnimationFrame(() => swappingConnection = false);
          return;
        }
        requestAnimationFrame(() => autogrowInputDisconnected(slot, this));
      }
    }
  );
}
__name(withComfyAutogrow, "withComfyAutogrow");
function applyAutogrow(node2, inputSpecV2) {
  withComfyAutogrow(node2);
  const parseResult = zAutogrowOptions.safeParse(inputSpecV2);
  if (!parseResult.success) throw new Error("invalid Autogrow spec");
  const inputSpec = parseResult.data;
  const { input, min = 1, names, prefix, max = 100 } = inputSpec.template;
  const inputTypes = [
    input.required,
    input.optional
  ];
  const inputsV2 = inputTypes.flatMap(
    (inputType, index2) => Object.entries(inputType ?? {}).map(
      ([name2, v2]) => transformInputSpecV1ToV2(v2, { name: name2, isOptional: index2 === 1 })
    )
  );
  node2.comfyDynamic.autogrow[inputSpecV2.name] = {
    names,
    min,
    max: names?.length ?? max,
    prefix,
    inputSpecs: inputsV2
  };
  for (let i = 0; i < min; i++) addAutogrowGroup(i, inputSpecV2.name, node2);
}
__name(applyAutogrow, "applyAutogrow");
const zIntInputSpec = zIntInputOptions.extend({
  type: z.literal("INT"),
  name: z.string(),
  isOptional: z.boolean().optional()
});
const zFloatInputSpec = zFloatInputOptions.extend({
  type: z.literal("FLOAT"),
  name: z.string(),
  isOptional: z.boolean().optional()
});
const zBooleanInputSpec = zBooleanInputOptions.extend({
  type: z.literal("BOOLEAN"),
  name: z.string(),
  isOptional: z.boolean().optional()
});
const zStringInputSpec = zStringInputOptions.extend({
  type: z.literal("STRING"),
  name: z.string(),
  isOptional: z.boolean().optional()
});
const zComboInputSpec = zComboInputOptions.extend({
  type: z.literal("COMBO"),
  name: z.string(),
  isOptional: z.boolean().optional()
});
const zColorInputSpec = zBaseInputOptions.extend({
  type: z.literal("COLOR"),
  name: z.string(),
  isOptional: z.boolean().optional(),
  options: z.object({
    default: z.string().optional()
  }).optional()
});
const zImageInputSpec = zBaseInputOptions.extend({
  type: z.literal("IMAGE"),
  name: z.string(),
  isOptional: z.boolean().optional(),
  options: z.record(z.unknown()).optional()
});
const zImageCompareInputSpec = zBaseInputOptions.extend({
  type: z.literal("IMAGECOMPARE"),
  name: z.string(),
  isOptional: z.boolean().optional(),
  options: z.record(z.unknown()).optional()
});
const zMarkdownInputSpec = zBaseInputOptions.extend({
  type: z.literal("MARKDOWN"),
  name: z.string(),
  isOptional: z.boolean().optional(),
  options: z.object({
    content: z.string().optional()
  }).optional()
});
const zChartInputSpec = zBaseInputOptions.extend({
  type: z.literal("CHART"),
  name: z.string(),
  isOptional: z.boolean().optional(),
  options: z.object({
    type: z.enum(["bar", "line"]).optional(),
    data: z.object({}).optional()
  }).optional()
});
const zGalleriaInputSpec = zBaseInputOptions.extend({
  type: z.literal("GALLERIA"),
  name: z.string(),
  isOptional: z.boolean().optional(),
  options: z.object({
    images: z.array(z.string()).optional()
  }).optional()
});
const zTextareaInputSpec = zBaseInputOptions.extend({
  type: z.literal("TEXTAREA"),
  name: z.string(),
  isOptional: z.boolean().optional(),
  options: z.object({
    rows: z.number().optional(),
    cols: z.number().optional(),
    default: z.string().optional()
  }).optional()
});
const zCustomInputSpec = zBaseInputOptions.extend({
  type: z.string(),
  name: z.string(),
  isOptional: z.boolean().optional()
});
const zInputSpec = z.union([
  zIntInputSpec,
  zFloatInputSpec,
  zBooleanInputSpec,
  zStringInputSpec,
  zComboInputSpec,
  zColorInputSpec,
  zImageInputSpec,
  zImageCompareInputSpec,
  zMarkdownInputSpec,
  zChartInputSpec,
  zGalleriaInputSpec,
  zTextareaInputSpec,
  zCustomInputSpec
]);
const zOutputSpec = z.object({
  index: z.number(),
  name: z.string(),
  type: z.string(),
  is_list: z.boolean(),
  options: z.array(z.any()).optional(),
  tooltip: z.string().optional()
});
z.object({
  inputs: z.record(zInputSpec),
  outputs: z.array(zOutputSpec),
  hidden: z.record(z.any()).optional(),
  name: z.string(),
  display_name: z.string(),
  description: z.string(),
  help: z.string().optional(),
  category: z.string(),
  output_node: z.boolean(),
  python_module: z.string(),
  deprecated: z.boolean().optional(),
  experimental: z.boolean().optional(),
  api_node: z.boolean().optional()
});
const isIntInputSpec = /* @__PURE__ */ __name((inputSpec) => {
  return inputSpec.type === "INT";
}, "isIntInputSpec");
const isFloatInputSpec = /* @__PURE__ */ __name((inputSpec) => {
  return inputSpec.type === "FLOAT";
}, "isFloatInputSpec");
const isBooleanInputSpec = /* @__PURE__ */ __name((inputSpec) => {
  return inputSpec.type === "BOOLEAN";
}, "isBooleanInputSpec");
const isStringInputSpec = /* @__PURE__ */ __name((inputSpec) => {
  return inputSpec.type === "STRING";
}, "isStringInputSpec");
const isComboInputSpec = /* @__PURE__ */ __name((inputSpec) => {
  return inputSpec.type === "COMBO";
}, "isComboInputSpec");
const isChartInputSpec = /* @__PURE__ */ __name((inputSpec) => {
  return inputSpec.type === "CHART";
}, "isChartInputSpec");
const useBooleanWidget = /* @__PURE__ */ __name(() => {
  const widgetConstructor = /* @__PURE__ */ __name((node2, inputSpec) => {
    if (!isBooleanInputSpec(inputSpec)) {
      throw new Error(`Invalid input data: ${inputSpec}`);
    }
    const defaultVal = inputSpec.default ?? false;
    const options = {
      on: inputSpec.label_on,
      off: inputSpec.label_off
    };
    return node2.addWidget(
      "toggle",
      inputSpec.name,
      defaultVal,
      () => {
      },
      options
    );
  }, "widgetConstructor");
  return widgetConstructor;
}, "useBooleanWidget");
const useChartWidget = /* @__PURE__ */ __name(() => {
  return (node2, inputSpec) => {
    if (!isChartInputSpec(inputSpec)) {
      throw new Error("Invalid input spec for chart widget");
    }
    const { name: name2, options = {} } = inputSpec;
    const chartType = options.type || "line";
    const widget = node2.addWidget("chart", name2, options.data || {}, () => {
    }, {
      serialize: true,
      type: chartType,
      ...options
    });
    return widget;
  };
}, "useChartWidget");
const useColorWidget = /* @__PURE__ */ __name(() => {
  return (node2, inputSpec) => {
    const { name: name2, options } = inputSpec;
    const defaultValue = options?.default || "#000000";
    const widget = node2.addWidget("color", name2, defaultValue, () => {
    }, {
      serialize: true
    });
    return widget;
  };
}, "useColorWidget");
const _sfc_main$2w = /* @__PURE__ */ defineComponent({
  __name: "MultiSelectWidget",
  props: /* @__PURE__ */ mergeModels({
    widget: {}
  }, {
    "modelValue": { required: true },
    "modelModifiers": {}
  }),
  emits: ["update:modelValue"],
  setup(__props) {
    const selectedItems = useModel(__props, "modelValue");
    const inputSpec = __props.widget.inputSpec;
    const options = inputSpec.options ?? [];
    const placeholder = inputSpec.multi_select?.placeholder ?? "Select items";
    const display = inputSpec.multi_select?.chip ? "chip" : "comma";
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", null, [
        createVNode(unref(script$2), {
          modelValue: selectedItems.value,
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedItems.value = $event),
          options: unref(options),
          filter: "",
          placeholder: unref(placeholder),
          "max-selected-labels": 3,
          display: unref(display),
          class: "w-full",
          pt: {
            dropdownIcon: "text-button-icon"
          }
        }, null, 8, ["modelValue", "options", "placeholder", "display"])
      ]);
    };
  }
});
const _sfc_main$2v = /* @__PURE__ */ defineComponent({
  __name: "SearchFilterChip",
  props: {
    text: {},
    badge: {},
    badgeClass: {}
  },
  emits: ["remove"],
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$3), {
        removable: "",
        onRemove: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("remove", $event))
      }, {
        default: withCtx(() => [
          createVNode(unref(script$4), {
            size: "small",
            class: normalizeClass(_ctx.badgeClass)
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(_ctx.badge), 1)
            ]),
            _: 1
          }, 8, ["class"]),
          createTextVNode(" " + toDisplayString(_ctx.text), 1)
        ]),
        _: 1
      });
    };
  }
});
const _export_sfc = /* @__PURE__ */ __name((sfc, props) => {
  const target = sfc.__vccOpts || sfc;
  for (const [key, val] of props) {
    target[key] = val;
  }
  return target;
}, "_export_sfc");
const SearchFilterChip = /* @__PURE__ */ _export_sfc(_sfc_main$2v, [["__scopeId", "data-v-1adce2e7"]]);
const _hoisted_1$22 = {
  key: 0,
  class: "search-filters flex flex-wrap gap-2 pt-2"
};
const _sfc_main$2u = /* @__PURE__ */ defineComponent({
  __name: "SearchBox",
  props: /* @__PURE__ */ mergeModels({
    placeholder: { default: "Search..." },
    icon: { default: "pi pi-search" },
    debounceTime: { default: 300 },
    filterIcon: {},
    filters: { default: /* @__PURE__ */ __name(() => [], "default") },
    autofocus: { type: Boolean, default: false },
    showBorder: { type: Boolean, default: false },
    size: { default: "md" },
    class: {}
  }, {
    "modelValue": { required: true },
    "modelModifiers": {}
  }),
  emits: /* @__PURE__ */ mergeModels(["search", "showFilter", "removeFilter"], ["update:modelValue"]),
  setup(__props, { expose: __expose, emit: __emit }) {
    const emit = __emit;
    const modelValue = useModel(__props, "modelValue");
    const inputRef = ref();
    __expose({
      focus: /* @__PURE__ */ __name(() => {
        inputRef.value?.$el?.focus();
      }, "focus")
    });
    watchDebounced(
      modelValue,
      (value) => {
        emit("search", value, __props.filters);
      },
      { debounce: __props.debounceTime }
    );
    const wrapperStyle = computed(() => {
      if (__props.showBorder) {
        return cn("rounded p-2 border border-solid border-border-default");
      }
      const sizeClasses = {
        md: "h-8 px-2 py-1.5",
        // Matches button sm size
        lg: "h-10 px-4 py-2"
        // Matches button md size
      }[__props.size];
      return cn("rounded-lg", sizeClasses);
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock(Fragment, null, [
        createBaseVNode("div", {
          class: normalizeClass(
            unref(cn)(
              "relative flex w-full items-center gap-2 bg-comfy-input cursor-text text-comfy-input-foreground",
              __props.class,
              wrapperStyle.value
            )
          )
        }, [
          createVNode(unref(script$5), {
            ref_key: "inputRef",
            ref: inputRef,
            modelValue: modelValue.value,
            "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => modelValue.value = $event),
            placeholder: _ctx.placeholder,
            autofocus: _ctx.autofocus,
            unstyled: "",
            class: "absolute inset-0 size-full pl-11 border-none outline-none bg-transparent text-sm",
            "aria-label": _ctx.placeholder
          }, null, 8, ["modelValue", "placeholder", "autofocus", "aria-label"]),
          _ctx.filterIcon ? (openBlock(), createBlock(_sfc_main$2A, {
            key: 0,
            size: "icon",
            variant: "textonly",
            class: "filter-button absolute right-0 inset-y-0 m-0 p-0",
            onClick: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("showFilter", $event))
          }, {
            default: withCtx(() => [
              createBaseVNode("i", {
                class: normalizeClass(_ctx.filterIcon)
              }, null, 2)
            ]),
            _: 1
          })) : createCommentVNode("", true),
          !modelValue.value ? (openBlock(), createBlock(unref(script$6), {
            key: 1,
            class: normalizeClass(_ctx.icon)
          }, null, 8, ["class"])) : createCommentVNode("", true),
          modelValue.value ? (openBlock(), createBlock(_sfc_main$2A, {
            key: 2,
            class: "clear-button absolute left-0",
            variant: "textonly",
            size: "icon",
            onClick: _cache[2] || (_cache[2] = ($event) => modelValue.value = "")
          }, {
            default: withCtx(() => _cache[3] || (_cache[3] = [
              createBaseVNode("i", { class: "icon-[lucide--x] size-4" }, null, -1)
            ])),
            _: 1
          })) : createCommentVNode("", true)
        ], 2),
        _ctx.filters?.length ? (openBlock(), createElementBlock("div", _hoisted_1$22, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.filters, (filter2) => {
            return openBlock(), createBlock(SearchFilterChip, {
              key: filter2.id,
              text: filter2.text,
              badge: filter2.badge,
              "badge-class": filter2.badgeClass,
              onRemove: /* @__PURE__ */ __name(($event) => _ctx.$emit("removeFilter", filter2), "onRemove")
            }, null, 8, ["text", "badge", "badge-class", "onRemove"]);
          }), 128))
        ])) : createCommentVNode("", true)
      ], 64);
    };
  }
});
const SearchBox = /* @__PURE__ */ _export_sfc(_sfc_main$2u, [["__scopeId", "data-v-0c8505a3"]]);
const _sfc_main$2t = /* @__PURE__ */ defineComponent({
  __name: "IconGroup",
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(
          unref(cn)(
            "flex justify-center items-center shrink-0 outline-hidden border-none p-0 rounded-lg bg-secondary-background shadow-sm transition-all duration-200 cursor-pointer"
          )
        )
      }, [
        renderSlot(_ctx.$slots, "default")
      ], 2);
    };
  }
});
const _hoisted_1$21 = { class: "editable-text" };
const _hoisted_2$1A = { key: 0 };
const _sfc_main$2s = /* @__PURE__ */ defineComponent({
  __name: "EditableText",
  props: {
    modelValue: {},
    isEditing: { type: Boolean, default: false },
    inputAttrs: { default: /* @__PURE__ */ __name(() => ({}), "default") }
  },
  emits: ["edit", "cancel"],
  setup(__props, { emit: __emit }) {
    const emit = __emit;
    const inputValue = ref(__props.modelValue);
    const inputRef = ref();
    const isCanceling = ref(false);
    const blurInputElement = /* @__PURE__ */ __name(() => {
      inputRef.value?.$el.blur();
    }, "blurInputElement");
    const finishEditing = /* @__PURE__ */ __name(() => {
      if (!isCanceling.value) {
        emit("edit", inputValue.value);
      }
      isCanceling.value = false;
    }, "finishEditing");
    const cancelEditing = /* @__PURE__ */ __name(() => {
      isCanceling.value = true;
      inputValue.value = __props.modelValue;
      emit("cancel");
      blurInputElement();
    }, "cancelEditing");
    watch(
      () => __props.isEditing,
      async (newVal) => {
        if (newVal) {
          inputValue.value = __props.modelValue;
          await nextTick(() => {
            if (!inputRef.value) return;
            const fileName = inputValue.value.includes(".") ? inputValue.value.split(".").slice(0, -1).join(".") : inputValue.value;
            const start = 0;
            const end = fileName.length;
            const inputElement = inputRef.value.$el;
            inputElement.setSelectionRange?.(start, end);
          });
        }
      },
      { immediate: true }
    );
    const vFocus = {
      mounted: /* @__PURE__ */ __name((el) => el.focus(), "mounted")
    };
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$21, [
        !_ctx.isEditing ? (openBlock(), createElementBlock("span", _hoisted_2$1A, toDisplayString(_ctx.modelValue), 1)) : withDirectives((openBlock(), createBlock(unref(script$5), {
          key: 1,
          ref_key: "inputRef",
          ref: inputRef,
          "model-value": inputValue.value,
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => inputValue.value = $event),
          type: "text",
          size: "small",
          fluid: "",
          pt: {
            root: {
              onBlur: finishEditing,
              ..._ctx.inputAttrs
            }
          },
          onKeyupCapture: withKeys(withModifiers(blurInputElement, ["stop"]), ["enter"]),
          onKeyup: withKeys(withModifiers(cancelEditing, ["stop"]), ["escape"]),
          onClick: _cache[1] || (_cache[1] = withModifiers(() => {
          }, ["stop"])),
          onContextmenu: _cache[2] || (_cache[2] = withModifiers(() => {
          }, ["stop"])),
          onPointerdownCapture: _cache[3] || (_cache[3] = withModifiers(() => {
          }, ["stop"])),
          onPointermoveCapture: _cache[4] || (_cache[4] = withModifiers(() => {
          }, ["stop"]))
        }, null, 8, ["model-value", "pt", "onKeyupCapture", "onKeyup"])), [
          [vFocus]
        ])
      ]);
    };
  }
});
const EditableText = /* @__PURE__ */ _export_sfc(_sfc_main$2s, [["__scopeId", "data-v-f1af930a"]]);
const useDialogStore = defineStore("dialog", () => {
  const dialogStack = ref([]);
  const activeKey = ref(null);
  const genDialogKey = /* @__PURE__ */ __name(() => `dialog-${Math.random().toString(36).slice(2, 9)}`, "genDialogKey");
  function insertDialogByPriority(dialog) {
    const insertIndex = dialogStack.value.findIndex(
      (d2) => d2.priority <= dialog.priority
    );
    dialogStack.value.splice(
      insertIndex === -1 ? dialogStack.value.length : insertIndex,
      0,
      dialog
    );
  }
  __name(insertDialogByPriority, "insertDialogByPriority");
  function riseDialog(options) {
    const dialogKey = options.key;
    const index2 = dialogStack.value.findIndex((d2) => d2.key === dialogKey);
    if (index2 !== -1) {
      const [dialog] = dialogStack.value.splice(index2, 1);
      insertDialogByPriority(dialog);
      activeKey.value = dialogKey;
      updateCloseOnEscapeStates();
    }
  }
  __name(riseDialog, "riseDialog");
  function closeDialog(options) {
    const targetDialog = options ? dialogStack.value.find((d2) => d2.key === options.key) : dialogStack.value.find((d2) => d2.key === activeKey.value);
    if (!targetDialog) return;
    targetDialog.dialogComponentProps?.onClose?.();
    const index2 = dialogStack.value.indexOf(targetDialog);
    dialogStack.value.splice(index2, 1);
    activeKey.value = dialogStack.value.length > 0 ? dialogStack.value[dialogStack.value.length - 1].key : null;
    updateCloseOnEscapeStates();
  }
  __name(closeDialog, "closeDialog");
  function createDialog(options) {
    if (dialogStack.value.length >= 10) {
      dialogStack.value.shift();
    }
    const dialog = {
      key: options.key,
      visible: true,
      title: options.title,
      headerComponent: options.headerComponent ? markRaw(options.headerComponent) : void 0,
      footerComponent: options.footerComponent ? markRaw(options.footerComponent) : void 0,
      component: markRaw(options.component),
      headerProps: { ...options.headerProps },
      contentProps: { ...options.props },
      footerProps: { ...options.footerProps },
      priority: options.priority ?? 1,
      dialogComponentProps: {
        maximizable: false,
        modal: true,
        closable: true,
        closeOnEscape: true,
        dismissableMask: true,
        ...options.dialogComponentProps,
        maximized: false,
        onMaximize: /* @__PURE__ */ __name(() => {
          dialog.dialogComponentProps.maximized = true;
        }, "onMaximize"),
        onUnmaximize: /* @__PURE__ */ __name(() => {
          dialog.dialogComponentProps.maximized = false;
        }, "onUnmaximize"),
        onAfterHide: /* @__PURE__ */ __name(() => {
          closeDialog(dialog);
        }, "onAfterHide"),
        pt: merge(options.dialogComponentProps?.pt || {}, {
          root: {
            onMousedown: /* @__PURE__ */ __name(() => {
              riseDialog(dialog);
            }, "onMousedown")
          }
        })
      }
    };
    insertDialogByPriority(dialog);
    activeKey.value = options.key;
    updateCloseOnEscapeStates();
    return dialog;
  }
  __name(createDialog, "createDialog");
  function updateCloseOnEscapeStates() {
    const topDialog = dialogStack.value.find((d2) => d2.key === activeKey.value);
    const topClosable = topDialog?.dialogComponentProps.closable;
    dialogStack.value.forEach((dialog) => {
      dialog.dialogComponentProps = {
        ...dialog.dialogComponentProps,
        closeOnEscape: dialog === topDialog && !!topClosable
      };
    });
  }
  __name(updateCloseOnEscapeStates, "updateCloseOnEscapeStates");
  function showDialog(options) {
    const dialogKey = options.key || genDialogKey();
    let dialog = dialogStack.value.find((d2) => d2.key === dialogKey);
    if (dialog) {
      dialog.visible = true;
      riseDialog(dialog);
    } else {
      dialog = createDialog({ ...options, key: dialogKey });
    }
    return dialog;
  }
  __name(showDialog, "showDialog");
  function showExtensionDialog(options) {
    const { key } = options;
    if (!key) {
      console.error("Extension dialog key is required");
      return;
    }
    const extKey = key.startsWith("extension-") ? key : `extension-${key}`;
    const dialog = dialogStack.value.find((d2) => d2.key === extKey);
    if (!dialog) return createDialog({ ...options, key: extKey });
    dialog.visible = true;
    riseDialog(dialog);
    return dialog;
  }
  __name(showExtensionDialog, "showExtensionDialog");
  function isDialogOpen(key) {
    return dialogStack.value.some((d2) => d2.key === key);
  }
  __name(isDialogOpen, "isDialogOpen");
  return {
    dialogStack,
    riseDialog,
    showDialog,
    closeDialog,
    showExtensionDialog,
    isDialogOpen,
    activeKey
  };
});
const zAsset = z.object({
  id: z.string(),
  name: z.string(),
  asset_hash: z.string().nullish(),
  size: z.number().optional(),
  // TBD: Will be provided by history API in the future
  mime_type: z.string().nullish(),
  tags: z.array(z.string()).optional().default([]),
  preview_id: z.string().nullable().optional(),
  preview_url: z.string().optional(),
  created_at: z.string(),
  updated_at: z.string().optional(),
  is_immutable: z.boolean().optional(),
  last_access_time: z.string().optional(),
  user_metadata: z.record(z.unknown()).optional()
  // API allows arbitrary key-value pairs
});
const zAssetResponse = z.object({
  assets: z.array(zAsset).optional(),
  total: z.number().optional(),
  has_more: z.boolean().optional()
});
z.object({
  name: z.string(),
  folders: z.array(z.string())
});
z.object({
  name: z.string(),
  pathIndex: z.number()
});
const zValidationError = z.object({
  code: z.string(),
  message: z.string(),
  field: z.string()
});
const zValidationResult = z.object({
  is_valid: z.boolean(),
  errors: z.array(zValidationError).optional(),
  warnings: z.array(zValidationError).optional()
});
z.object({
  content_length: z.number(),
  final_url: z.string(),
  content_type: z.string().optional(),
  filename: z.string().optional(),
  name: z.string().optional(),
  tags: z.array(z.string()).optional(),
  preview_url: z.string().optional(),
  preview_image: z.string().optional(),
  validation: zValidationResult.optional()
});
z.string().min(1, "Filename cannot be empty").regex(/^[^\\:*?"<>|]+$/, "Invalid filename characters").regex(/^(?!\/|.*\.\.)/, "Path must not start with / or contain ..").trim();
const assetItemSchema = zAsset;
const assetResponseSchema = zAssetResponse;
class ModelNodeProvider {
  static {
    __name(this, "ModelNodeProvider");
  }
  /** The node definition to use for this model. */
  nodeDef;
  /** The node input key for where to inside the model name. */
  key;
  constructor(nodeDef, key) {
    this.nodeDef = nodeDef;
    this.key = key;
  }
}
const useModelToNodeStore = defineStore("modelToNode", () => {
  const modelToNodeMap = ref({});
  const nodeDefStore = useNodeDefStore();
  const haveDefaultsLoaded = ref(false);
  const registeredNodeTypes = computed(() => {
    return Object.fromEntries(
      Object.values(modelToNodeMap.value).flat().filter((provider) => !!provider.nodeDef).map((provider) => [provider.nodeDef.name, provider.key])
    );
  });
  const nodeTypeToCategory = computed(() => {
    const lookup = {};
    for (const [category, providers] of Object.entries(modelToNodeMap.value)) {
      for (const provider of providers) {
        if (!provider.nodeDef) continue;
        if (!lookup[provider.nodeDef.name]) {
          lookup[provider.nodeDef.name] = category;
        }
      }
    }
    return lookup;
  });
  function getRegisteredNodeTypes() {
    registerDefaults();
    return registeredNodeTypes.value;
  }
  __name(getRegisteredNodeTypes, "getRegisteredNodeTypes");
  function getCategoryForNodeType(nodeType) {
    registerDefaults();
    if (!nodeType || typeof nodeType !== "string") {
      return void 0;
    }
    return nodeTypeToCategory.value[nodeType];
  }
  __name(getCategoryForNodeType, "getCategoryForNodeType");
  function getNodeProvider(modelType) {
    registerDefaults();
    return modelToNodeMap.value[modelType]?.[0];
  }
  __name(getNodeProvider, "getNodeProvider");
  function getAllNodeProviders(modelType) {
    registerDefaults();
    return modelToNodeMap.value[modelType] ?? [];
  }
  __name(getAllNodeProviders, "getAllNodeProviders");
  function registerNodeProvider(modelType, nodeProvider) {
    registerDefaults();
    if (!nodeProvider.nodeDef) return;
    if (!modelToNodeMap.value[modelType]) {
      modelToNodeMap.value[modelType] = [];
    }
    modelToNodeMap.value[modelType].push(nodeProvider);
  }
  __name(registerNodeProvider, "registerNodeProvider");
  function quickRegister(modelType, nodeClass, key) {
    registerNodeProvider(
      modelType,
      new ModelNodeProvider(nodeDefStore.nodeDefsByName[nodeClass], key)
    );
  }
  __name(quickRegister, "quickRegister");
  function registerDefaults() {
    if (haveDefaultsLoaded.value) {
      return;
    }
    if (Object.keys(nodeDefStore.nodeDefsByName).length === 0) {
      return;
    }
    haveDefaultsLoaded.value = true;
    quickRegister("checkpoints", "CheckpointLoaderSimple", "ckpt_name");
    quickRegister("checkpoints", "ImageOnlyCheckpointLoader", "ckpt_name");
    quickRegister("loras", "LoraLoader", "lora_name");
    quickRegister("loras", "LoraLoaderModelOnly", "lora_name");
    quickRegister("vae", "VAELoader", "vae_name");
    quickRegister("controlnet", "ControlNetLoader", "control_net_name");
    quickRegister("diffusion_models", "UNETLoader", "unet_name");
    quickRegister("upscale_models", "UpscaleModelLoader", "model_name");
    quickRegister("style_models", "StyleModelLoader", "style_model_name");
    quickRegister("gligen", "GLIGENLoader", "gligen_name");
    quickRegister("clip_vision", "CLIPVisionLoader", "clip_name");
    quickRegister("text_encoders", "CLIPLoader", "clip_name");
    quickRegister("audio_encoders", "AudioEncoderLoader", "audio_encoder_name");
    quickRegister("model_patches", "ModelPatchLoader", "name");
    quickRegister(
      "animatediff_models",
      "ADE_LoadAnimateDiffModel",
      "model_name"
    );
    quickRegister(
      "animatediff_motion_lora",
      "ADE_AnimateDiffLoRALoader",
      "name"
    );
  }
  __name(registerDefaults, "registerDefaults");
  return {
    modelToNodeMap,
    getRegisteredNodeTypes,
    getCategoryForNodeType,
    getNodeProvider,
    getAllNodeProviders,
    registerNodeProvider,
    quickRegister,
    registerDefaults
  };
});
function getLocalizedErrorMessage(errorCode) {
  const errorMessages = {
    FILE_TOO_LARGE: st("assetBrowser.errorFileTooLarge", "File too large"),
    FORMAT_NOT_ALLOWED: st(
      "assetBrowser.errorFormatNotAllowed",
      "Format not allowed"
    ),
    UNSAFE_PICKLE_SCAN: st(
      "assetBrowser.errorUnsafePickleScan",
      "Unsafe pickle scan"
    ),
    UNSAFE_VIRUS_SCAN: st(
      "assetBrowser.errorUnsafeVirusScan",
      "Unsafe virus scan"
    ),
    MODEL_TYPE_NOT_SUPPORTED: st(
      "assetBrowser.errorModelTypeNotSupported",
      "Model type not supported"
    )
  };
  return errorMessages[errorCode] || st("assetBrowser.errorUnknown", "Unknown error") || "Unknown error";
}
__name(getLocalizedErrorMessage, "getLocalizedErrorMessage");
const ASSETS_ENDPOINT = "/assets";
const EXPERIMENTAL_WARNING = `EXPERIMENTAL: If you are seeing this please make sure "Comfy.Assets.UseAssetAPI" is set to "false" in your ComfyUI Settings.
`;
const DEFAULT_LIMIT = 500;
const MODELS_TAG = "models";
const MISSING_TAG = "missing";
function validateAssetResponse(data) {
  const result = assetResponseSchema.safeParse(data);
  if (result.success) return result.data;
  const error = fromZodError(result.error);
  throw new Error(
    `${EXPERIMENTAL_WARNING}Invalid asset response against zod schema:
${error}`
  );
}
__name(validateAssetResponse, "validateAssetResponse");
function createAssetService() {
  async function handleAssetRequest(url, context) {
    const res = await api.fetchApi(url);
    if (!res.ok) {
      throw new Error(
        `${EXPERIMENTAL_WARNING}Unable to load ${context}: Server returned ${res.status}. Please try again.`
      );
    }
    const data = await res.json();
    return validateAssetResponse(data);
  }
  __name(handleAssetRequest, "handleAssetRequest");
  async function getAssetModelFolders() {
    const data = await handleAssetRequest(
      `${ASSETS_ENDPOINT}?include_tags=${MODELS_TAG}&limit=${DEFAULT_LIMIT}`,
      "model folders"
    );
    const blacklistedDirectories = /* @__PURE__ */ new Set(["configs"]);
    const discoveredFolders = new Set(
      data?.assets?.filter((asset) => !asset.tags.includes(MISSING_TAG))?.flatMap((asset) => asset.tags)?.filter(
        (tag) => tag !== MODELS_TAG && !blacklistedDirectories.has(tag)
      ) ?? []
    );
    const sortedFolders = Array.from(discoveredFolders).toSorted();
    return sortedFolders.map((name2) => ({ name: name2, folders: [] }));
  }
  __name(getAssetModelFolders, "getAssetModelFolders");
  async function getAssetModels(folder) {
    const data = await handleAssetRequest(
      `${ASSETS_ENDPOINT}?include_tags=${MODELS_TAG},${folder}&limit=${DEFAULT_LIMIT}`,
      `models for ${folder}`
    );
    return data?.assets?.filter(
      (asset) => !asset.tags.includes(MISSING_TAG) && asset.tags.includes(folder)
    )?.map((asset) => ({
      name: asset.name,
      pathIndex: 0
    })) ?? [];
  }
  __name(getAssetModels, "getAssetModels");
  function isAssetBrowserEligible(nodeType, widgetName) {
    if (!nodeType || !widgetName) return false;
    return useModelToNodeStore().getRegisteredNodeTypes()[nodeType] === widgetName;
  }
  __name(isAssetBrowserEligible, "isAssetBrowserEligible");
  async function getAssetsForNodeType(nodeType) {
    if (!nodeType || typeof nodeType !== "string") {
      return [];
    }
    const modelToNodeStore = useModelToNodeStore();
    const category = modelToNodeStore.getCategoryForNodeType(nodeType);
    if (!category) {
      return [];
    }
    const data = await handleAssetRequest(
      `${ASSETS_ENDPOINT}?include_tags=${MODELS_TAG},${category}&limit=${DEFAULT_LIMIT}`,
      `assets for ${nodeType}`
    );
    return data?.assets?.filter(
      (asset) => !asset.tags.includes(MISSING_TAG) && asset.tags.includes(category)
    ) ?? [];
  }
  __name(getAssetsForNodeType, "getAssetsForNodeType");
  async function getAssetDetails(id2) {
    const res = await api.fetchApi(`${ASSETS_ENDPOINT}/${id2}`);
    if (!res.ok) {
      throw new Error(
        `${EXPERIMENTAL_WARNING}Unable to load asset details for ${id2}: Server returned ${res.status}. Please try again.`
      );
    }
    const data = await res.json();
    const result = assetResponseSchema.safeParse({ assets: [data] });
    if (result.success && result.data.assets?.[0]) {
      return result.data.assets[0];
    }
    const error = result.error ? fromZodError(result.error) : "Unknown validation error";
    throw new Error(
      `${EXPERIMENTAL_WARNING}Invalid asset response against zod schema:
${error}`
    );
  }
  __name(getAssetDetails, "getAssetDetails");
  async function getAssetsByTag(tag, includePublic = true, {
    limit = DEFAULT_LIMIT,
    offset = 0
  } = {}) {
    const queryParams = new URLSearchParams({
      include_tags: tag,
      limit: limit.toString(),
      include_public: includePublic ? "true" : "false"
    });
    if (offset > 0) {
      queryParams.set("offset", offset.toString());
    }
    const data = await handleAssetRequest(
      `${ASSETS_ENDPOINT}?${queryParams.toString()}`,
      `assets for tag ${tag}`
    );
    return data?.assets?.filter((asset) => !asset.tags.includes(MISSING_TAG)) ?? [];
  }
  __name(getAssetsByTag, "getAssetsByTag");
  async function deleteAsset(id2) {
    const res = await api.fetchApi(`${ASSETS_ENDPOINT}/${id2}`, {
      method: "DELETE"
    });
    if (!res.ok) {
      throw new Error(
        `Unable to delete asset ${id2}: Server returned ${res.status}`
      );
    }
  }
  __name(deleteAsset, "deleteAsset");
  async function updateAsset(id2, newData) {
    const res = await api.fetchApi(`${ASSETS_ENDPOINT}/${id2}`, {
      method: "PUT",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(newData)
    });
    if (!res.ok) {
      throw new Error(
        `Unable to update asset ${id2}: Server returned ${res.status}`
      );
    }
    const newAsset = assetItemSchema.safeParse(await res.json());
    if (newAsset.success) {
      return newAsset.data;
    }
    throw new Error(
      `Unable to update asset ${id2}: Invalid response - ${newAsset.error}`
    );
  }
  __name(updateAsset, "updateAsset");
  async function getAssetMetadata(url) {
    const encodedUrl = encodeURIComponent(url);
    const res = await api.fetchApi(
      `${ASSETS_ENDPOINT}/remote-metadata?url=${encodedUrl}`
    );
    if (!res.ok) {
      const errorData = await res.json().catch(() => ({}));
      throw new Error(
        getLocalizedErrorMessage(errorData.code || "UNKNOWN_ERROR")
      );
    }
    const data = await res.json();
    if (data.validation?.is_valid === false) {
      throw new Error(
        getLocalizedErrorMessage(
          data.validation?.errors?.[0]?.code || "UNKNOWN_ERROR"
        )
      );
    }
    return data;
  }
  __name(getAssetMetadata, "getAssetMetadata");
  async function uploadAssetFromUrl(params) {
    const res = await api.fetchApi(ASSETS_ENDPOINT, {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(params)
    });
    if (!res.ok) {
      throw new Error(
        st(
          "assetBrowser.errorUploadFailed",
          "Failed to upload asset. Please try again."
        )
      );
    }
    return await res.json();
  }
  __name(uploadAssetFromUrl, "uploadAssetFromUrl");
  async function uploadAssetFromBase64(params) {
    if (!params.data || !params.data.startsWith("data:")) {
      throw new Error(
        'Invalid data URL: expected a string starting with "data:"'
      );
    }
    const blob = await fetch(params.data).then((r) => r.blob());
    const formData = new FormData();
    formData.append("file", blob, params.name);
    if (params.tags) {
      formData.append("tags", JSON.stringify(params.tags));
    }
    if (params.user_metadata) {
      formData.append("user_metadata", JSON.stringify(params.user_metadata));
    }
    const res = await api.fetchApi(ASSETS_ENDPOINT, {
      method: "POST",
      body: formData
    });
    if (!res.ok) {
      throw new Error(
        `Failed to upload asset from base64: ${res.status} ${res.statusText}`
      );
    }
    return await res.json();
  }
  __name(uploadAssetFromBase64, "uploadAssetFromBase64");
  return {
    getAssetModelFolders,
    getAssetModels,
    isAssetBrowserEligible,
    getAssetsForNodeType,
    getAssetDetails,
    getAssetsByTag,
    deleteAsset,
    updateAsset,
    getAssetMetadata,
    uploadAssetFromUrl,
    uploadAssetFromBase64
  };
}
__name(createAssetService, "createAssetService");
const assetService = createAssetService();
function getAssetType$1(tags) {
  const tag = tags?.[0];
  if (tag === "output") return "output";
  return "input";
}
__name(getAssetType$1, "getAssetType$1");
function mapTaskOutputToAssetItem(taskItem, output) {
  const metadata = {
    promptId: taskItem.promptId,
    nodeId: output.nodeId,
    subfolder: output.subfolder,
    executionTimeInSeconds: taskItem.executionTimeInSeconds,
    format: output.format,
    workflow: taskItem.workflow,
    create_time: taskItem.createTime
  };
  return {
    id: taskItem.promptId,
    name: output.filename,
    size: 0,
    created_at: taskItem.executionStartTimestamp ? new Date(taskItem.executionStartTimestamp).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
    tags: ["output"],
    preview_url: output.url,
    user_metadata: metadata
  };
}
__name(mapTaskOutputToAssetItem, "mapTaskOutputToAssetItem");
function mapInputFileToAssetItem(filename, index2, directory = "input") {
  return {
    id: `${directory}-${index2}-${filename}`,
    name: filename,
    size: 0,
    created_at: (/* @__PURE__ */ new Date()).toISOString(),
    tags: [directory],
    preview_url: api.apiURL(
      `/view?filename=${encodeURIComponent(filename)}&type=${directory}`
    )
  };
}
__name(mapInputFileToAssetItem, "mapInputFileToAssetItem");
async function fetchInputFilesFromAPI() {
  const response = await fetch(api.internalURL("/files/input"), {
    headers: {
      "Comfy-User": api.user
    }
  });
  if (!response.ok) {
    throw new Error("Failed to fetch input files");
  }
  const filenames = await response.json();
  return filenames.map(
    (name2, index2) => mapInputFileToAssetItem(name2, index2, "input")
  );
}
__name(fetchInputFilesFromAPI, "fetchInputFilesFromAPI");
function mapHistoryToAssets(historyItems) {
  const assetItems = [];
  for (const item of historyItems) {
    if (item.taskType !== "History") {
      continue;
    }
    if (!item.outputs || !item.status || item.status?.status_str === "error") {
      continue;
    }
    const task = new TaskItemImpl(
      "History",
      item.prompt,
      item.status,
      item.outputs
    );
    if (!task.previewOutput) {
      continue;
    }
    const assetItem = mapTaskOutputToAssetItem(task, task.previewOutput);
    const supportedOutputs = task.flatOutputs.filter((o) => o.supportsPreview);
    assetItem.user_metadata = {
      ...assetItem.user_metadata,
      outputCount: supportedOutputs.length,
      allOutputs: supportedOutputs
    };
    assetItems.push(assetItem);
  }
  return assetItems.sort(
    (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
  );
}
__name(mapHistoryToAssets, "mapHistoryToAssets");
const BATCH_SIZE = 200;
const MAX_HISTORY_ITEMS = 1e3;
const useAssetsStore = defineStore("assets", () => {
  const historyOffset = ref(0);
  const hasMoreHistory = ref(true);
  const isLoadingMore = ref(false);
  const allHistoryItems = ref([]);
  const loadedIds = shallowReactive(/* @__PURE__ */ new Set());
  const fetchInputFiles = fetchInputFilesFromAPI;
  const {
    state: inputAssets,
    isLoading: inputLoading,
    error: inputError,
    execute: updateInputs
  } = useAsyncState(fetchInputFiles, [], {
    immediate: false,
    resetOnExecute: false,
    onError: /* @__PURE__ */ __name((err) => {
      console.error("Error fetching input assets:", err);
    }, "onError")
  });
  const fetchHistoryAssets = /* @__PURE__ */ __name(async (loadMore = false) => {
    if (!loadMore) {
      historyOffset.value = 0;
      hasMoreHistory.value = true;
      allHistoryItems.value = [];
      loadedIds.clear();
    }
    const history = await api.getHistory(BATCH_SIZE, {
      offset: historyOffset.value
    });
    const newAssets = mapHistoryToAssets(history.History);
    if (loadMore) {
      for (const asset of newAssets) {
        if (loadedIds.has(asset.id)) {
          continue;
        }
        loadedIds.add(asset.id);
        const assetTime = new Date(asset.created_at).getTime();
        const insertIndex = allHistoryItems.value.findIndex(
          (item) => new Date(item.created_at).getTime() < assetTime
        );
        if (insertIndex === -1) {
          allHistoryItems.value.push(asset);
        } else {
          allHistoryItems.value.splice(insertIndex, 0, asset);
        }
      }
    } else {
      allHistoryItems.value = newAssets;
      newAssets.forEach((asset) => loadedIds.add(asset.id));
    }
    historyOffset.value += BATCH_SIZE;
    hasMoreHistory.value = history.History.length === BATCH_SIZE;
    if (allHistoryItems.value.length > MAX_HISTORY_ITEMS) {
      const removed = allHistoryItems.value.slice(MAX_HISTORY_ITEMS);
      allHistoryItems.value = allHistoryItems.value.slice(0, MAX_HISTORY_ITEMS);
      removed.forEach((item) => loadedIds.delete(item.id));
    }
    return allHistoryItems.value;
  }, "fetchHistoryAssets");
  const historyAssets = ref([]);
  const historyLoading = ref(false);
  const historyError = ref(null);
  const updateHistory = /* @__PURE__ */ __name(async () => {
    historyLoading.value = true;
    historyError.value = null;
    try {
      await fetchHistoryAssets(false);
      historyAssets.value = allHistoryItems.value;
    } catch (err) {
      console.error("Error fetching history assets:", err);
      historyError.value = err;
      if (!historyAssets.value.length) {
        historyAssets.value = [];
      }
    } finally {
      historyLoading.value = false;
    }
  }, "updateHistory");
  const loadMoreHistory = /* @__PURE__ */ __name(async () => {
    if (!hasMoreHistory.value || isLoadingMore.value) return;
    isLoadingMore.value = true;
    historyError.value = null;
    try {
      await fetchHistoryAssets(true);
      historyAssets.value = allHistoryItems.value;
    } catch (err) {
      console.error("Error loading more history:", err);
      historyError.value = err;
      if (!historyAssets.value.length) {
        historyAssets.value = [];
      }
    } finally {
      isLoadingMore.value = false;
    }
  }, "loadMoreHistory");
  const inputAssetsByFilename = computed(() => {
    const map2 = /* @__PURE__ */ new Map();
    for (const asset of inputAssets.value) {
      if (asset.asset_hash) {
        map2.set(asset.asset_hash, asset);
      }
    }
    return map2;
  });
  function getInputName(filename) {
    return inputAssetsByFilename.value.get(filename)?.name ?? filename;
  }
  __name(getInputName, "getInputName");
  const getModelState = /* @__PURE__ */ __name(() => {
    return {
      modelAssetsByNodeType: shallowReactive(/* @__PURE__ */ new Map()),
      modelLoadingByNodeType: shallowReactive(/* @__PURE__ */ new Map()),
      modelErrorByNodeType: shallowReactive(/* @__PURE__ */ new Map()),
      updateModelsForNodeType: /* @__PURE__ */ __name(async () => [], "updateModelsForNodeType")
    };
  }, "getModelState");
  const {
    modelAssetsByNodeType,
    modelLoadingByNodeType,
    modelErrorByNodeType,
    updateModelsForNodeType
  } = getModelState();
  return {
    // States
    inputAssets,
    historyAssets,
    inputLoading,
    historyLoading,
    inputError,
    historyError,
    hasMoreHistory,
    isLoadingMore,
    // Actions
    updateInputs,
    updateHistory,
    loadMoreHistory,
    // Input mapping helpers
    inputAssetsByFilename,
    getInputName,
    // Model assets
    modelAssetsByNodeType,
    modelLoadingByNodeType,
    modelErrorByNodeType,
    updateModelsForNodeType
  };
});
const MAX_RETRIES$1 = 5;
const TIMEOUT = 4096;
async function getAuthHeaders() {
  return {};
}
__name(getAuthHeaders, "getAuthHeaders");
const dataCache = /* @__PURE__ */ new Map();
const createCacheKey = /* @__PURE__ */ __name((config2) => {
  const { route, query_params = {}, refresh = 0 } = config2;
  const paramsKey = Object.entries(query_params).sort(([a], [b]) => a.localeCompare(b)).map(([k, v2]) => `${k}=${v2}`).join("&");
  return [route, `r=${refresh}`, paramsKey].join(";");
}, "createCacheKey");
const getBackoff = /* @__PURE__ */ __name((retryCount) => Math.min(1e3 * Math.pow(2, retryCount), 512), "getBackoff");
const isInitialized = /* @__PURE__ */ __name((entry) => entry?.data && entry?.timestamp && entry.timestamp > 0, "isInitialized");
const isStale = /* @__PURE__ */ __name((entry, ttl) => entry?.timestamp && Date.now() - entry.timestamp >= ttl, "isStale");
const isFetching = /* @__PURE__ */ __name((entry) => entry?.fetchPromise !== void 0, "isFetching");
const isFailed = /* @__PURE__ */ __name((entry) => entry?.failed === true, "isFailed");
const isBackingOff = /* @__PURE__ */ __name((entry) => entry?.error && entry?.lastErrorTime && Date.now() - entry.lastErrorTime < getBackoff(entry.retryCount || 0), "isBackingOff");
const fetchData = /* @__PURE__ */ __name(async (config2, controller) => {
  const { route, response_key, query_params, timeout = TIMEOUT } = config2;
  const authHeaders = await getAuthHeaders();
  const res = await axios.get(route, {
    params: query_params,
    signal: controller.signal,
    timeout,
    ...authHeaders
  });
  return response_key ? res.data[response_key] : res.data;
}, "fetchData");
function useRemoteWidget(options) {
  const { remoteConfig: remoteConfig2, defaultValue, node: node2, widget } = options;
  const { refresh = 0, max_retries = MAX_RETRIES$1 } = remoteConfig2;
  const isPermanent = refresh <= 0;
  const cacheKey = createCacheKey(remoteConfig2);
  let isLoaded = false;
  let refreshQueued = false;
  const setSuccess = /* @__PURE__ */ __name((entry, data) => {
    entry.retryCount = 0;
    entry.lastErrorTime = 0;
    entry.error = null;
    entry.timestamp = Date.now();
    entry.data = data ?? defaultValue;
  }, "setSuccess");
  const setError = /* @__PURE__ */ __name((entry, error) => {
    entry.retryCount = (entry.retryCount || 0) + 1;
    entry.lastErrorTime = Date.now();
    entry.error = error instanceof Error ? error : new Error(String(error));
    entry.data ??= defaultValue;
    entry.fetchPromise = void 0;
    if (entry.retryCount >= max_retries) {
      setFailed(entry);
    }
  }, "setError");
  const setFailed = /* @__PURE__ */ __name((entry) => {
    dataCache.set(cacheKey, {
      data: entry.data ?? defaultValue,
      failed: true
    });
  }, "setFailed");
  const isFirstLoad = /* @__PURE__ */ __name(() => {
    return !isLoaded && isInitialized(dataCache.get(cacheKey));
  }, "isFirstLoad");
  const onFirstLoad = /* @__PURE__ */ __name((data) => {
    isLoaded = true;
    widget.value = data[0];
    widget.callback?.(widget.value);
    node2.graph?.setDirtyCanvas(true);
  }, "onFirstLoad");
  const fetchValue = /* @__PURE__ */ __name(async () => {
    const entry = dataCache.get(cacheKey);
    if (isFailed(entry)) return entry.data;
    const isValid = isInitialized(entry) && (isPermanent || !isStale(entry, refresh));
    if (isValid || isBackingOff(entry) || isFetching(entry)) return entry.data;
    const currentEntry = entry || { data: defaultValue };
    dataCache.set(cacheKey, currentEntry);
    try {
      currentEntry.controller = new AbortController();
      currentEntry.fetchPromise = fetchData(
        remoteConfig2,
        currentEntry.controller
      );
      const data = await currentEntry.fetchPromise;
      setSuccess(currentEntry, data);
      return currentEntry.data;
    } catch (err) {
      setError(currentEntry, err);
      return currentEntry.data;
    } finally {
      currentEntry.fetchPromise = void 0;
      currentEntry.controller = void 0;
    }
  }, "fetchValue");
  const onRefresh = /* @__PURE__ */ __name(() => {
    if (remoteConfig2.control_after_refresh) {
      const data = getCachedValue();
      if (!Array.isArray(data)) return;
      switch (remoteConfig2.control_after_refresh) {
        case "first":
          widget.value = data[0] ?? defaultValue;
          break;
        case "last":
          widget.value = data.at(-1) ?? defaultValue;
          break;
      }
      widget.callback?.(widget.value);
      node2.graph?.setDirtyCanvas(true);
    }
  }, "onRefresh");
  const clearCachedValue = /* @__PURE__ */ __name(() => {
    const entry = dataCache.get(cacheKey);
    if (!entry) return;
    if (entry.fetchPromise) entry.controller?.abort();
    dataCache.delete(cacheKey);
  }, "clearCachedValue");
  function getCachedValue() {
    return dataCache.get(cacheKey)?.data;
  }
  __name(getCachedValue, "getCachedValue");
  function getValue(onFulfilled) {
    void fetchValue().then((data) => {
      if (isFirstLoad()) onFirstLoad(data);
      if (refreshQueued && data !== defaultValue) {
        onRefresh();
        refreshQueued = false;
      }
      onFulfilled?.();
    }).catch((err) => {
      console.error(err);
    });
    return getCachedValue() ?? defaultValue;
  }
  __name(getValue, "getValue");
  widget.refresh = function() {
    refreshQueued = true;
    clearCachedValue();
    getValue();
  };
  function addRefreshButton() {
    node2.addWidget("button", "refresh", "refresh", widget.refresh);
  }
  __name(addRefreshButton, "addRefreshButton");
  function addAutoRefreshToggle() {
    let autoRefreshEnabled = false;
    const handleExecutionSuccess = /* @__PURE__ */ __name(() => {
      if (autoRefreshEnabled && widget.refresh) {
        widget.refresh();
      }
    }, "handleExecutionSuccess");
    const autoRefreshWidget = node2.addWidget(
      "toggle",
      "Auto-refresh after generation",
      false,
      (value) => {
        autoRefreshEnabled = value;
      },
      {
        serialize: false
      }
    );
    api.addEventListener("execution_success", handleExecutionSuccess);
    node2.onRemoved = useChainCallback(node2.onRemoved, function() {
      api.removeEventListener("execution_success", handleExecutionSuccess);
    });
    return autoRefreshWidget;
  }
  __name(addAutoRefreshToggle, "addAutoRefreshToggle");
  addAutoRefreshToggle();
  return {
    getCachedValue,
    getValue,
    refreshValue: widget.refresh,
    addRefreshButton,
    getCacheEntry: /* @__PURE__ */ __name(() => dataCache.get(cacheKey), "getCacheEntry"),
    cacheKey
  };
}
__name(useRemoteWidget, "useRemoteWidget");
const getDefaultValue = /* @__PURE__ */ __name((inputSpec) => {
  if (inputSpec.default) return inputSpec.default;
  if (inputSpec.options?.length) return inputSpec.options[0];
  if (inputSpec.remote) return "Loading...";
  return void 0;
}, "getDefaultValue");
const addMultiSelectWidget = /* @__PURE__ */ __name((node2, inputSpec) => {
  const widgetValue = ref([]);
  const widget = new ComponentWidgetImpl({
    node: node2,
    name: inputSpec.name,
    component: _sfc_main$2w,
    inputSpec,
    options: {
      getValue: /* @__PURE__ */ __name(() => widgetValue.value, "getValue"),
      setValue: /* @__PURE__ */ __name((value) => {
        widgetValue.value = value;
      }, "setValue")
    }
  });
  addWidget(node2, widget);
  if (inputSpec.control_after_generate) {
    widget.linkedWidgets = addValueControlWidgets(
      node2,
      widget,
      "fixed",
      void 0,
      transformInputSpecV2ToV1(inputSpec)
    );
  }
  return widget;
}, "addMultiSelectWidget");
const addComboWidget = /* @__PURE__ */ __name((node2, inputSpec) => {
  const defaultValue = getDefaultValue(inputSpec);
  const widget = node2.addWidget(
    "combo",
    inputSpec.name,
    defaultValue,
    () => {
    },
    {
      values: inputSpec.options ?? []
    }
  );
  if (inputSpec.remote) {
    if (!isComboWidget(widget)) {
      throw new Error(`Expected combo widget but received ${widget.type}`);
    }
    const remoteWidget = useRemoteWidget({
      remoteConfig: inputSpec.remote,
      defaultValue,
      node: node2,
      widget
    });
    if (inputSpec.remote.refresh_button) remoteWidget.addRefreshButton();
    const origOptions = widget.options;
    widget.options = new Proxy(origOptions, {
      get(target, prop2) {
        return prop2 !== "values" ? target[prop2] : remoteWidget.getValue();
      }
    });
  }
  if (inputSpec.control_after_generate) {
    if (!isComboWidget(widget)) {
      throw new Error(`Expected combo widget but received ${widget.type}`);
    }
    widget.linkedWidgets = addValueControlWidgets(
      node2,
      widget,
      void 0,
      void 0,
      transformInputSpecV2ToV1(inputSpec)
    );
  }
  return widget;
}, "addComboWidget");
const useComboWidget = /* @__PURE__ */ __name(() => {
  const widgetConstructor = /* @__PURE__ */ __name((node2, inputSpec) => {
    if (!isComboInputSpec(inputSpec)) {
      throw new Error(`Invalid input data: ${inputSpec}`);
    }
    return inputSpec.multi_select ? addMultiSelectWidget(node2, inputSpec) : addComboWidget(node2, inputSpec);
  }, "widgetConstructor");
  return widgetConstructor;
}, "useComboWidget");
function onFloatValueChange(v2) {
  const round = this.options.round;
  if (round) {
    const precision = this.options.precision ?? Math.max(0, -Math.floor(Math.log10(round)));
    const rounded = Math.round(v2 / round) * round;
    this.value = clamp(
      Number(rounded.toFixed(precision)),
      this.options.min ?? -Infinity,
      this.options.max ?? Infinity
    );
  } else {
    this.value = v2;
  }
}
__name(onFloatValueChange, "onFloatValueChange");
const useFloatWidget = /* @__PURE__ */ __name(() => {
  const widgetConstructor = /* @__PURE__ */ __name((node2, inputSpec) => {
    if (!isFloatInputSpec(inputSpec)) {
      throw new Error(`Invalid input data: ${inputSpec}`);
    }
    const settingStore = useSettingStore();
    const sliderEnabled = !settingStore.get("Comfy.DisableSliders");
    const display_type = inputSpec.display;
    const widgetType = sliderEnabled && display_type == "slider" ? "slider" : display_type == "knob" ? "knob" : "number";
    const step = inputSpec.step ?? 0.5;
    const precision = settingStore.get("Comfy.FloatRoundingPrecision") || Math.max(0, -Math.floor(Math.log10(step)));
    const enableRounding = !settingStore.get("Comfy.DisableFloatRounding");
    const defaultValue = inputSpec.default ?? 0;
    const widget = node2.addWidget(
      widgetType,
      inputSpec.name,
      defaultValue,
      onFloatValueChange,
      {
        min: inputSpec.min ?? 0,
        max: inputSpec.max ?? 2048,
        round: enableRounding && precision && !inputSpec.round ? Math.pow(10, -precision) : inputSpec.round,
        /** @deprecated Use step2 instead. The 10x value is a legacy implementation. */
        step: step * 10,
        step2: step,
        precision
      }
    );
    if (inputSpec.control_after_generate) {
      const controlWidget = addValueControlWidget(
        node2,
        widget,
        "fixed",
        void 0,
        void 0,
        transformInputSpecV2ToV1(inputSpec)
      );
      widget.linkedWidgets = [controlWidget];
    }
    return widget;
  }, "widgetConstructor");
  return widgetConstructor;
}, "useFloatWidget");
const useGalleriaWidget = /* @__PURE__ */ __name(() => {
  return (node2, inputSpec) => {
    const { name: name2, options = {} } = inputSpec;
    const widget = node2.addWidget(
      "galleria",
      name2,
      options.images || [],
      () => {
      },
      {
        serialize: true,
        ...options
      }
    );
    return widget;
  };
}, "useGalleriaWidget");
const useImageCompareWidget = /* @__PURE__ */ __name(() => {
  return (node2, inputSpec) => {
    const { name: name2, options = {} } = inputSpec;
    const widget = node2.addWidget("imagecompare", name2, ["", ""], () => {
    }, {
      serialize: true,
      ...options
    });
    return widget;
  };
}, "useImageCompareWidget");
function isMiddlePointerInput(event) {
  if ("button" in event && event.button === 1) {
    return true;
  }
  if ("buttons" in event && typeof event.buttons === "number") {
    return event.buttons === 4;
  }
  return false;
}
__name(isMiddlePointerInput, "isMiddlePointerInput");
function useCanvasInteractions() {
  const settingStore = useSettingStore();
  const canvasStore = useCanvasStore();
  const { getCanvas } = canvasStore;
  const isStandardNavMode = computed(
    () => settingStore.get("Comfy.Canvas.NavigationMode") === "standard"
  );
  const shouldHandleNodePointerEvents = computed(
    () => !(canvasStore.canvas?.read_only ?? false)
  );
  const handleWheel = /* @__PURE__ */ __name((event) => {
    const target = event.target;
    const captureElement = target?.closest('[data-capture-wheel="true"]');
    if (captureElement) {
      return;
    }
    if (isStandardNavMode.value && (event.ctrlKey || event.metaKey)) {
      forwardEventToCanvas(event);
      return;
    }
    if (!isStandardNavMode.value) {
      forwardEventToCanvas(event);
      return;
    }
  }, "handleWheel");
  const handlePointer = /* @__PURE__ */ __name((event) => {
    if (isMiddlePointerInput(event)) {
      forwardEventToCanvas(event);
      return;
    }
    const canvas2 = getCanvas();
    if (!canvas2) return;
    const isSpacePanningDrag = canvas2.read_only && event.buttons === 1;
    const isMiddleMousePanning = event.buttons === 4;
    if (isSpacePanningDrag || isMiddleMousePanning) {
      event.preventDefault();
      event.stopPropagation();
      forwardEventToCanvas(event);
      return;
    }
  }, "handlePointer");
  const forwardEventToCanvas = /* @__PURE__ */ __name((event) => {
    const target = event.target;
    const captureElement = target?.closest('[data-capture-wheel="true"]');
    if (captureElement) {
      return;
    }
    const canvasEl = app$1.canvas?.canvas;
    if (!canvasEl) return;
    event.preventDefault();
    event.stopPropagation();
    if (event instanceof WheelEvent) {
      const { clientX, clientY, deltaX, deltaY, ctrlKey, metaKey, shiftKey } = event;
      canvasEl.dispatchEvent(
        new WheelEvent("wheel", {
          clientX,
          clientY,
          deltaX,
          deltaY,
          ctrlKey,
          metaKey,
          shiftKey
        })
      );
      return;
    }
    const EventConstructor = event.constructor;
    const newEvent = new EventConstructor(event.type, event);
    canvasEl.dispatchEvent(newEvent);
  }, "forwardEventToCanvas");
  return {
    handleWheel,
    handlePointer,
    forwardEventToCanvas,
    shouldHandleNodePointerEvents
  };
}
__name(useCanvasInteractions, "useCanvasInteractions");
const is_all_same_aspect_ratio = /* @__PURE__ */ __name((imgs) => {
  if (!imgs.length || imgs.length === 1) return true;
  const ratio = imgs[0].naturalWidth / imgs[0].naturalHeight;
  for (let i = 1; i < imgs.length; i++) {
    const this_ratio = imgs[i].naturalWidth / imgs[i].naturalHeight;
    if (ratio != this_ratio) return false;
  }
  return true;
}, "is_all_same_aspect_ratio");
const fitDimensionsToNodeWidth = /* @__PURE__ */ __name((width2, height, nodeWidth, minHeight = 64) => {
  const intrinsicAspectRatio = width2 / height;
  if (!intrinsicAspectRatio || isNaN(intrinsicAspectRatio))
    return { minHeight: 0, minWidth: 0 };
  const minWidth = nodeWidth;
  const calculatedHeight = Math.max(minWidth / intrinsicAspectRatio, minHeight);
  return { minHeight: calculatedHeight, minWidth };
}, "fitDimensionsToNodeWidth");
const VIDEO_WIDGET_NAME = "video-preview";
const VIDEO_DEFAULT_OPTIONS = {
  playsInline: true,
  controls: true,
  loop: true
};
const MEDIA_LOAD_TIMEOUT = 8192;
const MAX_RETRIES = 1;
const DEFAULT_VIDEO_SIZE = 256;
const createContainer = /* @__PURE__ */ __name(() => {
  const container = document.createElement("div");
  container.classList.add("comfy-img-preview");
  return container;
}, "createContainer");
const createTimeout = /* @__PURE__ */ __name((ms) => new Promise((resolve) => setTimeout(() => resolve(null), ms)), "createTimeout");
const useNodePreview = /* @__PURE__ */ __name((node2, options) => {
  const { loadElement, onLoaded, onFailedLoading } = options;
  const nodeOutputStore = useNodeOutputStore();
  const loadElementWithTimeout = /* @__PURE__ */ __name(async (url, retryCount = 0) => {
    const result = await Promise.race([
      loadElement(url),
      createTimeout(MEDIA_LOAD_TIMEOUT)
    ]);
    if (result === null && retryCount < MAX_RETRIES) {
      return loadElementWithTimeout(url, retryCount + 1);
    }
    return result;
  }, "loadElementWithTimeout");
  const loadElements = /* @__PURE__ */ __name(async (urls) => Promise.all(urls.map((url) => loadElementWithTimeout(url))), "loadElements");
  function showPreview(options2 = {}) {
    if (node2.isLoading) return;
    const outputUrls = nodeOutputStore.getNodeImageUrls(node2);
    if (!outputUrls?.length) return;
    if (options2?.block) node2.isLoading = true;
    loadElements(outputUrls).then((elements) => {
      const validElements = elements.filter(
        (el) => el !== null
      );
      if (validElements.length) {
        onLoaded?.(validElements);
        node2.graph?.setDirtyCanvas(true);
      }
    }).catch(() => {
      onFailedLoading?.();
    }).finally(() => {
      node2.isLoading = false;
    });
  }
  __name(showPreview, "showPreview");
  return {
    showPreview
  };
}, "useNodePreview");
const useNodeImage = /* @__PURE__ */ __name((node2, callback) => {
  node2.previewMediaType = "image";
  const loadElement = /* @__PURE__ */ __name((url) => new Promise((resolve) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = () => resolve(null);
    img.src = url;
  }), "loadElement");
  const onLoaded = /* @__PURE__ */ __name((elements) => {
    node2.imageIndex = null;
    node2.imgs = elements;
    callback?.();
  }, "onLoaded");
  return useNodePreview(node2, {
    loadElement,
    onLoaded,
    onFailedLoading: /* @__PURE__ */ __name(() => {
      node2.imgs = void 0;
    }, "onFailedLoading")
  });
}, "useNodeImage");
const useNodeVideo = /* @__PURE__ */ __name((node2, callback) => {
  node2.previewMediaType = "video";
  let minHeight = DEFAULT_VIDEO_SIZE;
  let minWidth = DEFAULT_VIDEO_SIZE;
  const { handleWheel, handlePointer } = useCanvasInteractions();
  const setMinDimensions = /* @__PURE__ */ __name((video) => {
    const { minHeight: calculatedHeight, minWidth: calculatedWidth } = fitDimensionsToNodeWidth(
      video.videoWidth,
      video.videoHeight,
      node2.size?.[0] || DEFAULT_VIDEO_SIZE
    );
    minWidth = calculatedWidth;
    minHeight = calculatedHeight;
  }, "setMinDimensions");
  const loadElement = /* @__PURE__ */ __name((url) => new Promise((resolve) => {
    const video = document.createElement("video");
    Object.assign(video, VIDEO_DEFAULT_OPTIONS);
    video.addEventListener("wheel", handleWheel);
    video.addEventListener("pointermove", handlePointer);
    video.addEventListener("pointerdown", handlePointer);
    video.onloadeddata = () => {
      setMinDimensions(video);
      resolve(video);
    };
    video.onerror = () => resolve(null);
    video.src = url;
  }), "loadElement");
  const addVideoDomWidget = /* @__PURE__ */ __name((container) => {
    const hasWidget = node2.widgets?.some((w) => w.name === VIDEO_WIDGET_NAME);
    if (!hasWidget) {
      const widget = node2.addDOMWidget(VIDEO_WIDGET_NAME, "video", container, {
        canvasOnly: true,
        hideOnZoom: false
      });
      widget.serialize = false;
      widget.computeLayoutSize = () => ({
        minHeight,
        minWidth
      });
    }
  }, "addVideoDomWidget");
  const onLoaded = /* @__PURE__ */ __name((videoElements) => {
    const videoElement = videoElements[0];
    if (!videoElement) return;
    if (!node2.videoContainer) {
      node2.videoContainer = createContainer();
      addVideoDomWidget(node2.videoContainer);
    }
    node2.videoContainer.replaceChildren(videoElement);
    callback?.();
  }, "onLoaded");
  return useNodePreview(node2, {
    loadElement,
    onLoaded,
    onFailedLoading: /* @__PURE__ */ __name(() => {
      node2.videoContainer = void 0;
    }, "onFailedLoading")
  });
}, "useNodeVideo");
const useNodeDragAndDrop = /* @__PURE__ */ __name((node2, options) => {
  const { onDragOver, onDrop, fileFilter = /* @__PURE__ */ __name(() => true, "fileFilter") } = options;
  const hasFiles = /* @__PURE__ */ __name((items) => !!Array.from(items).find((f) => f.kind === "file"), "hasFiles");
  const filterFiles = /* @__PURE__ */ __name((files) => Array.from(files).filter(fileFilter), "filterFiles");
  const hasValidFiles = /* @__PURE__ */ __name((files) => filterFiles(files).length > 0, "hasValidFiles");
  const isDraggingFiles = /* @__PURE__ */ __name((e2) => {
    if (!e2?.dataTransfer?.items) return false;
    return onDragOver?.(e2) ?? hasFiles(e2.dataTransfer.items);
  }, "isDraggingFiles");
  const isDraggingValidFiles = /* @__PURE__ */ __name((e2) => {
    if (!e2?.dataTransfer?.files) return false;
    return hasValidFiles(e2.dataTransfer.files);
  }, "isDraggingValidFiles");
  node2.onDragOver = isDraggingFiles;
  node2.onDragDrop = function(e2) {
    if (!isDraggingValidFiles(e2)) return false;
    const files = filterFiles(e2.dataTransfer.files);
    void onDrop(files);
    return true;
  };
}, "useNodeDragAndDrop");
function useNodeFileInput(node2, options) {
  const {
    accept,
    allow_batch = false,
    fileFilter = /* @__PURE__ */ __name(() => true, "fileFilter"),
    onSelect
  } = options;
  let fileInput = document.createElement("input");
  fileInput.type = "file";
  fileInput.accept = accept ?? "*";
  fileInput.multiple = allow_batch;
  fileInput.onchange = () => {
    if (fileInput?.files?.length) {
      const files = Array.from(fileInput.files).filter(fileFilter);
      if (files.length) onSelect(files);
    }
  };
  node2.onRemoved = useChainCallback(node2.onRemoved, () => {
    if (fileInput) {
      fileInput.onchange = null;
      fileInput = null;
    }
  });
  return {
    openFileSelection: /* @__PURE__ */ __name(() => fileInput?.click(), "openFileSelection")
  };
}
__name(useNodeFileInput, "useNodeFileInput");
const useNodePaste = /* @__PURE__ */ __name((node2, options) => {
  const { onPaste, fileFilter = /* @__PURE__ */ __name(() => true, "fileFilter"), allow_batch = false } = options;
  node2.pasteFiles = function(files) {
    const filteredFiles = Array.from(files).filter(fileFilter);
    if (!filteredFiles.length) return false;
    const paste = allow_batch ? filteredFiles : filteredFiles.slice(0, 1);
    void onPaste(paste);
    return true;
  };
}, "useNodePaste");
const PASTED_IMAGE_EXPIRY_MS = 2e3;
const uploadFile = /* @__PURE__ */ __name(async (file, isPasted, formFields = {}) => {
  const body = new FormData();
  body.append("image", file);
  if (isPasted) body.append("subfolder", "pasted");
  if (formFields.type) body.append("type", formFields.type);
  const resp = await api.fetchApi("/upload/image", {
    method: "POST",
    body
  });
  if (resp.status !== 200) {
    useToastStore().addAlert(resp.status + " - " + resp.statusText);
    return;
  }
  const data = await resp.json();
  if (formFields.type === "input" || !formFields.type && !isPasted) {
    const assetsStore = useAssetsStore();
    await assetsStore.updateInputs();
  }
  return data.subfolder ? `${data.subfolder}/${data.name}` : data.name;
}, "uploadFile");
const useNodeImageUpload = /* @__PURE__ */ __name((node2, options) => {
  const { fileFilter, onUploadComplete, allow_batch, accept } = options;
  const isPastedFile = /* @__PURE__ */ __name((file) => file.name === "image.png" && file.lastModified - Date.now() < PASTED_IMAGE_EXPIRY_MS, "isPastedFile");
  const handleUpload = /* @__PURE__ */ __name(async (file) => {
    try {
      const path = await uploadFile(file, isPastedFile(file), {
        type: options.folder
      });
      if (!path) return;
      return path;
    } catch (error) {
      useToastStore().addAlert(String(error));
    }
  }, "handleUpload");
  const handleUploadBatch = /* @__PURE__ */ __name(async (files) => {
    const paths = await Promise.all(files.map(handleUpload));
    const validPaths = paths.filter((p) => !!p);
    if (validPaths.length) onUploadComplete(validPaths);
    return validPaths;
  }, "handleUploadBatch");
  useNodeDragAndDrop(node2, {
    fileFilter,
    onDrop: handleUploadBatch
  });
  useNodePaste(node2, {
    fileFilter,
    allow_batch,
    onPaste: handleUploadBatch
  });
  const { openFileSelection } = useNodeFileInput(node2, {
    fileFilter,
    allow_batch,
    accept,
    onSelect: handleUploadBatch
  });
  return { openFileSelection, handleUpload };
}, "useNodeImageUpload");
function useValueTransform(transform, initialValue) {
  let internalValue = initialValue;
  let cachedValue = transform(initialValue);
  let isChanged = false;
  return {
    get: /* @__PURE__ */ __name(() => {
      if (!isChanged) return cachedValue;
      cachedValue = transform(internalValue);
      return cachedValue;
    }, "get"),
    set: /* @__PURE__ */ __name((value) => {
      isChanged = true;
      internalValue = value;
    }, "set")
  };
}
__name(useValueTransform, "useValueTransform");
function isImageUploadInput(inputData) {
  const options = inputData[1];
  return options !== void 0 && typeof options === "object" && "imageInputName" in options && typeof options.imageInputName === "string";
}
__name(isImageUploadInput, "isImageUploadInput");
const hasAnnotation = /* @__PURE__ */ __name((filepath) => /\[(input|output|temp)\]/i.test(filepath), "hasAnnotation");
const createAnnotation = /* @__PURE__ */ __name((filepath, rootFolder = "input") => !hasAnnotation(filepath) && rootFolder !== "input" ? ` [${rootFolder}]` : "", "createAnnotation");
const createPath = /* @__PURE__ */ __name((filename, subfolder = "") => subfolder ? `${subfolder}/${filename}` : filename, "createPath");
function createAnnotatedPath(item, options = {}) {
  const { rootFolder = "input", subfolder } = options;
  if (typeof item === "string")
    return `${createPath(item, subfolder)}${createAnnotation(item, rootFolder)}`;
  return `${createPath(item.filename ?? "", item.subfolder)}${item.type ? createAnnotation(item.type, rootFolder) : ""}`;
}
__name(createAnnotatedPath, "createAnnotatedPath");
function isImageNode(node2) {
  if (!node2) return false;
  return node2.previewMediaType === "image" || node2.previewMediaType !== "video" && !!node2.imgs?.length;
}
__name(isImageNode, "isImageNode");
function isVideoNode(node2) {
  if (!node2) return false;
  return node2.previewMediaType === "video" || !!node2.videoContainer;
}
__name(isVideoNode, "isVideoNode");
function isAudioNode(node2) {
  return !!node2 && node2.previewMediaType === "audio";
}
__name(isAudioNode, "isAudioNode");
function addToComboValues(widget, value) {
  if (!widget.options) widget.options = { values: [] };
  if (!widget.options.values) widget.options.values = [];
  if (!widget.options.values.includes(value)) {
    widget.options.values.push(value);
  }
}
__name(addToComboValues, "addToComboValues");
const isLGraphNode = /* @__PURE__ */ __name((item) => {
  return item instanceof LGraphNode;
}, "isLGraphNode");
const isLGraphGroup = /* @__PURE__ */ __name((item) => {
  return item instanceof LGraphGroup;
}, "isLGraphGroup");
const isReroute = /* @__PURE__ */ __name((item) => {
  return item instanceof Reroute;
}, "isReroute");
const getItemsColorOption = /* @__PURE__ */ __name((items) => {
  const validItems = toolkit.filter(items, isColorable);
  if (toolkit.isEmpty(validItems)) return null;
  const colorOptions = toolkit.map(validItems, (item) => item.getColorOption());
  return toolkit.every(
    colorOptions,
    (option) => toolkit.isEqual(option, toolkit.head(colorOptions))
  ) ? toolkit.head(colorOptions) : null;
}, "getItemsColorOption");
function executeWidgetsCallback(nodes, callbackName) {
  for (const node2 of nodes) {
    for (const widget of node2.widgets ?? []) {
      widget[callbackName]?.();
    }
  }
}
__name(executeWidgetsCallback, "executeWidgetsCallback");
function migrateWidgetsValues(inputDefs, widgets2, widgetsValues) {
  const widgetNames = new Set(widgets2.map((w) => w.name));
  const originalWidgetsInputs = Object.values(inputDefs).filter(
    (input) => widgetNames.has(input.name) || input.forceInput
  );
  const numOriginalWidgets = toolkit.sum(
    originalWidgetsInputs.map(
      (input) => (
        // If the input has control, it will have 2 widgets.
        input.control_after_generate || ["seed", "noise_seed"].includes(input.name) ? 2 : 1
      )
    )
  );
  if (numOriginalWidgets === widgetsValues?.length) {
    return toolkit.zip(originalWidgetsInputs, widgetsValues).filter(([input]) => !input?.forceInput).map(([_2, value]) => value);
  }
  return widgetsValues;
}
__name(migrateWidgetsValues, "migrateWidgetsValues");
function fixLinkInputSlots(graph) {
  for (const node2 of graph.nodes) {
    for (const [inputIndex, input] of node2.inputs.entries()) {
      const linkId = input.link;
      if (!linkId) continue;
      const link = graph.links.get(linkId);
      if (!link) continue;
      link.target_slot = inputIndex;
    }
    if (node2.isSubgraphNode?.() && node2.subgraph) {
      fixLinkInputSlots(node2.subgraph);
    }
  }
}
__name(fixLinkInputSlots, "fixLinkInputSlots");
function compressWidgetInputSlots(graph) {
  for (const node2 of graph.nodes) {
    node2.inputs = node2.inputs?.filter(matchesLegacyApi);
    for (const [inputIndex, input] of node2.inputs?.entries() ?? []) {
      if (input.link) {
        const link = graph.links.find((link2) => link2[0] === input.link);
        if (link) {
          link[4] = inputIndex;
        }
      }
    }
  }
  compressSubgraphWidgetInputSlots(graph.definitions?.subgraphs);
}
__name(compressWidgetInputSlots, "compressWidgetInputSlots");
function matchesLegacyApi(input) {
  return !(input.widget && input.link === null && !input.label);
}
__name(matchesLegacyApi, "matchesLegacyApi");
function compressSubgraphWidgetInputSlots(subgraphs, visited = /* @__PURE__ */ new WeakSet()) {
  if (!subgraphs) return;
  for (const subgraph of subgraphs) {
    if (visited.has(subgraph)) throw new Error("Infinite loop detected");
    visited.add(subgraph);
    if (subgraph.nodes) {
      for (const node2 of subgraph.nodes) {
        node2.inputs = node2.inputs?.filter(matchesLegacyApi);
        if (!subgraph.links) continue;
        for (const [inputIndex, input] of node2.inputs?.entries() ?? []) {
          if (input.link) {
            const link = subgraph.links.find((link2) => link2.id === input.link);
            if (link) link.target_slot = inputIndex;
          }
        }
      }
    }
    compressSubgraphWidgetInputSlots(subgraph.definitions?.subgraphs, visited);
  }
}
__name(compressSubgraphWidgetInputSlots, "compressSubgraphWidgetInputSlots");
function isLoad3dNode(node2) {
  return node2 && node2.type && (node2.type === "Load3D" || node2.type === "Load3DAnimation");
}
__name(isLoad3dNode, "isLoad3dNode");
const ACCEPTED_IMAGE_TYPES = "image/png,image/jpeg,image/webp";
const ACCEPTED_VIDEO_TYPES = "video/webm,video/mp4";
const isImageFile = /* @__PURE__ */ __name((file) => file.type.startsWith("image/"), "isImageFile");
const isVideoFile = /* @__PURE__ */ __name((file) => file.type.startsWith("video/"), "isVideoFile");
const findFileComboWidget = /* @__PURE__ */ __name((node2, inputName) => node2.widgets.find((w) => w.name === inputName), "findFileComboWidget");
const useImageUploadWidget = /* @__PURE__ */ __name(() => {
  const widgetConstructor = /* @__PURE__ */ __name((node2, inputName, inputData) => {
    if (!isImageUploadInput(inputData)) {
      throw new Error(
        "Image upload widget requires imageInputName augmentation"
      );
    }
    const inputOptions = inputData[1];
    const { imageInputName, allow_batch, image_folder = "input" } = inputOptions;
    const folder = image_folder;
    const nodeOutputStore = useNodeOutputStore();
    const isAnimated = !!inputOptions.animated_image_upload;
    const isVideo = !!inputOptions.video_upload;
    const accept = isVideo ? ACCEPTED_VIDEO_TYPES : ACCEPTED_IMAGE_TYPES;
    const { showPreview } = isVideo ? useNodeVideo(node2) : useNodeImage(node2);
    const fileFilter = isVideo ? isVideoFile : isImageFile;
    const fileComboWidget = findFileComboWidget(node2, imageInputName);
    const initialFile = `${fileComboWidget.value}`;
    const formatPath = /* @__PURE__ */ __name((value) => createAnnotatedPath(value, { rootFolder: image_folder }), "formatPath");
    const transform = /* @__PURE__ */ __name((internalValue) => {
      if (!internalValue) return initialFile;
      if (Array.isArray(internalValue))
        return allow_batch ? internalValue.map(formatPath) : formatPath(internalValue[0]);
      return formatPath(internalValue);
    }, "transform");
    Object.defineProperty(
      fileComboWidget,
      "value",
      useValueTransform(transform, initialFile)
    );
    const { openFileSelection } = useNodeImageUpload(node2, {
      allow_batch,
      fileFilter,
      accept,
      folder,
      onUploadComplete: /* @__PURE__ */ __name((output) => {
        output.forEach((path) => addToComboValues(fileComboWidget, path));
        const newValue2 = allow_batch ? [...output] : output[0];
        fileComboWidget.value = newValue2;
        fileComboWidget.callback?.(newValue2);
      }, "onUploadComplete")
    });
    const uploadWidget = node2.addWidget(
      "button",
      inputName,
      "image",
      () => openFileSelection(),
      {
        serialize: false,
        canvasOnly: true
      }
    );
    uploadWidget.label = t("g.choose_file_to_upload");
    fileComboWidget.callback = function() {
      nodeOutputStore.setNodeOutputs(node2, fileComboWidget.value, {
        isAnimated
      });
      node2.graph?.setDirtyCanvas(true);
    };
    requestAnimationFrame(() => {
      nodeOutputStore.setNodeOutputs(node2, fileComboWidget.value, {
        isAnimated
      });
      showPreview({ block: false });
    });
    return { widget: uploadWidget };
  }, "widgetConstructor");
  return widgetConstructor;
}, "useImageUploadWidget");
function onValueChange(v2) {
  const step = this.options.step2 || 1;
  if (step === 1) {
    this.value = Math.round(v2);
  } else {
    const min = this.options.min ?? 0;
    const offset = min % step;
    this.value = Math.round((v2 - offset) / step) * step + offset;
  }
}
__name(onValueChange, "onValueChange");
const useIntWidget = /* @__PURE__ */ __name(() => {
  const widgetConstructor = /* @__PURE__ */ __name((node2, inputSpec) => {
    if (!isIntInputSpec(inputSpec)) {
      throw new Error(`Invalid input data: ${inputSpec}`);
    }
    const settingStore = useSettingStore();
    const sliderEnabled = !settingStore.get("Comfy.DisableSliders");
    const display_type = inputSpec.display;
    const widgetType = sliderEnabled && display_type == "slider" ? "slider" : display_type == "knob" ? "knob" : "number";
    const step = inputSpec.step ?? 1;
    const defaultValue = inputSpec.default ?? 0;
    const widget = node2.addWidget(
      widgetType,
      inputSpec.name,
      defaultValue,
      onValueChange,
      {
        min: inputSpec.min ?? 0,
        max: inputSpec.max ?? 2048,
        /** @deprecated Use step2 instead. The 10x value is a legacy implementation. */
        step: step * 10,
        step2: step,
        precision: 0
      }
    );
    const controlAfterGenerate = inputSpec.control_after_generate ?? ["seed", "noise_seed"].includes(inputSpec.name);
    if (controlAfterGenerate) {
      const controlWidget = addValueControlWidget(
        node2,
        widget,
        "randomize",
        void 0,
        void 0,
        transformInputSpecV2ToV1(inputSpec)
      );
      widget.linkedWidgets = [controlWidget];
    }
    return widget;
  }, "widgetConstructor");
  return widgetConstructor;
}, "useIntWidget");
function addMarkdownWidget(node2, name2, opts) {
  Markdown.configure({
    html: false,
    breaks: true,
    transformPastedText: true
  });
  const editor = new Editor({
    extensions: [
      StarterKit,
      Markdown,
      Link,
      Table,
      TableCell,
      TableHeader,
      TableRow
    ],
    content: opts.defaultVal,
    editable: false
  });
  const inputEl = editor.options.element;
  inputEl.classList.add("comfy-markdown");
  const textarea = document.createElement("textarea");
  inputEl.append(textarea);
  const widget = node2.addDOMWidget(name2, "MARKDOWN", inputEl, {
    getValue() {
      return textarea.value;
    },
    setValue(v2) {
      textarea.value = v2;
      editor.commands.setContent(v2);
    }
  });
  widget.inputEl = inputEl;
  widget.options.minNodeSize = [400, 200];
  inputEl.addEventListener("dblclick", () => {
    inputEl.classList.add("editing");
    setTimeout(() => {
      textarea.focus();
    }, 0);
  });
  textarea.addEventListener("blur", () => {
    inputEl.classList.remove("editing");
  });
  textarea.addEventListener("change", () => {
    editor.commands.setContent(textarea.value);
    widget.callback?.(widget.value);
  });
  inputEl.addEventListener("keydown", (event) => {
    event.stopPropagation();
  });
  inputEl.addEventListener("pointerdown", (event) => {
    if (event.button === 1) {
      app$1.canvas.processMouseDown(event);
    }
  });
  inputEl.addEventListener("pointermove", (event) => {
    if ((event.buttons & 4) === 4) {
      app$1.canvas.processMouseMove(event);
    }
  });
  inputEl.addEventListener("pointerup", (event) => {
    if (event.button === 1) {
      app$1.canvas.processMouseUp(event);
    }
  });
  return widget;
}
__name(addMarkdownWidget, "addMarkdownWidget");
const useMarkdownWidget = /* @__PURE__ */ __name(() => {
  const widgetConstructor = /* @__PURE__ */ __name((node2, inputSpec) => {
    return addMarkdownWidget(node2, inputSpec.name, {
      defaultVal: inputSpec.default ?? ""
    });
  }, "widgetConstructor");
  return widgetConstructor;
}, "useMarkdownWidget");
const TRACKPAD_DETECTION_THRESHOLD = 50;
function addMultilineWidget(node2, name2, opts) {
  const inputEl = document.createElement("textarea");
  inputEl.className = "comfy-multiline-input";
  inputEl.value = opts.defaultVal;
  inputEl.placeholder = opts.placeholder || name2;
  inputEl.spellcheck = useSettingStore().get("Comfy.TextareaWidget.Spellcheck");
  const widget = node2.addDOMWidget(name2, "customtext", inputEl, {
    getValue() {
      return inputEl.value;
    },
    setValue(v2) {
      inputEl.value = v2;
    }
  });
  widget.inputEl = inputEl;
  widget.options.minNodeSize = [400, 200];
  inputEl.addEventListener("input", () => {
    widget.callback?.(widget.value);
  });
  inputEl.addEventListener("pointerdown", (event) => {
    if (event.button === 1) {
      app$1.canvas.processMouseDown(event);
    }
  });
  inputEl.addEventListener("pointermove", (event) => {
    if ((event.buttons & 4) === 4) {
      app$1.canvas.processMouseMove(event);
    }
  });
  inputEl.addEventListener("pointerup", (event) => {
    if (event.button === 1) {
      app$1.canvas.processMouseUp(event);
    }
  });
  inputEl.addEventListener("wheel", (event) => {
    const gesturesEnabled = useSettingStore().get(
      "LiteGraph.Pointer.TrackpadGestures"
    );
    const deltaX = event.deltaX;
    const deltaY = event.deltaY;
    const canScrollY = inputEl.scrollHeight > inputEl.clientHeight;
    const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
    if (event.ctrlKey) {
      event.preventDefault();
      event.stopPropagation();
      app$1.canvas.processMouseWheel(event);
      return;
    }
    const isLikelyTrackpad = Math.abs(deltaX) > 0 || Math.abs(deltaY) < TRACKPAD_DETECTION_THRESHOLD;
    if (gesturesEnabled && isLikelyTrackpad) {
      event.preventDefault();
      event.stopPropagation();
      app$1.canvas.processMouseWheel(event);
      return;
    }
    if (isHorizontal) {
      event.preventDefault();
      event.stopPropagation();
      app$1.canvas.processMouseWheel(event);
      return;
    }
    if (canScrollY) {
      event.stopPropagation();
      return;
    }
    event.preventDefault();
    app$1.canvas.processMouseWheel(event);
  });
  return widget;
}
__name(addMultilineWidget, "addMultilineWidget");
const useStringWidget = /* @__PURE__ */ __name(() => {
  const widgetConstructor = /* @__PURE__ */ __name((node2, inputSpec) => {
    if (!isStringInputSpec(inputSpec)) {
      throw new Error(`Invalid input data: ${inputSpec}`);
    }
    const defaultVal = inputSpec.default ?? "";
    const multiline = inputSpec.multiline;
    const widget = multiline ? addMultilineWidget(node2, inputSpec.name, {
      defaultVal,
      placeholder: inputSpec.placeholder
    }) : node2.addWidget("text", inputSpec.name, defaultVal, () => {
    }, {});
    if (typeof inputSpec.dynamicPrompts === "boolean") {
      widget.dynamicPrompts = inputSpec.dynamicPrompts;
    }
    return widget;
  }, "widgetConstructor");
  return widgetConstructor;
}, "useStringWidget");
const useTextareaWidget = /* @__PURE__ */ __name(() => {
  return (node2, inputSpec) => {
    const { name: name2, options = {} } = inputSpec;
    const widget = node2.addWidget(
      "textarea",
      name2,
      options.default || "",
      () => {
      },
      {
        serialize: true,
        rows: options.rows || 5,
        cols: options.cols || 50,
        ...options
      }
    );
    return widget;
  };
}, "useTextareaWidget");
const StringWidget = useStringWidget();
const FloatWidget = useFloatWidget();
const BooleanWidget = useBooleanWidget();
function addWidgetFromValue(node2, value) {
  let widget;
  if (typeof value === "string") {
    widget = StringWidget(node2, {
      type: "STRING",
      name: "UNKNOWN",
      multiline: value.length > 20
    });
  } else if (typeof value === "number") {
    widget = FloatWidget(node2, {
      type: "FLOAT",
      name: "UNKNOWN"
    });
  } else if (typeof value === "boolean") {
    widget = BooleanWidget(node2, {
      type: "BOOLEAN",
      name: "UNKNOWN"
    });
  } else {
    widget = StringWidget(node2, {
      type: "STRING",
      name: "UNKNOWN",
      multiline: true
    });
    widget.value = JSON.stringify(value);
    return;
  }
  widget.value = value;
}
__name(addWidgetFromValue, "addWidgetFromValue");
LGraphNode.prototype.onConfigure = useChainCallback(
  LGraphNode.prototype.onConfigure,
  function(info) {
    if (!this.has_errors || !info.widgets_values) return;
    for (let i = 0; i < info.widgets_values.length; i++) {
      const widgetValue = info.widgets_values[i];
      addWidgetFromValue(this, widgetValue);
    }
    this.serialize_widgets = true;
  }
);
const transformWidgetConstructorV2ToV1 = /* @__PURE__ */ __name((widgetConstructorV2) => {
  return (node2, inputName, inputData) => {
    const inputSpec = transformInputSpecV1ToV2(inputData, {
      name: inputName
    });
    const widget = widgetConstructorV2(node2, inputSpec);
    return {
      widget,
      minWidth: widget.options.minNodeSize?.[0],
      minHeight: widget.options.minNodeSize?.[1]
    };
  };
}, "transformWidgetConstructorV2ToV1");
function controlValueRunBefore() {
  return useSettingStore().get("Comfy.WidgetControlMode") === "before";
}
__name(controlValueRunBefore, "controlValueRunBefore");
function updateControlWidgetLabel(widget) {
  if (controlValueRunBefore()) {
    widget.label = t("g.control_before_generate");
  } else {
    widget.label = t("g.control_after_generate");
  }
}
__name(updateControlWidgetLabel, "updateControlWidgetLabel");
const IS_CONTROL_WIDGET = /* @__PURE__ */ Symbol();
const HAS_EXECUTED = /* @__PURE__ */ Symbol();
function addValueControlWidget(node2, targetWidget, defaultValue, _values, widgetName, inputData) {
  let name2 = inputData?.[1]?.control_after_generate;
  if (typeof name2 !== "string") {
    name2 = widgetName;
  }
  const widgets2 = addValueControlWidgets(
    node2,
    targetWidget,
    defaultValue ?? "randomize",
    {
      addFilterList: false,
      controlAfterGenerateName: name2
    },
    inputData
  );
  return widgets2[0];
}
__name(addValueControlWidget, "addValueControlWidget");
function addValueControlWidgets(node2, targetWidget, defaultValue, options, inputData) {
  if (!defaultValue) defaultValue = "randomize";
  if (!options) options = {};
  const getName = /* @__PURE__ */ __name((defaultName, optionName) => {
    let name2 = defaultName;
    if (options[optionName]) {
      name2 = options[optionName];
    } else if (typeof inputData?.[1]?.[defaultName] === "string") {
      name2 = inputData?.[1]?.[defaultName];
    } else if (inputData?.[1]?.control_prefix) {
      name2 = inputData?.[1]?.control_prefix + " " + name2;
    }
    return name2;
  }, "getName");
  const valueControl = node2.addWidget(
    "combo",
    getName("control_after_generate", "controlAfterGenerateName"),
    defaultValue,
    function() {
    },
    {
      values: ["fixed", "increment", "decrement", "randomize"],
      serialize: false,
      // Don't include this in prompt.
      canvasOnly: true
    }
  );
  valueControl.tooltip = "Allows the linked widget to be changed automatically, for example randomizing the noise seed.";
  valueControl[IS_CONTROL_WIDGET] = true;
  updateControlWidgetLabel(valueControl);
  const widgets2 = [valueControl];
  const isCombo = isComboWidget(targetWidget);
  let comboFilter;
  if (isCombo && valueControl.options.values) {
    valueControl.options.values.push("increment-wrap");
  }
  if (isCombo && options.addFilterList !== false) {
    comboFilter = node2.addWidget(
      "string",
      getName("control_filter_list", "controlFilterListName"),
      "",
      function() {
      },
      {
        serialize: false
        // Don't include this in prompt.
      }
    );
    updateControlWidgetLabel(comboFilter);
    comboFilter.tooltip = "Allows for filtering the list of values when changing the value via the control generate mode. Allows for RegEx matches in the format /abc/ to only filter to values containing 'abc'.";
    widgets2.push(comboFilter);
  }
  const applyWidgetControl = /* @__PURE__ */ __name(() => {
    var v2 = valueControl.value;
    if (isCombo && v2 !== "fixed") {
      let values = targetWidget.options.values ?? [];
      const filter2 = comboFilter?.value;
      if (filter2) {
        let check;
        if (filter2.startsWith("/") && filter2.endsWith("/")) {
          try {
            const regex = new RegExp(filter2.substring(1, filter2.length - 1));
            check = /* @__PURE__ */ __name((item) => regex.test(item), "check");
          } catch (error) {
            console.error(
              "Error constructing RegExp filter for node " + node2.id,
              filter2,
              error
            );
          }
        }
        if (!check) {
          const lower = filter2.toLocaleLowerCase();
          check = /* @__PURE__ */ __name((item) => item.toLocaleLowerCase().includes(lower), "check");
        }
        values = values.filter((item) => check(item));
        if (!values.length && targetWidget.options.values?.length) {
          console.warn(
            "Filter for node " + node2.id + " has filtered out all items",
            filter2
          );
        }
      }
      let current_index = values.indexOf(targetWidget.value);
      let current_length = values.length;
      switch (v2) {
        case "increment":
          current_index += 1;
          break;
        case "increment-wrap":
          current_index += 1;
          if (current_index >= current_length) {
            current_index = 0;
          }
          break;
        case "decrement":
          current_index -= 1;
          break;
        case "randomize":
          current_index = Math.floor(Math.random() * current_length);
          break;
      }
      current_index = Math.max(0, current_index);
      current_index = Math.min(current_length - 1, current_index);
      if (current_index >= 0) {
        let value = values[current_index];
        targetWidget.value = value;
        targetWidget.callback?.(value);
      }
    } else {
      let { min = 0, max = 1, step2 = 1 } = targetWidget.options;
      max = Math.min(1125899906842624, max);
      min = Math.max(-1125899906842624, min);
      let range = (max - min) / step2;
      switch (v2) {
        case "fixed":
          break;
        case "increment":
          targetWidget.value += step2;
          break;
        case "decrement":
          targetWidget.value -= step2;
          break;
        case "randomize":
          targetWidget.value = Math.floor(Math.random() * range) * step2 + min;
          break;
      }
      if (targetWidget.value < min) targetWidget.value = min;
      if (targetWidget.value > max) targetWidget.value = max;
      targetWidget.callback?.(targetWidget.value);
    }
  }, "applyWidgetControl");
  valueControl.beforeQueued = () => {
    if (controlValueRunBefore()) {
      if (valueControl[HAS_EXECUTED]) {
        applyWidgetControl();
      }
    }
    valueControl[HAS_EXECUTED] = true;
  };
  valueControl.afterQueued = () => {
    if (!controlValueRunBefore()) {
      applyWidgetControl();
    }
  };
  return widgets2;
}
__name(addValueControlWidgets, "addValueControlWidgets");
const ComfyWidgets = {
  INT: transformWidgetConstructorV2ToV1(useIntWidget()),
  FLOAT: transformWidgetConstructorV2ToV1(useFloatWidget()),
  BOOLEAN: transformWidgetConstructorV2ToV1(useBooleanWidget()),
  STRING: transformWidgetConstructorV2ToV1(useStringWidget()),
  MARKDOWN: transformWidgetConstructorV2ToV1(useMarkdownWidget()),
  COMBO: transformWidgetConstructorV2ToV1(useComboWidget()),
  IMAGEUPLOAD: useImageUploadWidget(),
  COLOR: transformWidgetConstructorV2ToV1(useColorWidget()),
  IMAGECOMPARE: transformWidgetConstructorV2ToV1(useImageCompareWidget()),
  CHART: transformWidgetConstructorV2ToV1(useChartWidget()),
  GALLERIA: transformWidgetConstructorV2ToV1(useGalleriaWidget()),
  TEXTAREA: transformWidgetConstructorV2ToV1(useTextareaWidget()),
  ...dynamicWidgets
};
function isValidWidgetType(key) {
  return ComfyWidgets[key] !== void 0;
}
__name(isValidWidgetType, "isValidWidgetType");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.widgets = window.comfyAPI.widgets || {};
window.comfyAPI.widgets.updateControlWidgetLabel = updateControlWidgetLabel;
window.comfyAPI.widgets.IS_CONTROL_WIDGET = IS_CONTROL_WIDGET;
window.comfyAPI.widgets.addValueControlWidget = addValueControlWidget;
window.comfyAPI.widgets.addValueControlWidgets = addValueControlWidgets;
window.comfyAPI.widgets.ComfyWidgets = ComfyWidgets;
window.comfyAPI.widgets.isValidWidgetType = isValidWidgetType;
const useWidgetStore = defineStore("widget", () => {
  const coreWidgets = ComfyWidgets;
  const customWidgets = ref(/* @__PURE__ */ new Map());
  const widgets2 = computed(
    () => new Map([...customWidgets.value, ...Object.entries(coreWidgets)])
  );
  function inputIsWidget(spec) {
    const type = Array.isArray(spec) ? getInputSpecType(spec) : spec.type;
    return widgets2.value.has(type);
  }
  __name(inputIsWidget, "inputIsWidget");
  function registerCustomWidgets(newWidgets) {
    for (const [type, widget] of Object.entries(newWidgets)) {
      customWidgets.value.set(type, widget);
    }
  }
  __name(registerCustomWidgets, "registerCustomWidgets");
  return {
    widgets: widgets2,
    inputIsWidget,
    registerCustomWidgets
  };
});
function useCommandSubcategories(commands2, config2) {
  const subcategories = computed(() => {
    const result = {};
    for (const command of commands2.value) {
      let subcategory = config2.defaultSubcategory;
      for (const rule of config2.rules) {
        const matches = typeof rule.pattern === "string" ? command.id.includes(rule.pattern) : rule.pattern.test(command.id);
        if (matches) {
          subcategory = rule.subcategory;
          break;
        }
      }
      if (!result[subcategory]) {
        result[subcategory] = [];
      }
      result[subcategory].push(command);
    }
    return result;
  });
  return {
    subcategories
  };
}
__name(useCommandSubcategories, "useCommandSubcategories");
const VIEW_CONTROLS_CONFIG = {
  defaultSubcategory: "view",
  rules: [
    { pattern: "Zoom", subcategory: "view" },
    { pattern: "Fit", subcategory: "view" },
    { pattern: "Panel", subcategory: "panel-controls" },
    { pattern: "Sidebar", subcategory: "panel-controls" }
  ]
};
const ESSENTIALS_CONFIG = {
  defaultSubcategory: "workflow",
  rules: [
    { pattern: "Workflow", subcategory: "workflow" },
    { pattern: "Node", subcategory: "node" },
    { pattern: "Queue", subcategory: "queue" }
  ]
};
const _hoisted_1$20 = { class: "shortcuts-list flex justify-center" };
const _hoisted_2$1z = { class: "grid h-full w-[90%] grid-cols-1 gap-4 md:grid-cols-3 md:gap-24" };
const _hoisted_3$11 = { class: "subcategory-title mb-4 text-xs font-bold tracking-wide text-text-secondary uppercase" };
const _hoisted_4$M = { class: "flex flex-col gap-1" };
const _hoisted_5$F = { class: "shortcut-info grow pr-4" };
const _hoisted_6$u = { class: "shortcut-name text-sm font-medium" };
const _hoisted_7$q = { class: "keybinding-display shrink-0" };
const _hoisted_8$k = ["aria-label"];
const _sfc_main$2r = /* @__PURE__ */ defineComponent({
  __name: "ShortcutsList",
  props: {
    subcategories: {}
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const filteredSubcategories = computed(() => {
      const result = {};
      for (const [subcategory, commands2] of Object.entries(__props.subcategories)) {
        result[subcategory] = commands2.filter((cmd) => !!cmd.keybinding);
      }
      return result;
    });
    const getSubcategoryTitle = /* @__PURE__ */ __name((subcategory) => {
      const titleMap = {
        workflow: t2("shortcuts.subcategories.workflow"),
        node: t2("shortcuts.subcategories.node"),
        queue: t2("shortcuts.subcategories.queue"),
        view: t2("shortcuts.subcategories.view"),
        "panel-controls": t2("shortcuts.subcategories.panelControls")
      };
      return titleMap[subcategory] || subcategory;
    }, "getSubcategoryTitle");
    const formatKey = /* @__PURE__ */ __name((key) => {
      const keyMap = {
        Control: "Ctrl",
        Meta: "Cmd",
        ArrowUp: "↑",
        ArrowDown: "↓",
        ArrowLeft: "←",
        ArrowRight: "→",
        Backspace: "⌫",
        Delete: "⌦",
        Enter: "↵",
        Escape: "Esc",
        Tab: "⇥",
        " ": "Space"
      };
      return keyMap[key] || key;
    }, "formatKey");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$20, [
        createBaseVNode("div", _hoisted_2$1z, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(filteredSubcategories.value, (subcategoryCommands, subcategory) => {
            return openBlock(), createElementBlock("div", {
              key: subcategory,
              class: "flex flex-col"
            }, [
              createBaseVNode("h3", _hoisted_3$11, toDisplayString(getSubcategoryTitle(subcategory)), 1),
              createBaseVNode("div", _hoisted_4$M, [
                (openBlock(true), createElementBlock(Fragment, null, renderList(subcategoryCommands, (command) => {
                  return openBlock(), createElementBlock("div", {
                    key: command.id,
                    class: "shortcut-item flex items-center justify-between rounded py-2 transition-colors duration-200"
                  }, [
                    createBaseVNode("div", _hoisted_5$F, [
                      createBaseVNode("div", _hoisted_6$u, toDisplayString(unref(t2)(`commands.${unref(normalizeI18nKey)(command.id)}.label`)), 1)
                    ]),
                    createBaseVNode("div", _hoisted_7$q, [
                      createBaseVNode("div", {
                        class: "keybinding-combo flex gap-1",
                        "aria-label": `Keyboard shortcut: ${command.keybinding.combo.getKeySequences().join(" + ")}`
                      }, [
                        (openBlock(true), createElementBlock(Fragment, null, renderList(command.keybinding.combo.getKeySequences(), (key) => {
                          return openBlock(), createElementBlock("span", {
                            key,
                            class: "key-badge min-w-6 rounded bg-muted-background px-2 py-1 text-center font-mono text-xs"
                          }, toDisplayString(formatKey(key)), 1);
                        }), 128))
                      ], 8, _hoisted_8$k)
                    ])
                  ]);
                }), 128))
              ])
            ]);
          }), 128))
        ])
      ]);
    };
  }
});
const _hoisted_1$1$ = { class: "flex h-full flex-col p-4" };
const _hoisted_2$1y = { class: "min-h-0 flex-1 overflow-auto" };
const _sfc_main$2q = /* @__PURE__ */ defineComponent({
  __name: "EssentialsPanel",
  setup(__props) {
    const commandStore = useCommandStore();
    const essentialsCommands = computed(
      () => commandStore.commands.filter((cmd) => cmd.category === "essentials")
    );
    const { subcategories: essentialsSubcategories } = useCommandSubcategories(
      essentialsCommands,
      ESSENTIALS_CONFIG
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1$, [
        createBaseVNode("div", _hoisted_2$1y, [
          createVNode(_sfc_main$2r, {
            commands: essentialsCommands.value,
            subcategories: unref(essentialsSubcategories)
          }, null, 8, ["commands", "subcategories"])
        ])
      ]);
    };
  }
});
const _hoisted_1$1_ = { class: "flex h-full flex-col p-4" };
const _hoisted_2$1x = { class: "min-h-0 flex-1 overflow-auto" };
const _sfc_main$2p = /* @__PURE__ */ defineComponent({
  __name: "ViewControlsPanel",
  setup(__props) {
    const commandStore = useCommandStore();
    const viewControlsCommands = computed(
      () => commandStore.commands.filter((cmd) => cmd.category === "view-controls")
    );
    const { subcategories: viewControlsSubcategories } = useCommandSubcategories(
      viewControlsCommands,
      VIEW_CONTROLS_CONFIG
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1_, [
        createBaseVNode("div", _hoisted_2$1x, [
          createVNode(_sfc_main$2r, {
            commands: viewControlsCommands.value,
            subcategories: unref(viewControlsSubcategories)
          }, null, 8, ["commands", "subcategories"])
        ])
      ]);
    };
  }
});
const useShortcutsTab = /* @__PURE__ */ __name(() => {
  const { t: t2 } = useI18n();
  return [
    {
      id: "shortcuts-essentials",
      title: t2("shortcuts.essentials"),
      // For command labels (collected by i18n workflow)
      titleKey: "shortcuts.essentials",
      // For dynamic translation in UI
      component: markRaw(_sfc_main$2q),
      type: "vue",
      targetPanel: "shortcuts"
    },
    {
      id: "shortcuts-view-controls",
      title: t2("shortcuts.viewControls"),
      // For command labels (collected by i18n workflow)
      titleKey: "shortcuts.viewControls",
      // For dynamic translation in UI
      component: markRaw(_sfc_main$2p),
      type: "vue",
      targetPanel: "shortcuts"
    }
  ];
}, "useShortcutsTab");
function useTerminal(element) {
  const fitAddon = new addonFitExports.FitAddon();
  const terminal = markRaw(
    new xtermExports.Terminal({
      convertEol: true,
      theme: isDesktop ? { background: "#171717" } : void 0
    })
  );
  terminal.loadAddon(fitAddon);
  terminal.attachCustomKeyEventHandler((event) => {
    if (event.type === "keydown" && (event.ctrlKey || event.metaKey) && (event.key === "c" && terminal.hasSelection() || event.key === "v")) {
      return false;
    }
    return true;
  });
  onMounted(async () => {
    if (element.value) {
      terminal.open(element.value);
    }
  });
  onUnmounted(() => {
    terminal.dispose();
  });
  return {
    terminal,
    useAutoSize({
      root,
      autoRows = true,
      autoCols = true,
      minCols = Number.NEGATIVE_INFINITY,
      minRows = Number.NEGATIVE_INFINITY,
      onResize
    }) {
      const ensureValidRows = /* @__PURE__ */ __name((rows) => {
        if (rows == null || isNaN(rows)) {
          return (root.value?.clientHeight ?? 80) / 20;
        }
        return rows;
      }, "ensureValidRows");
      const ensureValidCols = /* @__PURE__ */ __name((cols) => {
        if (cols == null || isNaN(cols)) {
          return (root.value?.clientWidth ?? 80) / 8;
        }
        return cols;
      }, "ensureValidCols");
      const resize = /* @__PURE__ */ __name(() => {
        const dims = fitAddon.proposeDimensions();
        terminal.resize(
          Math.max(
            autoCols ? ensureValidCols(dims?.cols) : terminal.cols,
            minCols
          ),
          Math.max(
            autoRows ? ensureValidRows(dims?.rows) : terminal.rows,
            minRows
          )
        );
        onResize?.();
      }, "resize");
      const resizeObserver = new ResizeObserver(debounce(resize, 25));
      onMounted(async () => {
        if (root.value) {
          resizeObserver.observe(root.value);
          resize();
        }
      });
      onUnmounted(() => {
        resizeObserver.disconnect();
      });
      return { resize };
    }
  };
}
__name(useTerminal, "useTerminal");
const _hoisted_1$1Z = { class: "p-terminal h-full w-full rounded-none p-2" };
const _sfc_main$2o = /* @__PURE__ */ defineComponent({
  __name: "BaseTerminal",
  emits: ["created", "unmounted"],
  setup(__props, { emit: __emit }) {
    const { t: t2 } = useI18n();
    const emit = __emit;
    const terminalEl = ref();
    const rootEl = ref();
    const hasSelection = ref(false);
    const isHovered = useElementHover(rootEl);
    const terminalData = useTerminal(terminalEl);
    emit("created", terminalData, ref(rootEl));
    const { terminal } = terminalData;
    let selectionDisposable;
    const tooltipText = computed(() => {
      return hasSelection.value ? t2("serverStart.copySelectionTooltip") : t2("serverStart.copyAllTooltip");
    });
    const handleCopy = /* @__PURE__ */ __name(async () => {
      const existingSelection = terminal.getSelection();
      const shouldSelectAll = !existingSelection;
      if (shouldSelectAll) terminal.selectAll();
      const selectedText = shouldSelectAll ? terminal.getSelection() : existingSelection;
      if (selectedText) {
        await navigator.clipboard.writeText(selectedText);
        if (shouldSelectAll) {
          terminal.clearSelection();
        }
      }
    }, "handleCopy");
    const showContextMenu = /* @__PURE__ */ __name((event) => {
      event.preventDefault();
      electronAPI()?.showContextMenu({ type: "text" });
    }, "showContextMenu");
    if (isElectron()) {
      useEventListener(terminalEl, "contextmenu", showContextMenu);
    }
    onMounted(() => {
      selectionDisposable = terminal.onSelectionChange(() => {
        hasSelection.value = terminal.hasSelection();
      });
    });
    onUnmounted(() => {
      selectionDisposable?.dispose();
      emit("unmounted");
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", {
        ref_key: "rootEl",
        ref: rootEl,
        class: "relative h-full w-full overflow-hidden bg-neutral-900"
      }, [
        createBaseVNode("div", _hoisted_1$1Z, [
          createBaseVNode("div", {
            ref_key: "terminalEl",
            ref: terminalEl,
            class: "terminal-host h-full"
          }, null, 512)
        ]),
        withDirectives((openBlock(), createBlock(_sfc_main$2A, {
          variant: "secondary",
          size: "sm",
          class: normalizeClass(
            unref(cn)("absolute top-2 right-8 transition-opacity", {
              "opacity-0 pointer-events-none select-none": !unref(isHovered)
            })
          ),
          "aria-label": tooltipText.value,
          onClick: handleCopy
        }, {
          default: withCtx(() => _cache[0] || (_cache[0] = [
            createBaseVNode("i", { class: "pi pi-copy" }, null, -1)
          ])),
          _: 1
        }, 8, ["class", "aria-label"])), [
          [
            _directive_tooltip,
            {
              value: tooltipText.value,
              showDelay: 300
            },
            void 0,
            { left: true }
          ]
        ])
      ], 512);
    };
  }
});
const BaseTerminal = /* @__PURE__ */ _export_sfc(_sfc_main$2o, [["__scopeId", "data-v-f7040d8a"]]);
const _sfc_main$2n = /* @__PURE__ */ defineComponent({
  __name: "CommandTerminal",
  setup(__props) {
    const terminalCreated = /* @__PURE__ */ __name(({ terminal, useAutoSize }, root) => {
      const terminalApi = electronAPI().Terminal;
      let offData;
      let offOutput;
      useAutoSize({
        root,
        autoRows: true,
        autoCols: true,
        onResize: /* @__PURE__ */ __name(async () => {
          if (!terminal.element?.offsetParent) return;
          await terminalApi.resize(terminal.cols, terminal.rows);
        }, "onResize")
      });
      onMounted(async () => {
        offData = terminal.onData(async (message) => {
          await terminalApi.write(message);
        });
        offOutput = terminalApi.onOutput((message) => {
          terminal.write(message);
        });
        const restore = await terminalApi.restore();
        setTimeout(() => {
          if (restore.buffer.length) {
            terminal.resize(restore.size.cols, restore.size.rows);
            terminal.write(restore.buffer.join(""));
          }
        }, 500);
      });
      onUnmounted(() => {
        offData?.dispose();
        offOutput?.();
      });
    }, "terminalCreated");
    return (_ctx, _cache) => {
      return openBlock(), createBlock(BaseTerminal, { onCreated: terminalCreated });
    };
  }
});
const CommandTerminal = /* @__PURE__ */ _export_sfc(_sfc_main$2n, [["__scopeId", "data-v-49782f06"]]);
const _hoisted_1$1Y = { class: "h-full w-full bg-transparent" };
const _hoisted_2$1w = {
  key: 0,
  class: "p-4 text-center"
};
const _sfc_main$2m = /* @__PURE__ */ defineComponent({
  __name: "LogsTerminal",
  setup(__props) {
    const errorMessage = ref("");
    const loading = ref(true);
    const terminalCreated = /* @__PURE__ */ __name(({ terminal, useAutoSize }, root) => {
      useAutoSize({ root, autoRows: true, autoCols: true, minCols: 80 });
      const update = /* @__PURE__ */ __name((entries) => {
        terminal.write(entries.map((e2) => e2.m).join(""));
      }, "update");
      const logReceived = /* @__PURE__ */ __name((e2) => {
        update(e2.detail.entries);
      }, "logReceived");
      const loadLogEntries = /* @__PURE__ */ __name(async () => {
        const logs = await api.getRawLogs();
        update(logs.entries);
      }, "loadLogEntries");
      const watchLogs = /* @__PURE__ */ __name(async () => {
        const { clientId } = storeToRefs(useExecutionStore());
        if (!clientId.value) {
          await until(clientId).not.toBeNull();
        }
        await api.subscribeLogs(true);
        api.addEventListener("logs", logReceived);
      }, "watchLogs");
      onMounted(async () => {
        try {
          await loadLogEntries();
        } catch (err) {
          console.error("Error loading logs", err);
          errorMessage.value = "Unable to load logs, please ensure you have updated your ComfyUI backend.";
          return;
        }
        await watchLogs();
        loading.value = false;
      });
      onUnmounted(async () => {
        if (api.clientId) {
          await api.subscribeLogs(false);
        }
        api.removeEventListener("logs", logReceived);
      });
    }, "terminalCreated");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1Y, [
        errorMessage.value ? (openBlock(), createElementBlock("p", _hoisted_2$1w, toDisplayString(errorMessage.value), 1)) : loading.value ? (openBlock(), createBlock(unref(script$7), {
          key: 1,
          class: "relative inset-0 z-10 flex h-full items-center justify-center"
        })) : createCommentVNode("", true),
        withDirectives(createVNode(BaseTerminal, { onCreated: terminalCreated }, null, 512), [
          [vShow, !loading.value]
        ])
      ]);
    };
  }
});
const LogsTerminal = /* @__PURE__ */ _export_sfc(_sfc_main$2m, [["__scopeId", "data-v-c03d3536"]]);
const useLogsTerminalTab = /* @__PURE__ */ __name(() => {
  const { t: t2 } = useI18n();
  return {
    id: "logs-terminal",
    title: t2("g.logs"),
    // For command labels (collected by i18n workflow)
    titleKey: "g.logs",
    // For dynamic translation in UI
    component: markRaw(LogsTerminal),
    type: "vue"
  };
}, "useLogsTerminalTab");
const useCommandTerminalTab = /* @__PURE__ */ __name(() => {
  const { t: t2 } = useI18n();
  return {
    id: "command-terminal",
    title: t2("g.terminal"),
    // For command labels (collected by i18n workflow)
    titleKey: "g.terminal",
    // For dynamic translation in UI
    component: markRaw(CommandTerminal),
    type: "vue"
  };
}, "useCommandTerminalTab");
const useBottomPanelStore = defineStore("bottomPanel", () => {
  const panels = ref({
    terminal: { tabs: [], activeTabId: "", visible: false },
    shortcuts: { tabs: [], activeTabId: "", visible: false }
  });
  const activePanel = ref(null);
  const activePanelState = computed(
    () => activePanel.value ? panels.value[activePanel.value] : null
  );
  const activeBottomPanelTab = computed(() => {
    const state2 = activePanelState.value;
    if (!state2) return null;
    return state2.tabs.find((tab) => tab.id === state2.activeTabId) ?? null;
  });
  const bottomPanelVisible = computed({
    get: /* @__PURE__ */ __name(() => !!activePanel.value, "get"),
    set: /* @__PURE__ */ __name((visible) => {
      if (!visible) {
        activePanel.value = null;
      }
    }, "set")
  });
  const bottomPanelTabs = computed(() => activePanelState.value?.tabs ?? []);
  const activeBottomPanelTabId = computed({
    get: /* @__PURE__ */ __name(() => activePanelState.value?.activeTabId ?? "", "get"),
    set: /* @__PURE__ */ __name((tabId) => {
      const state2 = activePanelState.value;
      if (state2) {
        state2.activeTabId = tabId;
      }
    }, "set")
  });
  const togglePanel = /* @__PURE__ */ __name((panelType) => {
    const panel = panels.value[panelType];
    if (panel.tabs.length === 0) return;
    if (activePanel.value === panelType) {
      activePanel.value = null;
    } else {
      activePanel.value = panelType;
      if (!panel.activeTabId && panel.tabs.length > 0) {
        panel.activeTabId = panel.tabs[0].id;
      }
    }
  }, "togglePanel");
  const toggleBottomPanel = /* @__PURE__ */ __name(() => {
    togglePanel("terminal");
  }, "toggleBottomPanel");
  const setActiveTab = /* @__PURE__ */ __name((tabId) => {
    const state2 = activePanelState.value;
    if (state2) {
      state2.activeTabId = tabId;
    }
  }, "setActiveTab");
  const toggleBottomPanelTab = /* @__PURE__ */ __name((tabId) => {
    for (const [panelType, panel] of Object.entries(panels.value)) {
      const tab = panel.tabs.find((t2) => t2.id === tabId);
      if (tab) {
        if (activePanel.value === panelType && panel.activeTabId === tabId) {
          activePanel.value = null;
        } else {
          activePanel.value = panelType;
          panel.activeTabId = tabId;
        }
        return;
      }
    }
  }, "toggleBottomPanelTab");
  const registerBottomPanelTab = /* @__PURE__ */ __name((tab) => {
    const targetPanel = tab.targetPanel ?? "terminal";
    const panel = panels.value[targetPanel];
    panel.tabs = [...panel.tabs, tab];
    if (panel.tabs.length === 1) {
      panel.activeTabId = tab.id;
    }
    const tabName = tab.title || tab.titleKey || tab.id;
    useCommandStore().registerCommand({
      id: `Workspace.ToggleBottomPanelTab.${tab.id}`,
      icon: "pi pi-list",
      label: `Toggle ${tabName} Bottom Panel`,
      category: "view-controls",
      function: /* @__PURE__ */ __name(() => toggleBottomPanelTab(tab.id), "function"),
      source: "System"
    });
  }, "registerBottomPanelTab");
  const registerCoreBottomPanelTabs = /* @__PURE__ */ __name(() => {
    registerBottomPanelTab(useLogsTerminalTab());
    if (isElectron()) {
      registerBottomPanelTab(useCommandTerminalTab());
    }
    useShortcutsTab().forEach(registerBottomPanelTab);
  }, "registerCoreBottomPanelTabs");
  const registerExtensionBottomPanelTabs = /* @__PURE__ */ __name((extension) => {
    if (extension.bottomPanelTabs) {
      extension.bottomPanelTabs.forEach(registerBottomPanelTab);
    }
  }, "registerExtensionBottomPanelTabs");
  return {
    // Multi-panel API
    panels,
    activePanel,
    togglePanel,
    bottomPanelVisible,
    toggleBottomPanel,
    bottomPanelTabs,
    activeBottomPanelTab,
    activeBottomPanelTabId,
    setActiveTab,
    toggleBottomPanelTab,
    registerBottomPanelTab,
    registerCoreBottomPanelTabs,
    registerExtensionBottomPanelTabs
  };
});
const useExtensionService = /* @__PURE__ */ __name(() => {
  const extensionStore = useExtensionStore();
  const settingStore = useSettingStore();
  const keybindingStore = useKeybindingStore();
  const {
    wrapWithErrorHandling,
    wrapWithErrorHandlingAsync,
    toastErrorHandler
  } = useErrorHandling();
  const loadExtensions = /* @__PURE__ */ __name(async () => {
    extensionStore.loadDisabledExtensionNames(
      settingStore.get("Comfy.Extension.Disabled")
    );
    const extensions = await api.getExtensions();
    await __vitePreload(() => import("./index-B6o-AXrW.js"), true ? __vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11,12,13]) : void 0, import.meta.url);
    extensionStore.captureCoreExtensions();
    await Promise.all(
      extensions.filter((extension) => !extension.includes("extensions/core")).map(async (ext) => {
        try {
          await import(
            /* @vite-ignore */
            api.fileURL(ext)
          );
        } catch (error) {
          console.error("Error loading extension", ext, error);
        }
      })
    );
  }, "loadExtensions");
  const registerExtension = /* @__PURE__ */ __name((extension) => {
    extensionStore.registerExtension(extension);
    const addKeybinding = wrapWithErrorHandling(
      keybindingStore.addDefaultKeybinding
    );
    const addSetting = wrapWithErrorHandling(settingStore.addSetting);
    extension.keybindings?.forEach((keybinding) => {
      addKeybinding(new KeybindingImpl(keybinding));
    });
    useCommandStore().loadExtensionCommands(extension);
    useMenuItemStore().loadExtensionMenuCommands(extension);
    extension.settings?.forEach(addSetting);
    useBottomPanelStore().registerExtensionBottomPanelTabs(extension);
    if (extension.getCustomWidgets) {
      void (async () => {
        if (extension.getCustomWidgets) {
          const widgets2 = await extension.getCustomWidgets(app$1);
          useWidgetStore().registerCustomWidgets(widgets2);
        }
      })();
    }
    if (extension.onAuthUserResolved) {
      const { onUserResolved } = useCurrentUser();
      const handleUserResolved = wrapWithErrorHandlingAsync(
        (user) => extension.onAuthUserResolved?.(user, app$1),
        (error) => {
          console.error("[Extension Auth Hook Error]", {
            extension: extension.name,
            hook: "onAuthUserResolved",
            error
          });
          toastErrorHandler(error);
        }
      );
      onUserResolved((user) => {
        void handleUserResolved(user);
      });
    }
    if (extension.onAuthTokenRefreshed) {
      const { onTokenRefreshed } = useCurrentUser();
      const handleTokenRefreshed = wrapWithErrorHandlingAsync(
        () => extension.onAuthTokenRefreshed?.(),
        (error) => {
          console.error("[Extension Auth Hook Error]", {
            extension: extension.name,
            hook: "onAuthTokenRefreshed",
            error
          });
          toastErrorHandler(error);
        }
      );
      onTokenRefreshed(() => {
        void handleTokenRefreshed();
      });
    }
    if (extension.onAuthUserLogout) {
      const { onUserLogout } = useCurrentUser();
      const handleUserLogout = wrapWithErrorHandlingAsync(
        () => extension.onAuthUserLogout?.(),
        (error) => {
          console.error("[Extension Auth Hook Error]", {
            extension: extension.name,
            hook: "onAuthUserLogout",
            error
          });
          toastErrorHandler(error);
        }
      );
      onUserLogout(() => {
        void handleUserLogout();
      });
    }
  }, "registerExtension");
  const invokeExtensions = /* @__PURE__ */ __name((method, ...args) => {
    const results = [];
    for (const ext of extensionStore.enabledExtensions) {
      if (method in ext) {
        try {
          results.push(ext[method](...args, app$1));
        } catch (error) {
          console.error(
            `Error calling extension '${ext.name}' method '${method}'`,
            { error },
            { extension: ext },
            { args }
          );
        }
      }
    }
    return results;
  }, "invokeExtensions");
  const invokeExtensionsAsync = /* @__PURE__ */ __name(async (method, ...args) => {
    return await Promise.all(
      extensionStore.enabledExtensions.map(async (ext) => {
        if (method in ext) {
          try {
            if (method === "setup") {
              legacyMenuCompat.setCurrentExtension(ext.name);
            }
            const result = await ext[method](...args, app$1);
            if (method === "setup") {
              legacyMenuCompat.setCurrentExtension(null);
            }
            return result;
          } catch (error) {
            if (method === "setup") {
              legacyMenuCompat.setCurrentExtension(null);
            }
            console.error(
              `Error calling extension '${ext.name}' method '${method}'`,
              { error },
              { extension: ext },
              { args }
            );
          }
        }
      })
    );
  }, "invokeExtensionsAsync");
  return {
    loadExtensions,
    registerExtension,
    invokeExtensions,
    invokeExtensionsAsync
  };
}, "useExtensionService");
class ResultItemImpl {
  static {
    __name(this, "ResultItemImpl");
  }
  filename;
  subfolder;
  type;
  nodeId;
  // 'audio' | 'images' | ...
  mediaType;
  // VHS output specific fields
  format;
  frame_rate;
  constructor(obj) {
    this.filename = obj.filename ?? "";
    this.subfolder = obj.subfolder ?? "";
    this.type = obj.type ?? "";
    this.nodeId = obj.nodeId;
    this.mediaType = obj.mediaType;
    this.format = obj.format;
    this.frame_rate = obj.frame_rate;
  }
  get urlParams() {
    const params = new URLSearchParams();
    params.set("filename", this.filename);
    params.set("type", this.type);
    params.set("subfolder", this.subfolder);
    if (this.format) {
      params.set("format", this.format);
    }
    if (this.frame_rate) {
      params.set("frame_rate", this.frame_rate.toString());
    }
    return params;
  }
  /**
   * VHS advanced preview URL. `/viewvideo` endpoint is provided by VHS node.
   *
   * `/viewvideo` always returns a webm file.
   */
  get vhsAdvancedPreviewUrl() {
    return api.apiURL("/viewvideo?" + this.urlParams);
  }
  get url() {
    return api.apiURL("/view?" + this.urlParams);
  }
  get urlWithTimestamp() {
    return `${this.url}&t=${+/* @__PURE__ */ new Date()}`;
  }
  get isVhsFormat() {
    return !!this.format && !!this.frame_rate;
  }
  get htmlVideoType() {
    if (this.isWebm) {
      return "video/webm";
    }
    if (this.isMp4) {
      return "video/mp4";
    }
    if (this.isVhsFormat) {
      if (this.format?.endsWith("webm")) {
        return "video/webm";
      }
      if (this.format?.endsWith("mp4")) {
        return "video/mp4";
      }
    }
    return void 0;
  }
  get htmlAudioType() {
    if (this.isMp3) {
      return "audio/mpeg";
    }
    if (this.isWav) {
      return "audio/wav";
    }
    if (this.isOgg) {
      return "audio/ogg";
    }
    if (this.isFlac) {
      return "audio/flac";
    }
    return void 0;
  }
  get isGif() {
    return this.filename.endsWith(".gif");
  }
  get isWebp() {
    return this.filename.endsWith(".webp");
  }
  get isWebm() {
    return this.filename.endsWith(".webm");
  }
  get isMp4() {
    return this.filename.endsWith(".mp4");
  }
  get isVideoBySuffix() {
    return this.isWebm || this.isMp4;
  }
  get isImageBySuffix() {
    return this.isGif || this.isWebp;
  }
  get isMp3() {
    return this.filename.endsWith(".mp3");
  }
  get isWav() {
    return this.filename.endsWith(".wav");
  }
  get isOgg() {
    return this.filename.endsWith(".ogg");
  }
  get isFlac() {
    return this.filename.endsWith(".flac");
  }
  get isAudioBySuffix() {
    return this.isMp3 || this.isWav || this.isOgg || this.isFlac;
  }
  get isVideo() {
    const isVideoByType = this.mediaType === "video" || !!this.format?.startsWith("video/");
    return this.isVideoBySuffix || isVideoByType && !this.isImageBySuffix && !this.isAudioBySuffix;
  }
  get isImage() {
    return this.isImageBySuffix || this.mediaType === "images" && !this.isVideoBySuffix && !this.isAudioBySuffix;
  }
  get isAudio() {
    const isAudioByType = this.mediaType === "audio" || !!this.format?.startsWith("audio/");
    return this.isAudioBySuffix || isAudioByType && !this.isImageBySuffix && !this.isVideoBySuffix;
  }
  get is3D() {
    return getMediaTypeFromFilename(this.filename) === "3D";
  }
  get supportsPreview() {
    return this.isImage || this.isVideo || this.isAudio || this.is3D;
  }
}
class TaskItemImpl {
  static {
    __name(this, "TaskItemImpl");
  }
  taskType;
  prompt;
  status;
  outputs;
  flatOutputs;
  constructor(taskType, prompt, status, outputs, flatOutputs) {
    this.taskType = taskType;
    this.prompt = prompt;
    this.status = status;
    this.outputs = toolkit.mapValues(
      outputs ?? {},
      (nodeOutputs) => toolkit.omit(nodeOutputs, "animated")
    );
    this.flatOutputs = flatOutputs ?? this.calculateFlatOutputs();
  }
  calculateFlatOutputs() {
    if (!this.outputs) {
      return [];
    }
    return Object.entries(this.outputs).flatMap(
      ([nodeId, nodeOutputs]) => Object.entries(nodeOutputs).flatMap(
        ([mediaType, items]) => items.map(
          (item) => new ResultItemImpl({
            ...item,
            nodeId,
            mediaType
          })
        )
      )
    );
  }
  get previewOutput() {
    return this.flatOutputs.find(
      // Prefer saved media files over the temp previews
      (output) => output.type === "output" && output.supportsPreview
    ) ?? this.flatOutputs.find((output) => output.supportsPreview);
  }
  get apiTaskType() {
    switch (this.taskType) {
      case "Running":
      case "Pending":
        return "queue";
      case "History":
        return "history";
    }
  }
  get key() {
    return this.promptId + this.displayStatus;
  }
  get queueIndex() {
    return this.prompt[0];
  }
  get promptId() {
    return this.prompt[1];
  }
  get promptInputs() {
    return this.prompt[2];
  }
  get extraData() {
    return this.prompt[3];
  }
  get outputsToExecute() {
    return this.prompt[4];
  }
  get extraPngInfo() {
    return this.extraData.extra_pnginfo;
  }
  get clientId() {
    return this.extraData.client_id;
  }
  get workflow() {
    return this.extraPngInfo?.workflow;
  }
  get messages() {
    return this.status?.messages || [];
  }
  /**
   * Server-provided creation time in milliseconds, when available.
   *
   * Sources:
   * - Queue: 5th tuple element may be a metadata object with { create_time }.
   * - History (Cloud V2): Adapter injects create_time into prompt[3].extra_data.
   */
  get createTime() {
    const extra = this.extraData || {};
    const fromExtra = typeof extra.create_time === "number" ? extra.create_time : void 0;
    if (typeof fromExtra === "number") return fromExtra;
    return void 0;
  }
  get interrupted() {
    return toolkit.some(
      this.messages,
      (message) => message[0] === "execution_interrupted"
    );
  }
  get isHistory() {
    return this.taskType === "History";
  }
  get isRunning() {
    return this.taskType === "Running";
  }
  get displayStatus() {
    switch (this.taskType) {
      case "Running":
        return "Running";
      case "Pending":
        return "Pending";
      case "History":
        if (this.interrupted) return "Cancelled";
        switch (this.status.status_str) {
          case "success":
            return "Completed";
          case "error":
            return "Failed";
        }
    }
  }
  get executionStartTimestamp() {
    const message = this.messages.find(
      (message2) => message2[0] === "execution_start"
    );
    return message ? message[1].timestamp : void 0;
  }
  get executionEndTimestamp() {
    const messages2 = this.messages.filter(
      (message) => [
        "execution_success",
        "execution_interrupted",
        "execution_error"
      ].includes(message[0])
    );
    if (!messages2.length) {
      return void 0;
    }
    return toolkit.max(messages2.map((message) => message[1].timestamp));
  }
  get executionTime() {
    if (!this.executionStartTimestamp || !this.executionEndTimestamp) {
      return void 0;
    }
    return this.executionEndTimestamp - this.executionStartTimestamp;
  }
  get executionTimeInSeconds() {
    return this.executionTime !== void 0 ? this.executionTime / 1e3 : void 0;
  }
  async loadWorkflow(app2) {
    let workflowData = this.workflow;
    if (!workflowData) {
      return;
    }
    await app2.loadGraphData(toRaw(workflowData));
    if (!this.outputs) {
      return;
    }
    const nodeOutputsStore = useNodeOutputStore();
    const rawOutputs = toRaw(this.outputs);
    for (const nodeExecutionId in rawOutputs) {
      nodeOutputsStore.setNodeOutputsByExecutionId(
        nodeExecutionId,
        rawOutputs[nodeExecutionId]
      );
    }
    useExtensionService().invokeExtensions(
      "onNodeOutputsUpdated",
      app2.nodeOutputs
    );
  }
  flatten() {
    if (this.displayStatus !== "Completed") {
      return [this];
    }
    return this.flatOutputs.map(
      (output, i) => new TaskItemImpl(
        this.taskType,
        [
          this.queueIndex,
          `${this.promptId}-${i}`,
          this.promptInputs,
          this.extraData,
          this.outputsToExecute
        ],
        this.status,
        {
          [output.nodeId]: {
            [output.mediaType]: [output]
          }
        },
        [output]
      )
    );
  }
  toTaskItem() {
    const item = {
      taskType: "History",
      prompt: this.prompt,
      status: this.status,
      outputs: this.outputs
    };
    return item;
  }
}
const sortNewestFirst = /* @__PURE__ */ __name((a, b) => b.queueIndex - a.queueIndex, "sortNewestFirst");
const toTaskItemImpls = /* @__PURE__ */ __name((tasks) => tasks.map(
  (task) => new TaskItemImpl(
    task.taskType,
    task.prompt,
    "status" in task ? task.status : void 0,
    "outputs" in task ? task.outputs : void 0
  )
), "toTaskItemImpls");
const useQueueStore = defineStore("queue", () => {
  const runningTasks = shallowRef([]);
  const pendingTasks = shallowRef([]);
  const historyTasks = shallowRef([]);
  const maxHistoryItems = ref(64);
  const isLoading = ref(false);
  const tasks = computed(
    () => [
      ...pendingTasks.value,
      ...runningTasks.value,
      ...historyTasks.value
    ]
  );
  const flatTasks = computed(
    () => tasks.value.flatMap((task) => task.flatten())
  );
  const lastHistoryQueueIndex = computed(
    () => historyTasks.value.length ? historyTasks.value[0].queueIndex : -1
  );
  const hasPendingTasks = computed(() => pendingTasks.value.length > 0);
  const update = /* @__PURE__ */ __name(async () => {
    isLoading.value = true;
    try {
      const [queue2, history] = await Promise.all([
        api.getQueue(),
        api.getHistory(maxHistoryItems.value)
      ]);
      runningTasks.value = toTaskItemImpls(queue2.Running).sort(sortNewestFirst);
      pendingTasks.value = toTaskItemImpls(queue2.Pending).sort(sortNewestFirst);
      const currentHistory = toValue(historyTasks);
      const appearedTasks = [...pendingTasks.value, ...runningTasks.value];
      const executionStore = useExecutionStore();
      appearedTasks.forEach((task) => {
        const promptIdString = String(task.promptId);
        const workflowId = task.workflow?.id;
        if (workflowId && promptIdString) {
          executionStore.registerPromptWorkflowIdMapping(
            promptIdString,
            workflowId
          );
        }
      });
      const items = reconcileHistory(
        history.History,
        currentHistory.map((impl) => impl.toTaskItem()),
        toValue(maxHistoryItems),
        toValue(lastHistoryQueueIndex)
      );
      const existingByPromptId = new Map(
        currentHistory.map((impl) => [impl.promptId, impl])
      );
      historyTasks.value = items.map(
        (item) => existingByPromptId.get(item.prompt[1]) ?? toTaskItemImpls([item])[0]
      );
    } finally {
      isLoading.value = false;
    }
  }, "update");
  const clear = /* @__PURE__ */ __name(async (targets = ["queue", "history"]) => {
    if (targets.length === 0) {
      return;
    }
    await Promise.all(targets.map((type) => api.clearItems(type)));
    await update();
  }, "clear");
  const deleteTask = /* @__PURE__ */ __name(async (task) => {
    await api.deleteItem(task.apiTaskType, task.promptId);
    await update();
  }, "deleteTask");
  return {
    runningTasks,
    pendingTasks,
    historyTasks,
    maxHistoryItems,
    isLoading,
    tasks,
    flatTasks,
    lastHistoryQueueIndex,
    hasPendingTasks,
    update,
    clear,
    delete: deleteTask
  };
});
const useQueuePendingTaskCountStore = defineStore(
  "queuePendingTaskCount",
  {
    state: /* @__PURE__ */ __name(() => ({
      count: 0
    }), "state"),
    actions: {
      update(e2) {
        this.count = e2.detail?.exec_info?.queue_remaining || 0;
      }
    }
  }
);
const useQueueSettingsStore = defineStore("queueSettingsStore", {
  state: /* @__PURE__ */ __name(() => ({
    mode: "disabled",
    batchCount: 1
  }), "state")
});
const _hoisted_1$1X = { class: "flex flex-col items-center" };
const _sfc_main$2l = /* @__PURE__ */ defineComponent({
  __name: "NoResultsPlaceholder",
  props: {
    class: {},
    icon: {},
    title: {},
    message: {},
    textClass: {},
    buttonLabel: {}
  },
  emits: ["action"],
  setup(__props) {
    const props = __props;
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(["no-results-placeholder h-full p-8", props.class])
      }, [
        createVNode(unref(script$8), null, {
          content: withCtx(() => [
            createBaseVNode("div", _hoisted_1$1X, [
              createBaseVNode("i", {
                class: normalizeClass(_ctx.icon),
                style: { "font-size": "3rem", "margin-bottom": "1rem" }
              }, null, 2),
              createBaseVNode("h3", null, toDisplayString(_ctx.title), 1),
              createBaseVNode("p", {
                class: normalizeClass([_ctx.textClass, "text-center whitespace-pre-line"])
              }, toDisplayString(_ctx.message), 3),
              _ctx.buttonLabel ? (openBlock(), createBlock(_sfc_main$2A, {
                key: 0,
                variant: "textonly",
                onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("action"))
              }, {
                default: withCtx(() => [
                  createTextVNode(toDisplayString(_ctx.buttonLabel), 1)
                ]),
                _: 1
              })) : createCommentVNode("", true)
            ])
          ]),
          _: 1
        })
      ], 2);
    };
  }
});
const NoResultsPlaceholder = /* @__PURE__ */ _export_sfc(_sfc_main$2l, [["__scopeId", "data-v-f45f4900"]]);
const _sfc_main$2k = /* @__PURE__ */ defineComponent({
  __name: "VirtualGrid",
  props: {
    items: {},
    gridStyle: {},
    bufferRows: { default: 1 },
    scrollThrottle: { default: 64 },
    resizeDebounce: { default: 64 },
    defaultItemHeight: { default: 200 },
    defaultItemWidth: { default: 200 }
  },
  emits: ["approach-end"],
  setup(__props, { emit: __emit }) {
    const emit = __emit;
    const itemHeight = ref(__props.defaultItemHeight);
    const itemWidth = ref(__props.defaultItemWidth);
    const container = ref(null);
    const { width: width2, height } = useElementSize(container);
    const { y: scrollY } = useScroll(container, {
      throttle: __props.scrollThrottle,
      eventListenerOptions: { passive: true }
    });
    const cols = computed(() => Math.floor(width2.value / itemWidth.value) || 1);
    const viewRows = computed(() => Math.ceil(height.value / itemHeight.value));
    const offsetRows = computed(() => Math.floor(scrollY.value / itemHeight.value));
    const isValidGrid = computed(() => height.value && width2.value && __props.items?.length);
    const state2 = computed(() => {
      const fromRow = offsetRows.value - __props.bufferRows;
      const toRow = offsetRows.value + __props.bufferRows + viewRows.value;
      const fromCol = fromRow * cols.value;
      const toCol = toRow * cols.value;
      const remainingCol = __props.items.length - toCol;
      const hasMoreToRender = remainingCol >= 0;
      return {
        start: clamp(fromCol, 0, __props.items?.length),
        end: clamp(toCol, fromCol, __props.items?.length),
        isNearEnd: hasMoreToRender && remainingCol <= cols.value * __props.bufferRows
      };
    });
    const renderedItems = computed(
      () => isValidGrid.value ? __props.items.slice(state2.value.start, state2.value.end) : []
    );
    whenever(
      () => state2.value.isNearEnd,
      () => {
        emit("approach-end");
      }
    );
    const updateItemSize = /* @__PURE__ */ __name(() => {
      if (container.value) {
        const firstItem = container.value.querySelector("[data-virtual-grid-item]");
        if (!firstItem?.clientHeight || !firstItem?.clientWidth) return;
        if (itemHeight.value !== firstItem.clientHeight) {
          itemHeight.value = firstItem.clientHeight;
        }
        if (itemWidth.value !== firstItem.clientWidth) {
          itemWidth.value = firstItem.clientWidth;
        }
      }
    }, "updateItemSize");
    const onResize = debounce(updateItemSize, __props.resizeDebounce);
    watch([width2, height], onResize, { flush: "post" });
    whenever(() => __props.items, updateItemSize, { flush: "post" });
    onBeforeUnmount(() => {
      onResize.cancel();
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        ref_key: "container",
        ref: container,
        class: "scroll-container"
      }, [
        createBaseVNode("div", {
          style: normalizeStyle({ height: `${state2.value.start / cols.value * itemHeight.value}px` })
        }, null, 4),
        createBaseVNode("div", {
          style: normalizeStyle(_ctx.gridStyle)
        }, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(renderedItems.value, (item) => {
            return openBlock(), createElementBlock("div", {
              key: item.key,
              "data-virtual-grid-item": ""
            }, [
              renderSlot(_ctx.$slots, "item", { item }, void 0, true)
            ]);
          }), 128))
        ], 4),
        createBaseVNode("div", {
          style: normalizeStyle({
            height: `${(_ctx.items.length - state2.value.end) / cols.value * itemHeight.value}px`
          })
        }, null, 4)
      ], 512);
    };
  }
});
const VirtualGrid = /* @__PURE__ */ _export_sfc(_sfc_main$2k, [["__scopeId", "data-v-c3b01db5"]]);
const _hoisted_1$1W = { class: "space-y-4" };
const _hoisted_2$1v = { class: "space-y-4" };
const _hoisted_3$10 = {
  key: 0,
  class: "space-y-4"
};
const _sfc_main$2j = /* @__PURE__ */ defineComponent({
  __name: "ViewerCameraControls",
  props: {
    "cameraType": {},
    "cameraTypeModifiers": {},
    "fov": {},
    "fovModifiers": {}
  },
  emits: ["update:cameraType", "update:fov"],
  setup(__props) {
    const cameras = [
      { title: t("load3d.cameraType.perspective"), value: "perspective" },
      { title: t("load3d.cameraType.orthographic"), value: "orthographic" }
    ];
    const cameraType = useModel(__props, "cameraType");
    const fov = useModel(__props, "fov");
    const showFOVButton = computed(() => cameraType.value === "perspective");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1W, [
        createBaseVNode("div", _hoisted_2$1v, [
          createBaseVNode("label", null, toDisplayString(unref(t)("load3d.viewer.cameraType")), 1),
          createVNode(unref(script$9), {
            modelValue: cameraType.value,
            "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => cameraType.value = $event),
            options: cameras,
            "option-label": "title",
            "option-value": "value"
          }, null, 8, ["modelValue"])
        ]),
        showFOVButton.value ? (openBlock(), createElementBlock("div", _hoisted_3$10, [
          createBaseVNode("label", null, toDisplayString(unref(t)("load3d.fov")), 1),
          createVNode(unref(script$a), {
            modelValue: fov.value,
            "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => fov.value = $event),
            min: 10,
            max: 150,
            step: 1,
            "aria-label": unref(t)("load3d.fov")
          }, null, 8, ["modelValue", "aria-label"])
        ])) : createCommentVNode("", true)
      ]);
    };
  }
});
const _hoisted_1$1V = { class: "space-y-4" };
const _sfc_main$2i = /* @__PURE__ */ defineComponent({
  __name: "ViewerExportControls",
  emits: ["exportModel"],
  setup(__props, { emit: __emit }) {
    const emit = __emit;
    const exportFormats = [
      { label: "GLB", value: "glb" },
      { label: "OBJ", value: "obj" },
      { label: "STL", value: "stl" }
    ];
    const exportFormat = ref("obj");
    const exportModel = /* @__PURE__ */ __name((format2) => {
      emit("exportModel", format2);
    }, "exportModel");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1V, [
        createVNode(unref(script$9), {
          modelValue: exportFormat.value,
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => exportFormat.value = $event),
          options: exportFormats,
          "option-label": "label",
          "option-value": "value"
        }, null, 8, ["modelValue"]),
        createVNode(_sfc_main$2A, {
          variant: "muted-textonly",
          class: "rounded-full",
          onClick: _cache[1] || (_cache[1] = ($event) => exportModel(exportFormat.value))
        }, {
          default: withCtx(() => [
            createTextVNode(toDisplayString(_ctx.$t("load3d.export")), 1)
          ]),
          _: 1
        })
      ]);
    };
  }
});
const _hoisted_1$1U = { class: "space-y-4" };
const _sfc_main$2h = /* @__PURE__ */ defineComponent({
  __name: "ViewerLightControls",
  props: {
    "lightIntensity": {},
    "lightIntensityModifiers": {}
  },
  emits: ["update:lightIntensity"],
  setup(__props) {
    const lightIntensity = useModel(__props, "lightIntensity");
    const lightIntensityMaximum = useSettingStore().get(
      "Comfy.Load3D.LightIntensityMaximum"
    );
    const lightIntensityMinimum = useSettingStore().get(
      "Comfy.Load3D.LightIntensityMinimum"
    );
    const lightAdjustmentIncrement = useSettingStore().get(
      "Comfy.Load3D.LightAdjustmentIncrement"
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1U, [
        createBaseVNode("label", null, toDisplayString(_ctx.$t("load3d.lightIntensity")), 1),
        createVNode(unref(script$a), {
          modelValue: lightIntensity.value,
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => lightIntensity.value = $event),
          class: "w-full",
          min: unref(lightIntensityMinimum),
          max: unref(lightIntensityMaximum),
          step: unref(lightAdjustmentIncrement)
        }, null, 8, ["modelValue", "min", "max", "step"])
      ]);
    };
  }
});
const _hoisted_1$1T = { class: "space-y-4" };
const _hoisted_2$1u = { key: 0 };
const _sfc_main$2g = /* @__PURE__ */ defineComponent({
  __name: "ViewerModelControls",
  props: /* @__PURE__ */ mergeModels({
    hideMaterialMode: { type: Boolean, default: false },
    isPlyModel: { type: Boolean, default: false }
  }, {
    "upDirection": {},
    "upDirectionModifiers": {},
    "materialMode": {},
    "materialModeModifiers": {}
  }),
  emits: ["update:upDirection", "update:materialMode"],
  setup(__props) {
    const upDirection = useModel(__props, "upDirection");
    const materialMode = useModel(__props, "materialMode");
    const upDirectionOptions = [
      { label: t("load3d.upDirections.original"), value: "original" },
      { label: "-X", value: "-x" },
      { label: "+X", value: "+x" },
      { label: "-Y", value: "-y" },
      { label: "+Y", value: "+y" },
      { label: "-Z", value: "-z" },
      { label: "+Z", value: "+z" }
    ];
    const materialModeOptions = computed(() => {
      const options = [
        { label: t("load3d.materialModes.original"), value: "original" }
      ];
      if (__props.isPlyModel) {
        options.push({
          label: t("load3d.materialModes.pointCloud"),
          value: "pointCloud"
        });
      }
      options.push(
        { label: t("load3d.materialModes.normal"), value: "normal" },
        { label: t("load3d.materialModes.wireframe"), value: "wireframe" }
      );
      return options;
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1T, [
        createBaseVNode("div", null, [
          createBaseVNode("label", null, toDisplayString(_ctx.$t("load3d.upDirection")), 1),
          createVNode(unref(script$9), {
            modelValue: upDirection.value,
            "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => upDirection.value = $event),
            options: upDirectionOptions,
            "option-label": "label",
            "option-value": "value"
          }, null, 8, ["modelValue"])
        ]),
        !_ctx.hideMaterialMode ? (openBlock(), createElementBlock("div", _hoisted_2$1u, [
          createBaseVNode("label", null, toDisplayString(_ctx.$t("load3d.materialMode")), 1),
          createVNode(unref(script$9), {
            modelValue: materialMode.value,
            "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => materialMode.value = $event),
            options: materialModeOptions.value,
            "option-label": "label",
            "option-value": "value"
          }, null, 8, ["modelValue", "options"])
        ])) : createCommentVNode("", true)
      ]);
    };
  }
});
const _hoisted_1$1S = { class: "space-y-4" };
const _hoisted_2$1t = { key: 0 };
const _hoisted_3$$ = {
  for: "showGrid",
  class: "pl-2"
};
const _hoisted_4$L = { key: 1 };
const _hoisted_5$E = {
  key: 2,
  class: "space-y-2"
};
const _hoisted_6$t = { class: "flex gap-2" };
const _sfc_main$2f = /* @__PURE__ */ defineComponent({
  __name: "ViewerSceneControls",
  props: /* @__PURE__ */ mergeModels({
    hasBackgroundImage: { type: Boolean },
    disableBackgroundUpload: { type: Boolean }
  }, {
    "backgroundColor": {},
    "backgroundColorModifiers": {},
    "showGrid": { type: Boolean },
    "showGridModifiers": {},
    "backgroundRenderMode": {},
    "backgroundRenderModeModifiers": {}
  }),
  emits: /* @__PURE__ */ mergeModels(["updateBackgroundImage"], ["update:backgroundColor", "update:showGrid", "update:backgroundRenderMode"]),
  setup(__props, { emit: __emit }) {
    const backgroundColor = useModel(__props, "backgroundColor");
    const showGrid = useModel(__props, "showGrid");
    const backgroundRenderMode = useModel(
      __props,
      "backgroundRenderMode"
    );
    const emit = __emit;
    const imagePickerRef = ref(null);
    const openImagePicker = /* @__PURE__ */ __name(() => {
      imagePickerRef.value?.click();
    }, "openImagePicker");
    const handleImageUpload = /* @__PURE__ */ __name((event) => {
      const input = event.target;
      if (input.files && input.files[0]) {
        emit("updateBackgroundImage", input.files[0]);
      }
      input.value = "";
    }, "handleImageUpload");
    const removeBackgroundImage = /* @__PURE__ */ __name(() => {
      emit("updateBackgroundImage", null);
    }, "removeBackgroundImage");
    const setBackgroundRenderMode = /* @__PURE__ */ __name((mode) => {
      backgroundRenderMode.value = mode;
    }, "setBackgroundRenderMode");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1S, [
        !_ctx.hasBackgroundImage ? (openBlock(), createElementBlock("div", _hoisted_2$1t, [
          createBaseVNode("label", null, toDisplayString(_ctx.$t("load3d.backgroundColor")), 1),
          withDirectives(createBaseVNode("input", {
            "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => backgroundColor.value = $event),
            type: "color",
            class: "w-full"
          }, null, 512), [
            [vModelText, backgroundColor.value]
          ])
        ])) : createCommentVNode("", true),
        createBaseVNode("div", null, [
          createVNode(unref(script$b), {
            modelValue: showGrid.value,
            "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => showGrid.value = $event),
            "input-id": "showGrid",
            binary: "",
            name: "showGrid"
          }, null, 8, ["modelValue"]),
          createBaseVNode("label", _hoisted_3$$, toDisplayString(_ctx.$t("load3d.showGrid")), 1)
        ]),
        !_ctx.hasBackgroundImage && !_ctx.disableBackgroundUpload ? (openBlock(), createElementBlock("div", _hoisted_4$L, [
          createVNode(_sfc_main$2A, {
            variant: "secondary",
            class: "w-full",
            onClick: openImagePicker
          }, {
            default: withCtx(() => [
              _cache[4] || (_cache[4] = createBaseVNode("i", { class: "pi pi-image" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("load3d.uploadBackgroundImage")), 1)
            ]),
            _: 1
          }),
          createBaseVNode("input", {
            ref_key: "imagePickerRef",
            ref: imagePickerRef,
            type: "file",
            accept: "image/*",
            class: "hidden",
            onChange: handleImageUpload
          }, null, 544)
        ])) : createCommentVNode("", true),
        _ctx.hasBackgroundImage ? (openBlock(), createElementBlock("div", _hoisted_5$E, [
          createBaseVNode("div", _hoisted_6$t, [
            createVNode(_sfc_main$2A, {
              variant: backgroundRenderMode.value === "tiled" ? "primary" : "secondary",
              class: "flex-1",
              onClick: _cache[2] || (_cache[2] = ($event) => setBackgroundRenderMode("tiled"))
            }, {
              default: withCtx(() => [
                _cache[5] || (_cache[5] = createBaseVNode("i", { class: "pi pi-th-large" }, null, -1)),
                createTextVNode(" " + toDisplayString(_ctx.$t("load3d.tiledMode")), 1)
              ]),
              _: 1
            }, 8, ["variant"]),
            createVNode(_sfc_main$2A, {
              variant: backgroundRenderMode.value === "panorama" ? "primary" : "secondary",
              class: "flex-1",
              onClick: _cache[3] || (_cache[3] = ($event) => setBackgroundRenderMode("panorama"))
            }, {
              default: withCtx(() => [
                _cache[6] || (_cache[6] = createBaseVNode("i", { class: "pi pi-globe" }, null, -1)),
                createTextVNode(" " + toDisplayString(_ctx.$t("load3d.panoramaMode")), 1)
              ]),
              _: 1
            }, 8, ["variant"])
          ]),
          createVNode(_sfc_main$2A, {
            variant: "secondary",
            class: "w-full",
            onClick: removeBackgroundImage
          }, {
            default: withCtx(() => [
              _cache[7] || (_cache[7] = createBaseVNode("i", { class: "pi pi-times" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("load3d.removeBackgroundImage")), 1)
            ]),
            _: 1
          })
        ])) : createCommentVNode("", true)
      ]);
    };
  }
});
const SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
  ".gltf",
  ".glb",
  ".obj",
  ".fbx",
  ".stl",
  ".spz",
  ".splat",
  ".ply",
  ".ksplat"
]);
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.interfaces = window.comfyAPI.interfaces || {};
window.comfyAPI.interfaces.SUPPORTED_EXTENSIONS = SUPPORTED_EXTENSIONS;
function useLoad3dDrag(options) {
  const isDragging = ref(false);
  const dragMessage = ref("");
  const isDisabled = computed(() => toValue(options.disabled) ?? false);
  function isValidModelFile(file) {
    const fileName = file.name.toLowerCase();
    const extension = fileName.substring(fileName.lastIndexOf("."));
    return SUPPORTED_EXTENSIONS.has(extension);
  }
  __name(isValidModelFile, "isValidModelFile");
  function handleDragOver(event) {
    if (isDisabled.value) return;
    if (!event.dataTransfer) return;
    const hasFiles = event.dataTransfer.types.includes("Files");
    if (!hasFiles) return;
    isDragging.value = true;
    event.dataTransfer.dropEffect = "copy";
    dragMessage.value = t("load3d.dropToLoad");
  }
  __name(handleDragOver, "handleDragOver");
  function handleDragLeave() {
    isDragging.value = false;
  }
  __name(handleDragLeave, "handleDragLeave");
  async function handleDrop(event) {
    isDragging.value = false;
    if (isDisabled.value) return;
    if (!event.dataTransfer) return;
    const files = Array.from(event.dataTransfer.files);
    if (files.length === 0) return;
    const modelFile = files.find(isValidModelFile);
    if (modelFile) {
      await options.onModelDrop(modelFile);
    } else {
      useToastStore().addAlert(t("load3d.unsupportedFileType"));
    }
  }
  __name(handleDrop, "handleDrop");
  return {
    isDragging,
    dragMessage,
    handleDragOver,
    handleDragLeave,
    handleDrop
  };
}
__name(useLoad3dDrag, "useLoad3dDrag");
class AnimationManager {
  static {
    __name(this, "AnimationManager");
  }
  currentAnimation = null;
  animationActions = [];
  animationClips = [];
  selectedAnimationIndex = 0;
  isAnimationPlaying = false;
  animationSpeed = 1;
  eventManager;
  constructor(eventManager) {
    this.eventManager = eventManager;
  }
  init() {
  }
  dispose() {
    if (this.currentAnimation) {
      this.animationActions.forEach((action) => {
        action.stop();
      });
      this.currentAnimation = null;
    }
    this.animationActions = [];
    this.animationClips = [];
    this.selectedAnimationIndex = 0;
    this.isAnimationPlaying = false;
    this.animationSpeed = 1;
    this.eventManager.emitEvent("animationListChange", []);
  }
  setupModelAnimations(model, originalModel) {
    if (this.currentAnimation) {
      this.currentAnimation.stopAllAction();
      this.animationActions = [];
    }
    let animations = [];
    if (model.animations?.length > 0) {
      animations = model.animations;
    } else if (originalModel && "animations" in originalModel && Array.isArray(originalModel.animations)) {
      animations = originalModel.animations;
    }
    if (animations.length > 0) {
      this.animationClips = animations;
      this.currentAnimation = new AnimationMixer(model);
      if (this.animationClips.length > 0) {
        this.updateSelectedAnimation(0);
      }
    } else {
      this.animationClips = [];
    }
    this.updateAnimationList();
  }
  updateAnimationList() {
    let updatedAnimationList = [];
    if (this.animationClips.length > 0) {
      updatedAnimationList = this.animationClips.map((clip, index2) => ({
        name: clip.name || `Animation ${index2 + 1}`,
        index: index2
      }));
    }
    this.eventManager.emitEvent("animationListChange", updatedAnimationList);
  }
  setAnimationSpeed(speed) {
    this.animationSpeed = speed;
    this.animationActions.forEach((action) => {
      action.setEffectiveTimeScale(speed);
    });
  }
  updateSelectedAnimation(index2) {
    if (!this.currentAnimation || !this.animationClips || index2 >= this.animationClips.length) {
      console.warn("Invalid animation update request");
      return;
    }
    this.animationActions.forEach((action2) => {
      action2.stop();
    });
    this.currentAnimation.stopAllAction();
    this.animationActions = [];
    this.selectedAnimationIndex = index2;
    const clip = this.animationClips[index2];
    const action = this.currentAnimation.clipAction(clip);
    action.setEffectiveTimeScale(this.animationSpeed);
    action.reset();
    action.clampWhenFinished = false;
    action.loop = LoopRepeat;
    if (this.isAnimationPlaying) {
      action.play();
    } else {
      action.play();
      action.paused = true;
    }
    this.animationActions = [action];
  }
  toggleAnimation(play) {
    if (!this.currentAnimation || this.animationActions.length === 0) {
      console.warn("No animation to toggle");
      return;
    }
    this.isAnimationPlaying = play ?? !this.isAnimationPlaying;
    this.animationActions.forEach((action) => {
      if (this.isAnimationPlaying) {
        action.paused = false;
        if (action.time === 0 || action.time === action.getClip().duration) {
          action.reset();
        }
      } else {
        action.paused = true;
      }
    });
  }
  update(delta2) {
    if (this.currentAnimation && this.isAnimationPlaying) {
      this.currentAnimation.update(delta2);
    }
  }
  reset() {
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.AnimationManager = window.comfyAPI.AnimationManager || {};
window.comfyAPI.AnimationManager.AnimationManager = AnimationManager;
class CameraManager {
  static {
    __name(this, "CameraManager");
  }
  perspectiveCamera;
  orthographicCamera;
  activeCamera;
  // @ts-expect-error unused variable
  renderer;
  eventManager;
  controls = null;
  DEFAULT_DISTANCE = 10;
  DEFAULT_LOOK_AT = 0;
  DEFAULT_CAMERA = {
    near: 0.01,
    far: 1e4
  };
  DEFAULT_PERSPECTIVE_CAMERA = {
    fov: 35,
    aspect: 1
  };
  DEFAULT_FRUSTUM_SIZE = 10;
  DEFAULT_ORTHOGRAPHIC_CAMERA = {
    left: -this.DEFAULT_FRUSTUM_SIZE / 2,
    right: this.DEFAULT_FRUSTUM_SIZE / 2,
    top: this.DEFAULT_FRUSTUM_SIZE / 2,
    bottom: -this.DEFAULT_FRUSTUM_SIZE / 2
  };
  constructor(renderer, eventManager) {
    this.renderer = renderer;
    this.eventManager = eventManager;
    this.perspectiveCamera = new PerspectiveCamera(
      this.DEFAULT_PERSPECTIVE_CAMERA.fov,
      this.DEFAULT_PERSPECTIVE_CAMERA.aspect,
      this.DEFAULT_CAMERA.near,
      this.DEFAULT_CAMERA.far
    );
    this.orthographicCamera = new OrthographicCamera(
      this.DEFAULT_ORTHOGRAPHIC_CAMERA.left,
      this.DEFAULT_ORTHOGRAPHIC_CAMERA.right,
      this.DEFAULT_ORTHOGRAPHIC_CAMERA.top,
      this.DEFAULT_ORTHOGRAPHIC_CAMERA.bottom,
      this.DEFAULT_CAMERA.near,
      this.DEFAULT_CAMERA.far
    );
    this.reset();
    this.activeCamera = this.perspectiveCamera;
  }
  init() {
  }
  dispose() {
  }
  setControls(controls) {
    this.controls = controls;
    if (this.controls) {
      this.controls.addEventListener("end", () => {
        this.eventManager.emitEvent("cameraChanged", this.getCameraState());
      });
    }
  }
  getCurrentCameraType() {
    return this.activeCamera === this.perspectiveCamera ? "perspective" : "orthographic";
  }
  toggleCamera(cameraType) {
    const oldCamera = this.activeCamera;
    const position = oldCamera.position.clone();
    const rotation = oldCamera.rotation.clone();
    const target = this.controls?.target.clone() || new Vector3();
    const oldZoom = oldCamera instanceof OrthographicCamera ? oldCamera.zoom : oldCamera.zoom;
    if (!cameraType) {
      this.activeCamera = oldCamera === this.perspectiveCamera ? this.orthographicCamera : this.perspectiveCamera;
    } else {
      this.activeCamera = cameraType === "perspective" ? this.perspectiveCamera : this.orthographicCamera;
      if (oldCamera === this.activeCamera) {
        return;
      }
    }
    this.activeCamera.position.copy(position);
    this.activeCamera.rotation.copy(rotation);
    if (this.activeCamera instanceof OrthographicCamera) {
      this.activeCamera.zoom = oldZoom;
      this.activeCamera.updateProjectionMatrix();
    } else if (this.activeCamera instanceof PerspectiveCamera) {
      this.activeCamera.zoom = oldZoom;
      this.activeCamera.updateProjectionMatrix();
    }
    if (this.controls) {
      this.controls.object = this.activeCamera;
      this.controls.target.copy(target);
      this.controls.update();
    }
    this.eventManager.emitEvent("cameraTypeChange", cameraType);
  }
  setFOV(fov) {
    if (this.activeCamera === this.perspectiveCamera) {
      this.perspectiveCamera.fov = fov;
      this.perspectiveCamera.updateProjectionMatrix();
    }
    this.eventManager.emitEvent("fovChange", fov);
  }
  getCameraState() {
    return {
      position: this.activeCamera.position.clone(),
      target: this.controls?.target.clone() || new Vector3(),
      zoom: this.activeCamera instanceof OrthographicCamera ? this.activeCamera.zoom : this.activeCamera.zoom,
      cameraType: this.getCurrentCameraType()
    };
  }
  setCameraState(state2) {
    this.activeCamera.position.copy(state2.position);
    this.controls?.target.copy(state2.target);
    if (this.activeCamera instanceof OrthographicCamera) {
      this.activeCamera.zoom = state2.zoom;
      this.activeCamera.updateProjectionMatrix();
    } else if (this.activeCamera instanceof PerspectiveCamera) {
      this.activeCamera.zoom = state2.zoom;
      this.activeCamera.updateProjectionMatrix();
    }
    this.controls?.update();
  }
  handleResize(width2, height) {
    const aspect = width2 / height;
    this.updateAspectRatio(aspect);
  }
  updateAspectRatio(aspect) {
    if (this.activeCamera === this.perspectiveCamera) {
      this.perspectiveCamera.aspect = aspect;
      this.perspectiveCamera.updateProjectionMatrix();
    } else {
      const frustumSize = 10;
      this.orthographicCamera.left = -frustumSize * aspect / 2;
      this.orthographicCamera.right = frustumSize * aspect / 2;
      this.orthographicCamera.top = frustumSize / 2;
      this.orthographicCamera.bottom = -frustumSize / 2;
      this.orthographicCamera.updateProjectionMatrix();
    }
  }
  setupForModel(size) {
    const distance2 = Math.max(size.x, size.z) * 2;
    const height = size.y * 2;
    this.perspectiveCamera.position.set(distance2, height, distance2);
    this.orthographicCamera.position.set(distance2, height, distance2);
    if (this.activeCamera === this.perspectiveCamera) {
      this.perspectiveCamera.lookAt(0, size.y / 2, 0);
      this.perspectiveCamera.updateProjectionMatrix();
    } else {
      const frustumSize = Math.max(size.x, size.y, size.z) * 2;
      const aspect = this.perspectiveCamera.aspect;
      this.orthographicCamera.left = -frustumSize * aspect / 2;
      this.orthographicCamera.right = frustumSize * aspect / 2;
      this.orthographicCamera.top = frustumSize / 2;
      this.orthographicCamera.bottom = -frustumSize / 2;
      this.orthographicCamera.lookAt(0, size.y / 2, 0);
      this.orthographicCamera.updateProjectionMatrix();
    }
    this.controls?.target.set(0, size.y / 2, 0);
    this.controls?.update();
  }
  reset() {
    this.perspectiveCamera.position.set(
      this.DEFAULT_DISTANCE,
      this.DEFAULT_DISTANCE,
      this.DEFAULT_DISTANCE
    );
    this.orthographicCamera.position.set(
      this.DEFAULT_DISTANCE,
      this.DEFAULT_DISTANCE,
      this.DEFAULT_DISTANCE
    );
    this.perspectiveCamera.lookAt(
      this.DEFAULT_LOOK_AT,
      this.DEFAULT_LOOK_AT,
      this.DEFAULT_LOOK_AT
    );
    this.orthographicCamera.lookAt(
      this.DEFAULT_LOOK_AT,
      this.DEFAULT_LOOK_AT,
      this.DEFAULT_LOOK_AT
    );
    this.perspectiveCamera.updateProjectionMatrix();
    this.orthographicCamera.updateProjectionMatrix();
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.CameraManager = window.comfyAPI.CameraManager || {};
window.comfyAPI.CameraManager.CameraManager = CameraManager;
class ControlsManager {
  static {
    __name(this, "ControlsManager");
  }
  controls;
  eventManager;
  camera;
  constructor(renderer, camera, eventManager) {
    this.eventManager = eventManager;
    this.camera = camera;
    const container = renderer.domElement.parentElement || renderer.domElement;
    this.controls = new OrbitControls(camera, container);
    this.controls.enableDamping = true;
  }
  init() {
    this.controls.addEventListener("end", () => {
      const cameraState = {
        position: this.camera.position.clone(),
        target: this.controls.target.clone(),
        zoom: this.camera instanceof OrthographicCamera ? this.camera.zoom : this.camera.zoom,
        cameraType: this.camera instanceof PerspectiveCamera ? "perspective" : "orthographic"
      };
      this.eventManager.emitEvent("cameraChanged", cameraState);
    });
  }
  dispose() {
    this.controls.dispose();
  }
  handleResize() {
  }
  update() {
    this.controls.update();
  }
  updateCamera(camera) {
    const position = this.controls.object.position.clone();
    const target = this.controls.target.clone();
    this.camera = camera;
    this.controls.object = camera;
    this.controls.target = target;
    camera.position.copy(position);
    this.controls.update();
  }
  reset() {
    this.controls.target.set(0, 0, 0);
    this.controls.update();
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.ControlsManager = window.comfyAPI.ControlsManager || {};
window.comfyAPI.ControlsManager.ControlsManager = ControlsManager;
class EventManager {
  static {
    __name(this, "EventManager");
  }
  listeners = {};
  addEventListener(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }
  removeEventListener(event, callback) {
    if (this.listeners[event]) {
      this.listeners[event] = this.listeners[event].filter(
        (cb) => cb !== callback
      );
    }
  }
  emitEvent(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach((callback) => callback(data));
    }
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.EventManager = window.comfyAPI.EventManager || {};
window.comfyAPI.EventManager.EventManager = EventManager;
class LightingManager {
  static {
    __name(this, "LightingManager");
  }
  lights = [];
  currentIntensity = 3;
  scene;
  eventManager;
  constructor(scene, eventManager) {
    this.scene = scene;
    this.eventManager = eventManager;
  }
  init() {
    this.setupLights();
  }
  dispose() {
    this.lights.forEach((light2) => {
      this.scene.remove(light2);
    });
    this.lights = [];
  }
  setupLights() {
    const ambientLight = new AmbientLight(16777215, 0.5);
    this.scene.add(ambientLight);
    this.lights.push(ambientLight);
    const mainLight = new DirectionalLight(16777215, 0.8);
    mainLight.position.set(0, 10, 10);
    this.scene.add(mainLight);
    this.lights.push(mainLight);
    const backLight = new DirectionalLight(16777215, 0.5);
    backLight.position.set(0, 10, -10);
    this.scene.add(backLight);
    this.lights.push(backLight);
    const leftFillLight = new DirectionalLight(16777215, 0.3);
    leftFillLight.position.set(-10, 0, 0);
    this.scene.add(leftFillLight);
    this.lights.push(leftFillLight);
    const rightFillLight = new DirectionalLight(16777215, 0.3);
    rightFillLight.position.set(10, 0, 0);
    this.scene.add(rightFillLight);
    this.lights.push(rightFillLight);
    const bottomLight = new DirectionalLight(16777215, 0.2);
    bottomLight.position.set(0, -10, 0);
    this.scene.add(bottomLight);
    this.lights.push(bottomLight);
  }
  setLightIntensity(intensity) {
    this.currentIntensity = intensity;
    this.lights.forEach((light2) => {
      if (light2 instanceof DirectionalLight) {
        if (light2 === this.lights[1]) {
          light2.intensity = intensity * 0.8;
        } else if (light2 === this.lights[2]) {
          light2.intensity = intensity * 0.5;
        } else if (light2 === this.lights[5]) {
          light2.intensity = intensity * 0.2;
        } else {
          light2.intensity = intensity * 0.3;
        }
      } else if (light2 instanceof AmbientLight) {
        light2.intensity = intensity * 0.5;
      }
    });
    this.eventManager.emitEvent("lightIntensityChange", intensity);
  }
  reset() {
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.LightingManager = window.comfyAPI.LightingManager || {};
window.comfyAPI.LightingManager.LightingManager = LightingManager;
function parsePLYHeader(lines) {
  let vertexCount = 0;
  let headerEndLine = 0;
  let hasColor = false;
  let xIndex = -1;
  let yIndex = -1;
  let zIndex = -1;
  let redIndex = -1;
  let greenIndex = -1;
  let blueIndex = -1;
  let propertyIndex = 0;
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim();
    if (line.startsWith("element vertex")) {
      vertexCount = parseInt(line.split(/\s+/)[2]);
    } else if (line.startsWith("property")) {
      const parts2 = line.split(/\s+/);
      const propName = parts2[parts2.length - 1];
      if (propName === "x") xIndex = propertyIndex;
      else if (propName === "y") yIndex = propertyIndex;
      else if (propName === "z") zIndex = propertyIndex;
      else if (propName === "red") {
        hasColor = true;
        redIndex = propertyIndex;
      } else if (propName === "green") greenIndex = propertyIndex;
      else if (propName === "blue") blueIndex = propertyIndex;
      propertyIndex++;
    } else if (line === "end_header") {
      headerEndLine = i;
      break;
    }
  }
  if (vertexCount === 0 || xIndex < 0 || yIndex < 0 || zIndex < 0) {
    return null;
  }
  return {
    vertexCount,
    hasColor,
    propertyIndices: {
      x: xIndex,
      y: yIndex,
      z: zIndex,
      red: redIndex,
      green: greenIndex,
      blue: blueIndex
    },
    headerEndLine
  };
}
__name(parsePLYHeader, "parsePLYHeader");
function parsePLYVertices(lines, header) {
  const { vertexCount, hasColor, propertyIndices, headerEndLine } = header;
  const { x: xIndex, y: yIndex, z: zIndex } = propertyIndices;
  const { red: redIndex, green: greenIndex, blue: blueIndex } = propertyIndices;
  const positions = new Float32Array(vertexCount * 3);
  const colors2 = hasColor ? new Float32Array(vertexCount * 3) : null;
  let vertexIndex = 0;
  for (let i = headerEndLine + 1; i < lines.length && vertexIndex < vertexCount; i++) {
    const line = lines[i].trim();
    if (!line) continue;
    const parts2 = line.split(/\s+/);
    if (parts2.length < 3) continue;
    const posIndex = vertexIndex * 3;
    positions[posIndex] = parseFloat(parts2[xIndex]);
    positions[posIndex + 1] = parseFloat(parts2[yIndex]);
    positions[posIndex + 2] = parseFloat(parts2[zIndex]);
    if (hasColor && colors2 && redIndex >= 0 && greenIndex >= 0 && blueIndex >= 0) {
      if (parts2.length > Math.max(redIndex, greenIndex, blueIndex)) {
        colors2[posIndex] = parseInt(parts2[redIndex]) / 255;
        colors2[posIndex + 1] = parseInt(parts2[greenIndex]) / 255;
        colors2[posIndex + 2] = parseInt(parts2[blueIndex]) / 255;
      }
    }
    vertexIndex++;
  }
  return {
    positions,
    colors: colors2,
    vertexCount: vertexIndex
  };
}
__name(parsePLYVertices, "parsePLYVertices");
function parseASCIIPLY(arrayBuffer) {
  const text = new TextDecoder().decode(arrayBuffer);
  const lines = text.split("\n");
  const header = parsePLYHeader(lines);
  if (!header) return null;
  return parsePLYVertices(lines, header);
}
__name(parseASCIIPLY, "parseASCIIPLY");
function isPLYAsciiFormat(arrayBuffer) {
  const header = new TextDecoder().decode(arrayBuffer.slice(0, 500));
  return header.includes("format ascii");
}
__name(isPLYAsciiFormat, "isPLYAsciiFormat");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.ply = window.comfyAPI.ply || {};
window.comfyAPI.ply.parseASCIIPLY = parseASCIIPLY;
window.comfyAPI.ply.isPLYAsciiFormat = isPLYAsciiFormat;
class FastPLYLoader {
  static {
    __name(this, "FastPLYLoader");
  }
  parse(arrayBuffer) {
    const plyData = parseASCIIPLY(arrayBuffer);
    if (!plyData) {
      throw new Error("Failed to parse PLY data");
    }
    const geometry = new BufferGeometry();
    geometry.setAttribute(
      "position",
      new BufferAttribute(plyData.positions, 3)
    );
    if (plyData.colors) {
      geometry.setAttribute(
        "color",
        new BufferAttribute(plyData.colors, 3)
      );
    }
    return geometry;
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.FastPLYLoader = window.comfyAPI.FastPLYLoader || {};
window.comfyAPI.FastPLYLoader.FastPLYLoader = FastPLYLoader;
class LoaderManager {
  static {
    __name(this, "LoaderManager");
  }
  gltfLoader;
  objLoader;
  mtlLoader;
  fbxLoader;
  stlLoader;
  plyLoader;
  fastPlyLoader;
  modelManager;
  eventManager;
  currentLoadId = 0;
  constructor(modelManager, eventManager) {
    this.modelManager = modelManager;
    this.eventManager = eventManager;
    this.gltfLoader = new GLTFLoader();
    this.objLoader = new OBJLoader();
    this.mtlLoader = new MTLLoader();
    this.fbxLoader = new FBXLoader();
    this.stlLoader = new STLLoader();
    this.plyLoader = new PLYLoader();
    this.fastPlyLoader = new FastPLYLoader();
  }
  init() {
  }
  dispose() {
  }
  async loadModel(url, originalFileName) {
    const loadId = ++this.currentLoadId;
    try {
      this.eventManager.emitEvent("modelLoadingStart", null);
      this.modelManager.clearModel();
      this.modelManager.originalURL = url;
      let fileExtension;
      if (originalFileName) {
        fileExtension = originalFileName.split(".").pop()?.toLowerCase();
        this.modelManager.originalFileName = originalFileName.split("/").pop()?.split(".")[0] || "model";
      } else {
        const filename = new URLSearchParams(url.split("?")[1]).get("filename");
        fileExtension = filename?.split(".").pop()?.toLowerCase();
        if (filename) {
          this.modelManager.originalFileName = filename.split(".")[0] || "model";
        } else {
          this.modelManager.originalFileName = "model";
        }
      }
      if (!fileExtension) {
        useToastStore().addAlert(t("toastMessages.couldNotDetermineFileType"));
        return;
      }
      const model = await this.loadModelInternal(url, fileExtension);
      if (loadId !== this.currentLoadId) {
        return;
      }
      if (model) {
        await this.modelManager.setupModel(model);
      }
      this.eventManager.emitEvent("modelLoadingEnd", null);
    } catch (error) {
      if (loadId === this.currentLoadId) {
        this.eventManager.emitEvent("modelLoadingEnd", null);
        console.error("Error loading model:", error);
        useToastStore().addAlert(t("toastMessages.errorLoadingModel"));
      }
    }
  }
  async loadModelInternal(url, fileExtension) {
    let model = null;
    const params = new URLSearchParams(url.split("?")[1]);
    const filename = params.get("filename");
    if (!filename) {
      console.error("Missing filename in URL:", url);
      return null;
    }
    const loadRootFolder = params.get("type") === "output" ? "output" : "input";
    const subfolder = params.get("subfolder") ?? "";
    const path = "api/view?type=" + loadRootFolder + "&subfolder=" + encodeURIComponent(subfolder) + "&filename=";
    switch (fileExtension) {
      case "stl":
        this.stlLoader.setPath(path);
        const geometry = await this.stlLoader.loadAsync(filename);
        this.modelManager.setOriginalModel(geometry);
        geometry.computeVertexNormals();
        const mesh = new Mesh(
          geometry,
          this.modelManager.standardMaterial
        );
        const group = new Group();
        group.add(mesh);
        model = group;
        break;
      case "fbx":
        this.fbxLoader.setPath(path);
        const fbxModel = await this.fbxLoader.loadAsync(filename);
        this.modelManager.setOriginalModel(fbxModel);
        model = fbxModel;
        fbxModel.traverse((child) => {
          if (child instanceof Mesh) {
            this.modelManager.originalMaterials.set(child, child.material);
            if (child instanceof SkinnedMesh) {
              child.frustumCulled = false;
            }
          }
        });
        break;
      case "obj":
        if (this.modelManager.materialMode === "original") {
          try {
            this.mtlLoader.setPath(path);
            const mtlFileName = filename.replace(/\.obj$/, ".mtl");
            const materials = await this.mtlLoader.loadAsync(mtlFileName);
            materials.preload();
            this.objLoader.setMaterials(materials);
          } catch (e2) {
            console.log(
              "No MTL file found or error loading it, continuing without materials"
            );
          }
        }
        this.objLoader.setPath(path);
        model = await this.objLoader.loadAsync(filename);
        model.traverse((child) => {
          if (child instanceof Mesh) {
            this.modelManager.originalMaterials.set(child, child.material);
          }
        });
        break;
      case "gltf":
      case "glb":
        this.gltfLoader.setPath(path);
        const gltf = await this.gltfLoader.loadAsync(filename);
        this.modelManager.setOriginalModel(gltf);
        model = gltf.scene;
        gltf.scene.traverse((child) => {
          if (child instanceof Mesh) {
            child.geometry.computeVertexNormals();
            this.modelManager.originalMaterials.set(child, child.material);
            if (child instanceof SkinnedMesh) {
              child.frustumCulled = false;
            }
          }
        });
        break;
      case "ply":
        model = await this.loadPLY(path, filename);
        break;
      case "spz":
      case "splat":
      case "ksplat":
        model = await this.loadSplat(path, filename);
        break;
    }
    return model;
  }
  async fetchModelData(path, filename) {
    const route = "/" + path.replace(/^api\//, "") + encodeURIComponent(filename);
    const response = await api.fetchApi(route);
    if (!response.ok) {
      throw new Error(`Failed to fetch model: ${response.status}`);
    }
    return response.arrayBuffer();
  }
  async loadSplat(path, filename) {
    const arrayBuffer = await this.fetchModelData(path, filename);
    const splatMesh = new SplatMesh({ fileBytes: arrayBuffer });
    this.modelManager.setOriginalModel(splatMesh);
    const splatGroup = new Group();
    splatGroup.add(splatMesh);
    return splatGroup;
  }
  async loadPLY(path, filename) {
    const plyEngine = useSettingStore().get("Comfy.Load3D.PLYEngine");
    if (plyEngine === "sparkjs") {
      return this.loadSplat(path, filename);
    }
    const arrayBuffer = await this.fetchModelData(path, filename);
    const isASCII = isPLYAsciiFormat(arrayBuffer);
    let plyGeometry;
    if (isASCII && plyEngine === "fastply") {
      plyGeometry = this.fastPlyLoader.parse(arrayBuffer);
    } else {
      this.plyLoader.setPath(path);
      plyGeometry = this.plyLoader.parse(arrayBuffer);
    }
    this.modelManager.setOriginalModel(plyGeometry);
    plyGeometry.computeVertexNormals();
    const hasVertexColors = plyGeometry.attributes.color !== void 0;
    const materialMode = this.modelManager.materialMode;
    if (materialMode === "pointCloud") {
      plyGeometry.computeBoundingSphere();
      if (plyGeometry.boundingSphere) {
        const center = plyGeometry.boundingSphere.center;
        const radius = plyGeometry.boundingSphere.radius;
        plyGeometry.translate(-center.x, -center.y, -center.z);
        if (radius > 0) {
          const scale = 1 / radius;
          plyGeometry.scale(scale, scale, scale);
        }
      }
      const pointMaterial = hasVertexColors ? new PointsMaterial({
        size: 5e-3,
        vertexColors: true,
        sizeAttenuation: true
      }) : new PointsMaterial({
        size: 5e-3,
        color: 13421772,
        sizeAttenuation: true
      });
      const plyPoints = new Points(plyGeometry, pointMaterial);
      this.modelManager.originalMaterials.set(
        plyPoints,
        pointMaterial
      );
      const plyGroup2 = new Group();
      plyGroup2.add(plyPoints);
      return plyGroup2;
    }
    let plyMaterial;
    if (hasVertexColors) {
      plyMaterial = new MeshStandardMaterial({
        vertexColors: true,
        metalness: 0,
        roughness: 0.5,
        side: DoubleSide
      });
    } else {
      plyMaterial = this.modelManager.standardMaterial.clone();
      plyMaterial.side = DoubleSide;
    }
    const plyMesh = new Mesh(plyGeometry, plyMaterial);
    this.modelManager.originalMaterials.set(plyMesh, plyMaterial);
    const plyGroup = new Group();
    plyGroup.add(plyMesh);
    return plyGroup;
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.LoaderManager = window.comfyAPI.LoaderManager || {};
window.comfyAPI.LoaderManager.LoaderManager = LoaderManager;
class ModelExporter {
  static {
    __name(this, "ModelExporter");
  }
  static detectFormatFromURL(url) {
    try {
      const filenameParam = new URLSearchParams(url.split("?")[1]).get(
        "filename"
      );
      if (filenameParam) {
        const extension = filenameParam.split(".").pop()?.toLowerCase();
        return extension || null;
      }
    } catch (e2) {
      console.error("Error parsing URL:", e2);
    }
    return null;
  }
  static canUseDirectURL(url, format2) {
    if (!url) return false;
    const urlFormat = ModelExporter.detectFormatFromURL(url);
    if (!urlFormat) return false;
    return urlFormat.toLowerCase() === format2.toLowerCase();
  }
  static async downloadFromURL(url, desiredFilename) {
    try {
      const response = await fetch(url);
      const blob = await response.blob();
      downloadBlob(desiredFilename, blob);
    } catch (error) {
      console.error("Error downloading from URL:", error);
      useToastStore().addAlert(t("toastMessages.failedToDownloadFile"));
      throw error;
    }
  }
  static async exportGLB(model, filename = "model.glb", originalURL) {
    if (originalURL && ModelExporter.canUseDirectURL(originalURL, "glb")) {
      console.log("Using direct URL download for GLB");
      return ModelExporter.downloadFromURL(originalURL, filename);
    }
    const exporter = new GLTFExporter();
    try {
      await new Promise((resolve) => setTimeout(resolve, 50));
      const result = await new Promise((resolve, reject) => {
        exporter.parse(
          model,
          (gltf) => {
            resolve(gltf);
          },
          (error) => {
            reject(error);
          },
          { binary: true }
        );
      });
      await new Promise((resolve) => setTimeout(resolve, 50));
      ModelExporter.saveArrayBuffer(result, filename);
    } catch (error) {
      console.error("Error exporting GLB:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToExportModel", { format: "GLB" })
      );
      throw error;
    }
  }
  static async exportOBJ(model, filename = "model.obj", originalURL) {
    if (originalURL && ModelExporter.canUseDirectURL(originalURL, "obj")) {
      console.log("Using direct URL download for OBJ");
      return ModelExporter.downloadFromURL(originalURL, filename);
    }
    const exporter = new OBJExporter();
    try {
      await new Promise((resolve) => setTimeout(resolve, 50));
      const result = exporter.parse(model);
      await new Promise((resolve) => setTimeout(resolve, 50));
      ModelExporter.saveString(result, filename);
    } catch (error) {
      console.error("Error exporting OBJ:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToExportModel", { format: "OBJ" })
      );
      throw error;
    }
  }
  static async exportSTL(model, filename = "model.stl", originalURL) {
    if (originalURL && ModelExporter.canUseDirectURL(originalURL, "stl")) {
      console.log("Using direct URL download for STL");
      return ModelExporter.downloadFromURL(originalURL, filename);
    }
    const exporter = new STLExporter();
    try {
      await new Promise((resolve) => setTimeout(resolve, 50));
      const result = exporter.parse(model);
      await new Promise((resolve) => setTimeout(resolve, 50));
      ModelExporter.saveString(result, filename);
    } catch (error) {
      console.error("Error exporting STL:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToExportModel", { format: "STL" })
      );
      throw error;
    }
  }
  static saveArrayBuffer(buffer, filename) {
    const blob = new Blob([buffer], { type: "application/octet-stream" });
    downloadBlob(filename, blob);
  }
  static saveString(text, filename) {
    const blob = new Blob([text], { type: "text/plain" });
    downloadBlob(filename, blob);
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.ModelExporter = window.comfyAPI.ModelExporter || {};
window.comfyAPI.ModelExporter.ModelExporter = ModelExporter;
class RecordingManager {
  static {
    __name(this, "RecordingManager");
  }
  mediaRecorder = null;
  recordedChunks = [];
  isRecording = false;
  recordingStream = null;
  recordingIndicator = null;
  scene;
  renderer;
  eventManager;
  recordingStartTime = 0;
  recordingDuration = 0;
  recordingCanvas = null;
  recordingContext = null;
  animationFrameId = null;
  constructor(scene, renderer, eventManager) {
    this.scene = scene;
    this.renderer = renderer;
    this.eventManager = eventManager;
    this.setupRecordingIndicator();
  }
  setupRecordingIndicator() {
    const map2 = new TextureLoader().load(
      "data:image/svg+xml;base64," + btoa(`<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
        <circle cx="32" cy="32" r="24" fill="#4CAF50" opacity="0.8" />
        <circle cx="32" cy="32" r="16" fill="#2E7D32" opacity="0.8" />
      </svg>`)
    );
    const material = new SpriteMaterial({
      map: map2,
      transparent: true,
      depthTest: false,
      depthWrite: false
    });
    this.recordingIndicator = new Sprite(material);
    this.recordingIndicator.scale.set(0.5, 0.5, 0.5);
    this.recordingIndicator.position.set(-0.8, 0.8, 0);
    this.recordingIndicator.visible = false;
    this.scene.add(this.recordingIndicator);
  }
  async startRecording(targetWidth, targetHeight) {
    if (this.isRecording) {
      return;
    }
    try {
      const sourceCanvas = this.renderer.domElement;
      const sourceWidth = sourceCanvas.width;
      const sourceHeight = sourceCanvas.height;
      const recordWidth = targetWidth || sourceWidth;
      const recordHeight = targetHeight || sourceHeight;
      this.recordingCanvas = document.createElement("canvas");
      this.recordingCanvas.width = recordWidth;
      this.recordingCanvas.height = recordHeight;
      this.recordingContext = this.recordingCanvas.getContext("2d", {
        alpha: false
      });
      if (!this.recordingContext) {
        throw new Error("Failed to get 2D context for recording canvas");
      }
      const sourceAspectRatio = sourceWidth / sourceHeight;
      const targetAspectRatio = recordWidth / recordHeight;
      let sx = 0, sy = 0, sw = sourceWidth, sh = sourceHeight;
      if (Math.abs(sourceAspectRatio - targetAspectRatio) > 0.01) {
        if (sourceAspectRatio > targetAspectRatio) {
          sw = sourceHeight * targetAspectRatio;
          sx = (sourceWidth - sw) / 2;
        } else {
          sh = sourceWidth / targetAspectRatio;
          sy = (sourceHeight - sh) / 2;
        }
      }
      const captureFrame = /* @__PURE__ */ __name(() => {
        if (!this.isRecording || !this.recordingContext) {
          return;
        }
        this.recordingContext.drawImage(
          sourceCanvas,
          sx,
          sy,
          sw,
          sh,
          0,
          0,
          recordWidth,
          recordHeight
        );
        this.animationFrameId = requestAnimationFrame(captureFrame);
      }, "captureFrame");
      this.recordingStream = this.recordingCanvas.captureStream(30);
      if (!this.recordingStream) {
        throw new Error("Failed to capture stream from canvas");
      }
      this.mediaRecorder = new MediaRecorder(this.recordingStream, {
        mimeType: "video/webm;codecs=vp9",
        videoBitsPerSecond: 5e6
      });
      this.recordedChunks = [];
      this.mediaRecorder.ondataavailable = (event) => {
        if (event.data.size > 0) {
          this.recordedChunks.push(event.data);
        }
      };
      this.mediaRecorder.onstop = () => {
        this.recordingIndicator.visible = false;
        this.isRecording = false;
        this.recordingStream = null;
        if (this.animationFrameId !== null) {
          cancelAnimationFrame(this.animationFrameId);
          this.animationFrameId = null;
        }
        this.eventManager.emitEvent("recordingStopped", {
          duration: this.recordingDuration,
          hasRecording: this.recordedChunks.length > 0
        });
      };
      if (this.recordingIndicator) {
        this.recordingIndicator.visible = true;
      }
      this.mediaRecorder.start(100);
      this.isRecording = true;
      this.recordingStartTime = Date.now();
      captureFrame();
      this.eventManager.emitEvent("recordingStarted", null);
    } catch (error) {
      console.error("Error starting recording:", error);
      this.eventManager.emitEvent("recordingError", error);
    }
  }
  stopRecording() {
    if (!this.isRecording || !this.mediaRecorder) {
      return;
    }
    this.recordingDuration = (Date.now() - this.recordingStartTime) / 1e3;
    if (this.animationFrameId !== null) {
      cancelAnimationFrame(this.animationFrameId);
      this.animationFrameId = null;
    }
    this.mediaRecorder.stop();
    if (this.recordingStream) {
      this.recordingStream.getTracks().forEach((track) => track.stop());
    }
    this.recordingCanvas = null;
    this.recordingContext = null;
  }
  getIsRecording() {
    return this.isRecording;
  }
  hasRecording() {
    return this.recordedChunks.length > 0;
  }
  getRecordingDuration() {
    return this.recordingDuration;
  }
  getRecordingData() {
    if (this.recordedChunks.length !== 0) {
      const blob = new Blob(this.recordedChunks, { type: "video/webm" });
      return URL.createObjectURL(blob);
    }
    return null;
  }
  exportRecording(filename = "scene-recording.mp4") {
    if (this.recordedChunks.length === 0) {
      this.eventManager.emitEvent(
        "recordingError",
        new Error("No recording available to export")
      );
      return;
    }
    this.eventManager.emitEvent("exportingRecording", null);
    try {
      const blob = new Blob(this.recordedChunks, { type: "video/webm" });
      downloadBlob(filename, blob);
      this.eventManager.emitEvent("recordingExported", null);
    } catch (error) {
      console.error("Error exporting recording:", error);
      this.eventManager.emitEvent("recordingError", error);
    }
  }
  clearRecording() {
    this.recordedChunks = [];
    this.recordingDuration = 0;
    this.eventManager.emitEvent("recordingCleared", null);
  }
  dispose() {
    if (this.animationFrameId !== null) {
      cancelAnimationFrame(this.animationFrameId);
      this.animationFrameId = null;
    }
    this.stopRecording();
    this.clearRecording();
    this.recordingCanvas = null;
    this.recordingContext = null;
    if (this.recordingIndicator) {
      this.scene.remove(this.recordingIndicator);
      this.recordingIndicator.material.map?.dispose();
      this.recordingIndicator.material.dispose();
    }
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.RecordingManager = window.comfyAPI.RecordingManager || {};
window.comfyAPI.RecordingManager.RecordingManager = RecordingManager;
class Load3dUtils {
  static {
    __name(this, "Load3dUtils");
  }
  static async uploadTempImage(imageData, prefix, fileType = "png") {
    const blob = await fetch(imageData).then((r) => r.blob());
    const name2 = `${prefix}_${Date.now()}.${fileType}`;
    const file = new File([blob], name2, {
      type: fileType === "mp4" ? "video/mp4" : "image/png"
    });
    const body = new FormData();
    body.append("image", file);
    body.append("subfolder", "threed");
    body.append("type", "temp");
    const resp = await api.fetchApi("/upload/image", {
      method: "POST",
      body
    });
    if (resp.status !== 200) {
      const err = `Error uploading temp file: ${resp.status} - ${resp.statusText}`;
      useToastStore().addAlert(err);
      throw new Error(err);
    }
    return await resp.json();
  }
  static async uploadFile(file, subfolder) {
    let uploadPath;
    try {
      const body = new FormData();
      body.append("image", file);
      body.append("subfolder", subfolder);
      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;
        }
        uploadPath = path;
      } else {
        useToastStore().addAlert(resp.status + " - " + resp.statusText);
      }
    } catch (error) {
      console.error("Upload error:", error);
      useToastStore().addAlert(
        error instanceof Error ? error.message : t("toastMessages.fileUploadFailed")
      );
    }
    return uploadPath;
  }
  static splitFilePath(path) {
    const folder_separator = path.lastIndexOf("/");
    if (folder_separator === -1) {
      return ["", path];
    }
    return [
      path.substring(0, folder_separator),
      path.substring(folder_separator + 1)
    ];
  }
  static getResourceURL(subfolder, filename, type = "input") {
    const params = [
      "filename=" + encodeURIComponent(filename),
      "type=" + type,
      "subfolder=" + subfolder,
      app$1.getRandParam().substring(1)
    ].join("&");
    return `/view?${params}`;
  }
  static async uploadMultipleFiles(files, subfolder = "3d") {
    const uploadPromises = Array.from(files).map(
      (file) => this.uploadFile(file, subfolder)
    );
    await Promise.all(uploadPromises);
  }
}
class SceneManager {
  static {
    __name(this, "SceneManager");
  }
  scene;
  gridHelper;
  backgroundScene;
  backgroundCamera;
  backgroundMesh = null;
  backgroundTexture = null;
  backgroundRenderMode = "tiled";
  backgroundColorMaterial = null;
  currentBackgroundType = "color";
  currentBackgroundColor = "#282828";
  eventManager;
  renderer;
  getActiveCamera;
  // @ts-expect-error unused variable
  getControls;
  constructor(renderer, getActiveCamera, getControls, eventManager) {
    this.renderer = renderer;
    this.eventManager = eventManager;
    this.scene = new Scene();
    this.getActiveCamera = getActiveCamera;
    this.getControls = getControls;
    this.gridHelper = new GridHelper(20, 20);
    this.gridHelper.position.set(0, 0, 0);
    this.scene.add(this.gridHelper);
    this.backgroundScene = new Scene();
    this.backgroundCamera = new OrthographicCamera(-1, 1, 1, -1, -1, 1);
    this.initBackgroundScene();
  }
  initBackgroundScene() {
    const planeGeometry = new PlaneGeometry(2, 2);
    this.backgroundColorMaterial = new MeshBasicMaterial({
      color: new Color(this.currentBackgroundColor),
      transparent: false,
      depthWrite: false,
      depthTest: false,
      side: DoubleSide
    });
    this.backgroundMesh = new Mesh(
      planeGeometry,
      this.backgroundColorMaterial
    );
    this.backgroundMesh.position.set(0, 0, 0);
    this.backgroundScene.add(this.backgroundMesh);
    this.renderer.setClearColor(0, 0);
  }
  init() {
  }
  dispose() {
    if (this.backgroundTexture) {
      this.backgroundTexture.dispose();
    }
    if (this.backgroundColorMaterial) {
      this.backgroundColorMaterial.dispose();
    }
    if (this.backgroundMesh) {
      this.backgroundMesh.geometry.dispose();
      if (this.backgroundMesh.material instanceof Material) {
        this.backgroundMesh.material.dispose();
      }
    }
    if (this.scene.background) {
      this.scene.background = null;
    }
    this.scene.clear();
  }
  toggleGrid(showGrid) {
    if (this.gridHelper) {
      this.gridHelper.visible = showGrid;
    }
    this.eventManager.emitEvent("showGridChange", showGrid);
  }
  setBackgroundColor(color2) {
    this.currentBackgroundColor = color2;
    this.currentBackgroundType = "color";
    if (this.scene.background instanceof Texture) {
      this.scene.background = null;
    }
    if (this.backgroundRenderMode === "panorama") {
      this.backgroundRenderMode = "tiled";
      this.eventManager.emitEvent("backgroundRenderModeChange", "tiled");
    }
    if (!this.backgroundMesh || !this.backgroundColorMaterial) {
      this.initBackgroundScene();
    }
    this.backgroundColorMaterial.color.set(color2);
    this.backgroundColorMaterial.map = null;
    this.backgroundColorMaterial.transparent = false;
    this.backgroundColorMaterial.needsUpdate = true;
    if (this.backgroundMesh) {
      this.backgroundMesh.material = this.backgroundColorMaterial;
    }
    if (this.backgroundTexture) {
      this.backgroundTexture.dispose();
      this.backgroundTexture = null;
    }
    this.eventManager.emitEvent("backgroundColorChange", color2);
  }
  async setBackgroundImage(uploadPath) {
    if (uploadPath === "") {
      this.setBackgroundColor(this.currentBackgroundColor);
      return;
    }
    this.eventManager.emitEvent("backgroundImageLoadingStart", null);
    let type = "input";
    let pathParts = Load3dUtils.splitFilePath(uploadPath);
    let subfolder = pathParts[0];
    let filename = pathParts[1];
    if (subfolder === "temp") {
      type = "temp";
      pathParts = ["", filename];
    } else if (subfolder === "output") {
      type = "output";
      pathParts = ["", filename];
    }
    let imageUrl = Load3dUtils.getResourceURL(...pathParts, type);
    if (!imageUrl.startsWith("/api")) {
      imageUrl = "/api" + imageUrl;
    }
    try {
      const textureLoader = new TextureLoader();
      const texture = await new Promise((resolve, reject) => {
        textureLoader.load(imageUrl, resolve, void 0, reject);
      });
      if (this.backgroundTexture) {
        this.backgroundTexture.dispose();
      }
      texture.colorSpace = SRGBColorSpace;
      this.backgroundTexture = texture;
      this.currentBackgroundType = "image";
      if (this.backgroundRenderMode === "panorama") {
        texture.mapping = EquirectangularReflectionMapping;
        this.scene.background = texture;
      } else {
        if (!this.backgroundMesh) {
          this.initBackgroundScene();
        }
        const imageMaterial = new MeshBasicMaterial({
          map: texture,
          transparent: true,
          depthWrite: false,
          depthTest: false,
          side: DoubleSide
        });
        if (this.backgroundMesh) {
          if (this.backgroundMesh.material !== this.backgroundColorMaterial && this.backgroundMesh.material instanceof Material) {
            this.backgroundMesh.material.dispose();
          }
          this.backgroundMesh.material = imageMaterial;
          this.backgroundMesh.position.set(0, 0, 0);
        }
        this.updateBackgroundSize(
          this.backgroundTexture,
          this.backgroundMesh,
          this.renderer.domElement.clientWidth,
          this.renderer.domElement.clientHeight
        );
      }
      this.eventManager.emitEvent("backgroundImageChange", uploadPath);
      this.eventManager.emitEvent("backgroundImageLoadingEnd", null);
    } catch (error) {
      this.eventManager.emitEvent("backgroundImageLoadingEnd", null);
      console.error("Error loading background image:", error);
      this.setBackgroundColor(this.currentBackgroundColor);
    }
  }
  removeBackgroundImage() {
    this.setBackgroundColor(this.currentBackgroundColor);
    this.eventManager.emitEvent("backgroundImageLoadingEnd", null);
  }
  setBackgroundRenderMode(mode) {
    if (this.backgroundRenderMode === mode) return;
    this.backgroundRenderMode = mode;
    if (this.currentBackgroundType === "image" && this.backgroundTexture) {
      try {
        if (mode === "panorama") {
          this.backgroundTexture.mapping = EquirectangularReflectionMapping;
          this.scene.background = this.backgroundTexture;
        } else {
          this.scene.background = null;
          if (this.backgroundMesh && this.backgroundMesh.material instanceof MeshBasicMaterial) {
            this.backgroundMesh.material.map = this.backgroundTexture;
            this.backgroundMesh.material.needsUpdate = true;
          }
        }
      } catch (error) {
        console.error("Error set background render mode:", error);
      }
    }
    this.eventManager.emitEvent("backgroundRenderModeChange", mode);
  }
  updateBackgroundSize(backgroundTexture, backgroundMesh, targetWidth, targetHeight) {
    if (!backgroundTexture || !backgroundMesh) return;
    const material = backgroundMesh.material;
    if (!material.map) return;
    const imageAspect = backgroundTexture.image.width / backgroundTexture.image.height;
    const targetAspect = targetWidth / targetHeight;
    if (imageAspect > targetAspect) {
      backgroundMesh.scale.set(imageAspect / targetAspect, 1, 1);
    } else {
      backgroundMesh.scale.set(1, targetAspect / imageAspect, 1);
    }
    material.needsUpdate = true;
  }
  handleResize(width2, height) {
    if (this.backgroundTexture && this.backgroundMesh && this.currentBackgroundType === "image") {
      this.updateBackgroundSize(
        this.backgroundTexture,
        this.backgroundMesh,
        width2,
        height
      );
    }
  }
  renderBackground() {
    if ((this.backgroundRenderMode === "tiled" || this.currentBackgroundType === "color") && this.backgroundMesh) {
      const currentToneMapping = this.renderer.toneMapping;
      const currentExposure = this.renderer.toneMappingExposure;
      this.renderer.toneMapping = NoToneMapping;
      this.renderer.render(this.backgroundScene, this.backgroundCamera);
      this.renderer.toneMapping = currentToneMapping;
      this.renderer.toneMappingExposure = currentExposure;
    }
  }
  getCurrentBackgroundInfo() {
    return {
      type: this.currentBackgroundType,
      value: this.currentBackgroundType === "color" ? this.currentBackgroundColor : ""
    };
  }
  captureScene(width2, height) {
    return new Promise(async (resolve, reject) => {
      try {
        const originalWidth = this.renderer.domElement.width;
        const originalHeight = this.renderer.domElement.height;
        const originalClearColor = this.renderer.getClearColor(
          new Color()
        );
        const originalClearAlpha = this.renderer.getClearAlpha();
        const originalOutputColorSpace = this.renderer.outputColorSpace;
        this.renderer.setSize(width2, height);
        if (this.getActiveCamera() instanceof PerspectiveCamera) {
          const perspectiveCamera = this.getActiveCamera();
          perspectiveCamera.aspect = width2 / height;
          perspectiveCamera.updateProjectionMatrix();
        } else {
          const orthographicCamera = this.getActiveCamera();
          const frustumSize = 10;
          const aspect = width2 / height;
          orthographicCamera.left = -frustumSize * aspect / 2;
          orthographicCamera.right = frustumSize * aspect / 2;
          orthographicCamera.top = frustumSize / 2;
          orthographicCamera.bottom = -frustumSize / 2;
          orthographicCamera.updateProjectionMatrix();
        }
        if (this.backgroundTexture && this.backgroundMesh && this.currentBackgroundType === "image") {
          this.updateBackgroundSize(
            this.backgroundTexture,
            this.backgroundMesh,
            width2,
            height
          );
        }
        const originalMaterials = /* @__PURE__ */ new Map();
        this.renderer.clear();
        this.renderBackground();
        this.renderer.render(this.scene, this.getActiveCamera());
        const sceneData = this.renderer.domElement.toDataURL("image/png");
        this.renderer.setClearColor(0, 0);
        this.renderer.clear();
        this.renderer.render(this.scene, this.getActiveCamera());
        const maskData = this.renderer.domElement.toDataURL("image/png");
        this.scene.traverse((child) => {
          if (child instanceof Mesh) {
            originalMaterials.set(child, child.material);
            child.material = new MeshNormalMaterial({
              flatShading: false,
              side: DoubleSide,
              normalScale: new Vector2(1, 1)
            });
          }
        });
        const gridVisible = this.gridHelper.visible;
        this.gridHelper.visible = false;
        this.renderer.setClearColor(0, 1);
        this.renderer.clear();
        this.renderer.render(this.scene, this.getActiveCamera());
        const normalData = this.renderer.domElement.toDataURL("image/png");
        this.scene.traverse((child) => {
          if (child instanceof Mesh) {
            const originalMaterial = originalMaterials.get(child);
            if (originalMaterial) {
              child.material = originalMaterial;
            }
          }
        });
        this.renderer.setClearColor(16777215, 1);
        this.renderer.clear();
        this.gridHelper.visible = gridVisible;
        this.renderer.setClearColor(originalClearColor, originalClearAlpha);
        this.renderer.setSize(originalWidth, originalHeight);
        this.renderer.outputColorSpace = originalOutputColorSpace;
        this.handleResize(originalWidth, originalHeight);
        resolve({
          scene: sceneData,
          mask: maskData,
          normal: normalData
        });
      } catch (error) {
        reject(error);
      }
    });
  }
  reset() {
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.SceneManager = window.comfyAPI.SceneManager || {};
window.comfyAPI.SceneManager.SceneManager = SceneManager;
class SceneModelManager {
  static {
    __name(this, "SceneModelManager");
  }
  currentModel = null;
  originalModel = null;
  originalRotation = null;
  currentUpDirection = "original";
  materialMode = "original";
  originalMaterials = /* @__PURE__ */ new WeakMap();
  normalMaterial;
  standardMaterial;
  wireframeMaterial;
  depthMaterial;
  originalFileName = null;
  originalURL = null;
  appliedTexture = null;
  textureLoader;
  scene;
  renderer;
  eventManager;
  activeCamera;
  setupCamera;
  constructor(scene, renderer, eventManager, getActiveCamera, setupCamera) {
    this.scene = scene;
    this.renderer = renderer;
    this.eventManager = eventManager;
    this.activeCamera = getActiveCamera();
    this.setupCamera = setupCamera;
    this.textureLoader = new TextureLoader();
    this.normalMaterial = new MeshNormalMaterial({
      flatShading: false,
      side: DoubleSide,
      normalScale: new Vector2(1, 1),
      transparent: false,
      opacity: 1
    });
    this.wireframeMaterial = new MeshBasicMaterial({
      color: 16777215,
      wireframe: true,
      transparent: false,
      opacity: 1
    });
    this.depthMaterial = new MeshDepthMaterial({
      depthPacking: BasicDepthPacking,
      side: DoubleSide
    });
    this.standardMaterial = this.createSTLMaterial();
  }
  init() {
  }
  dispose() {
    this.clearModel();
    this.normalMaterial.dispose();
    this.standardMaterial.dispose();
    this.wireframeMaterial.dispose();
    this.depthMaterial.dispose();
    if (this.appliedTexture) {
      this.appliedTexture.dispose();
      this.appliedTexture = null;
    }
  }
  createSTLMaterial() {
    return new MeshStandardMaterial({
      color: 8421504,
      metalness: 0.1,
      roughness: 0.8,
      flatShading: false,
      side: DoubleSide
    });
  }
  handlePLYModeSwitch(mode) {
    if (!(this.originalModel instanceof BufferGeometry)) {
      return;
    }
    const plyGeometry = this.originalModel.clone();
    const hasVertexColors = plyGeometry.attributes.color !== void 0;
    const oldMainModels = [];
    this.scene.traverse((obj) => {
      if (obj.name === "MainModel") {
        oldMainModels.push(obj);
      }
    });
    oldMainModels.forEach((oldModel) => {
      oldModel.traverse((child) => {
        if (child instanceof Mesh || child instanceof Points) {
          child.geometry?.dispose();
          if (Array.isArray(child.material)) {
            child.material.forEach((m) => m.dispose());
          } else {
            child.material?.dispose();
          }
        }
      });
      this.scene.remove(oldModel);
    });
    this.currentModel = null;
    let newModel;
    if (mode === "pointCloud") {
      plyGeometry.computeBoundingSphere();
      if (plyGeometry.boundingSphere) {
        const center = plyGeometry.boundingSphere.center;
        const radius = plyGeometry.boundingSphere.radius;
        plyGeometry.translate(-center.x, -center.y, -center.z);
        if (radius > 0) {
          const scale = 1 / radius;
          plyGeometry.scale(scale, scale, scale);
        }
      }
      const pointMaterial = hasVertexColors ? new PointsMaterial({
        size: 5e-3,
        vertexColors: true,
        sizeAttenuation: true
      }) : new PointsMaterial({
        size: 5e-3,
        color: 13421772,
        sizeAttenuation: true
      });
      const points = new Points(plyGeometry, pointMaterial);
      newModel = new Group();
      newModel.add(points);
    } else {
      let meshMaterial = hasVertexColors ? new MeshStandardMaterial({
        vertexColors: true,
        metalness: 0,
        roughness: 0.5,
        side: DoubleSide
      }) : this.standardMaterial.clone();
      if (!hasVertexColors && meshMaterial instanceof MeshStandardMaterial) {
        meshMaterial.side = DoubleSide;
      }
      const mesh = new Mesh(plyGeometry, meshMaterial);
      this.originalMaterials.set(mesh, meshMaterial);
      newModel = new Group();
      newModel.add(mesh);
      if (mode === "normal") {
        mesh.material = new MeshNormalMaterial({
          flatShading: false,
          side: DoubleSide
        });
      } else if (mode === "wireframe") {
        mesh.material = new MeshBasicMaterial({
          color: 16777215,
          wireframe: true
        });
      }
    }
    const remainingMainModels = [];
    this.scene.traverse((obj) => {
      if (obj.name === "MainModel") {
        remainingMainModels.push(obj);
      }
    });
    remainingMainModels.forEach((obj) => this.scene.remove(obj));
    this.currentModel = newModel;
    newModel.name = "MainModel";
    if (mode === "pointCloud") {
      this.scene.add(newModel);
    } else {
      const box = new Box3().setFromObject(newModel);
      const size = box.getSize(new Vector3());
      const center = box.getCenter(new Vector3());
      const maxDim = Math.max(size.x, size.y, size.z);
      const targetSize = 5;
      const scale = targetSize / maxDim;
      newModel.scale.multiplyScalar(scale);
      box.setFromObject(newModel);
      box.getCenter(center);
      box.getSize(size);
      newModel.position.set(-center.x, -box.min.y, -center.z);
      this.scene.add(newModel);
    }
    this.eventManager.emitEvent("materialModeChange", mode);
  }
  setMaterialMode(mode) {
    if (!this.currentModel || mode === this.materialMode) {
      return;
    }
    this.materialMode = mode;
    if (this.originalModel instanceof BufferGeometry) {
      this.handlePLYModeSwitch(mode);
      return;
    }
    if (mode === "depth") {
      this.renderer.outputColorSpace = LinearSRGBColorSpace;
    } else {
      this.renderer.outputColorSpace = SRGBColorSpace;
    }
    if (this.currentModel) {
      this.currentModel.visible = true;
    }
    this.currentModel.traverse((child) => {
      if (child instanceof Mesh) {
        switch (mode) {
          case "depth":
            if (!this.originalMaterials.has(child)) {
              this.originalMaterials.set(child, child.material);
            }
            const depthMat = new MeshDepthMaterial({
              depthPacking: BasicDepthPacking,
              side: DoubleSide
            });
            depthMat.onBeforeCompile = (shader) => {
              shader.uniforms.cameraType = {
                value: this.activeCamera instanceof OrthographicCamera ? 1 : 0
              };
              shader.fragmentShader = `
                uniform float cameraType;
                ${shader.fragmentShader}
              `;
              shader.fragmentShader = shader.fragmentShader.replace(
                /gl_FragColor\s*=\s*vec4\(\s*vec3\(\s*1.0\s*-\s*fragCoordZ\s*\)\s*,\s*opacity\s*\)\s*;/,
                `
                  float depth = 1.0 - fragCoordZ;
                  if (cameraType > 0.5) {
                    depth = pow(depth, 400.0);
                  } else {
                    depth = pow(depth, 0.6);
                  }
                  gl_FragColor = vec4(vec3(depth), opacity);
                `
              );
            };
            depthMat.customProgramCacheKey = () => {
              return this.activeCamera instanceof OrthographicCamera ? "ortho" : "persp";
            };
            child.material = depthMat;
            break;
          case "normal":
            if (!this.originalMaterials.has(child)) {
              this.originalMaterials.set(child, child.material);
            }
            child.material = new MeshNormalMaterial({
              flatShading: false,
              side: DoubleSide,
              normalScale: new Vector2(1, 1),
              transparent: false,
              opacity: 1
            });
            break;
          case "wireframe":
            if (!this.originalMaterials.has(child)) {
              this.originalMaterials.set(child, child.material);
            }
            child.material = new MeshBasicMaterial({
              color: 16777215,
              wireframe: true,
              transparent: false,
              opacity: 1
            });
            break;
          case "original":
          case "pointCloud":
            const originalMaterial = this.originalMaterials.get(child);
            if (originalMaterial) {
              child.material = originalMaterial;
            } else {
              if (this.appliedTexture) {
                child.material = new MeshStandardMaterial({
                  map: this.appliedTexture,
                  metalness: 0.1,
                  roughness: 0.8,
                  side: DoubleSide
                });
              } else {
                child.material = this.standardMaterial;
              }
            }
            break;
        }
      }
    });
    this.eventManager.emitEvent("materialModeChange", mode);
  }
  setupModelMaterials(model) {
    model.traverse((child) => {
      if (child instanceof Mesh) {
        this.originalMaterials.set(child, child.material);
      }
    });
    this.setMaterialMode("original");
  }
  clearModel() {
    const objectsToRemove = [];
    this.scene.traverse((object) => {
      const isEnvironmentObject = object instanceof GridHelper || object instanceof Light || object instanceof Camera;
      if (!isEnvironmentObject) {
        objectsToRemove.push(object);
      }
    });
    objectsToRemove.forEach((obj) => {
      if (obj.parent && obj.parent !== this.scene) {
        obj.parent.remove(obj);
      } else {
        this.scene.remove(obj);
      }
      if (obj instanceof Mesh) {
        obj.geometry?.dispose();
        if (Array.isArray(obj.material)) {
          obj.material.forEach((material) => material.dispose());
        } else {
          obj.material?.dispose();
        }
      }
    });
    this.reset();
  }
  reset() {
    this.currentModel = null;
    this.originalModel = null;
    this.originalRotation = null;
    this.currentUpDirection = "original";
    this.setMaterialMode("original");
    this.originalFileName = null;
    this.originalURL = null;
    if (this.appliedTexture) {
      this.appliedTexture.dispose();
      this.appliedTexture = null;
    }
    this.originalMaterials = /* @__PURE__ */ new WeakMap();
  }
  addModelToScene(model) {
    this.currentModel = model;
    model.name = "MainModel";
    this.scene.add(this.currentModel);
  }
  async setupModel(model) {
    this.currentModel = model;
    model.name = "MainModel";
    const isSplatModel = this.containsSplatMesh(model);
    if (isSplatModel) {
      this.scene.add(model);
      this.setupCamera(new Vector3(5, 5, 5));
      return;
    }
    const box = new Box3().setFromObject(model);
    const size = box.getSize(new Vector3());
    const center = box.getCenter(new Vector3());
    const maxDim = Math.max(size.x, size.y, size.z);
    const targetSize = 5;
    const scale = targetSize / maxDim;
    model.scale.multiplyScalar(scale);
    box.setFromObject(model);
    box.getCenter(center);
    box.getSize(size);
    model.position.set(-center.x, -box.min.y, -center.z);
    this.scene.add(model);
    if (this.materialMode !== "original") {
      this.setMaterialMode(this.materialMode);
    }
    if (this.currentUpDirection !== "original") {
      this.setUpDirection(this.currentUpDirection);
    }
    this.setupModelMaterials(model);
    this.setupCamera(size);
  }
  containsSplatMesh(model) {
    const target = model ?? this.currentModel;
    if (!target) return false;
    if (target instanceof SplatMesh) return true;
    let found = false;
    target.traverse((child) => {
      if (child instanceof SplatMesh) found = true;
    });
    return found;
  }
  setOriginalModel(model) {
    this.originalModel = model;
  }
  setUpDirection(direction) {
    if (!this.currentModel) return;
    if (!this.originalRotation && this.currentModel.rotation) {
      this.originalRotation = this.currentModel.rotation.clone();
    }
    this.currentUpDirection = direction;
    if (this.originalRotation) {
      this.currentModel.rotation.copy(this.originalRotation);
    }
    switch (direction) {
      case "original":
        break;
      case "-x":
        this.currentModel.rotation.z = Math.PI / 2;
        break;
      case "+x":
        this.currentModel.rotation.z = -Math.PI / 2;
        break;
      case "-y":
        this.currentModel.rotation.x = Math.PI;
        break;
      case "+y":
        break;
      case "-z":
        this.currentModel.rotation.x = Math.PI / 2;
        break;
      case "+z":
        this.currentModel.rotation.x = -Math.PI / 2;
        break;
    }
    this.eventManager.emitEvent("upDirectionChange", direction);
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.SceneModelManager = window.comfyAPI.SceneModelManager || {};
window.comfyAPI.SceneModelManager.SceneModelManager = SceneModelManager;
class ViewHelperManager {
  static {
    __name(this, "ViewHelperManager");
  }
  viewHelper = {};
  viewHelperContainer = {};
  getActiveCamera;
  getControls;
  eventManager;
  // @ts-expect-error unused variable
  renderer;
  constructor(renderer, getActiveCamera, getControls, eventManager) {
    this.renderer = renderer;
    this.getActiveCamera = getActiveCamera;
    this.getControls = getControls;
    this.eventManager = eventManager;
  }
  init() {
  }
  dispose() {
    if (this.viewHelper) {
      this.viewHelper.dispose();
    }
    if (this.viewHelperContainer && this.viewHelperContainer.parentNode) {
      this.viewHelperContainer.parentNode.removeChild(this.viewHelperContainer);
    }
  }
  createViewHelper(container) {
    this.viewHelperContainer = document.createElement("div");
    this.viewHelperContainer.style.position = "absolute";
    this.viewHelperContainer.style.bottom = "0";
    this.viewHelperContainer.style.left = "0";
    this.viewHelperContainer.style.width = "128px";
    this.viewHelperContainer.style.height = "128px";
    this.viewHelperContainer.addEventListener("pointerup", (event) => {
      event.stopPropagation();
      this.viewHelper.handleClick(event);
    });
    this.viewHelperContainer.addEventListener("pointerdown", (event) => {
      event.stopPropagation();
    });
    container.appendChild(this.viewHelperContainer);
    this.viewHelper = new ViewHelper(
      this.getActiveCamera(),
      this.viewHelperContainer
    );
    this.viewHelper.center = this.getControls().target;
  }
  update(delta2) {
    if (this.viewHelper.animating) {
      this.viewHelper.update(delta2);
      if (!this.viewHelper.animating) {
        const cameraState = {
          position: this.getActiveCamera().position.clone(),
          target: this.getControls().target.clone(),
          zoom: this.getActiveCamera() instanceof OrthographicCamera ? this.getActiveCamera().zoom : this.getActiveCamera().zoom,
          cameraType: this.getActiveCamera() instanceof PerspectiveCamera ? "perspective" : "orthographic"
        };
        this.eventManager.emitEvent("cameraChanged", cameraState);
      }
    }
  }
  handleResize() {
  }
  visibleViewHelper(visible) {
    if (visible) {
      this.viewHelper.visible = true;
      this.viewHelperContainer.style.display = "block";
    } else {
      this.viewHelper.visible = false;
      this.viewHelperContainer.style.display = "none";
    }
  }
  recreateViewHelper() {
    if (this.viewHelper) {
      this.viewHelper.dispose();
    }
    this.viewHelper = new ViewHelper(
      this.getActiveCamera(),
      this.viewHelperContainer
    );
    this.viewHelper.center = this.getControls().target;
  }
  reset() {
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.ViewHelperManager = window.comfyAPI.ViewHelperManager || {};
window.comfyAPI.ViewHelperManager.ViewHelperManager = ViewHelperManager;
class Load3d {
  static {
    __name(this, "Load3d");
  }
  renderer;
  clock;
  animationFrameId = null;
  loadingPromise = null;
  onContextMenuCallback;
  getDimensionsCallback;
  eventManager;
  sceneManager;
  cameraManager;
  controlsManager;
  lightingManager;
  viewHelperManager;
  loaderManager;
  modelManager;
  recordingManager;
  animationManager;
  STATUS_MOUSE_ON_NODE;
  STATUS_MOUSE_ON_SCENE;
  STATUS_MOUSE_ON_VIEWER;
  INITIAL_RENDER_DONE = false;
  targetWidth = 0;
  targetHeight = 0;
  targetAspectRatio = 1;
  isViewerMode = false;
  // Context menu tracking
  rightMouseDownX = 0;
  rightMouseDownY = 0;
  rightMouseMoved = false;
  dragThreshold = 5;
  contextMenuAbortController = null;
  constructor(container, options = {}) {
    this.clock = new Clock();
    this.isViewerMode = options.isViewerMode || false;
    this.onContextMenuCallback = options.onContextMenu;
    this.getDimensionsCallback = options.getDimensions;
    if (options.width && options.height) {
      this.targetWidth = options.width;
      this.targetHeight = options.height;
      this.targetAspectRatio = options.width / options.height;
    }
    this.renderer = new WebGLRenderer({ alpha: true, antialias: true });
    this.renderer.setSize(300, 300);
    this.renderer.setClearColor(2631720);
    this.renderer.autoClear = false;
    this.renderer.outputColorSpace = SRGBColorSpace;
    this.renderer.domElement.classList.add(
      "absolute",
      "inset-0",
      "h-full",
      "w-full",
      "outline-none"
    );
    container.appendChild(this.renderer.domElement);
    this.eventManager = new EventManager();
    this.sceneManager = new SceneManager(
      this.renderer,
      this.getActiveCamera.bind(this),
      this.getControls.bind(this),
      this.eventManager
    );
    this.cameraManager = new CameraManager(this.renderer, this.eventManager);
    this.controlsManager = new ControlsManager(
      this.renderer,
      this.cameraManager.activeCamera,
      this.eventManager
    );
    this.cameraManager.setControls(this.controlsManager.controls);
    this.lightingManager = new LightingManager(
      this.sceneManager.scene,
      this.eventManager
    );
    this.viewHelperManager = new ViewHelperManager(
      this.renderer,
      this.getActiveCamera.bind(this),
      this.getControls.bind(this),
      this.eventManager
    );
    this.modelManager = new SceneModelManager(
      this.sceneManager.scene,
      this.renderer,
      this.eventManager,
      this.getActiveCamera.bind(this),
      this.setupCamera.bind(this)
    );
    this.loaderManager = new LoaderManager(this.modelManager, this.eventManager);
    this.recordingManager = new RecordingManager(
      this.sceneManager.scene,
      this.renderer,
      this.eventManager
    );
    this.animationManager = new AnimationManager(this.eventManager);
    this.sceneManager.init();
    this.cameraManager.init();
    this.controlsManager.init();
    this.lightingManager.init();
    this.loaderManager.init();
    this.animationManager.init();
    this.viewHelperManager.createViewHelper(container);
    this.viewHelperManager.init();
    this.STATUS_MOUSE_ON_NODE = false;
    this.STATUS_MOUSE_ON_SCENE = false;
    this.STATUS_MOUSE_ON_VIEWER = false;
    this.initContextMenu();
    this.handleResize();
    this.startAnimation();
    setTimeout(() => {
      this.forceRender();
    }, 100);
  }
  /**
   * Initialize context menu on the Three.js canvas
   * Detects right-click vs right-drag to show menu only on click
   */
  initContextMenu() {
    const canvas2 = this.renderer.domElement;
    this.contextMenuAbortController = new AbortController();
    const { signal } = this.contextMenuAbortController;
    const mousedownHandler = /* @__PURE__ */ __name((e2) => {
      if (e2.button === 2) {
        this.rightMouseDownX = e2.clientX;
        this.rightMouseDownY = e2.clientY;
        this.rightMouseMoved = false;
      }
    }, "mousedownHandler");
    const mousemoveHandler = /* @__PURE__ */ __name((e2) => {
      if (e2.buttons === 2) {
        const dx = Math.abs(e2.clientX - this.rightMouseDownX);
        const dy = Math.abs(e2.clientY - this.rightMouseDownY);
        if (dx > this.dragThreshold || dy > this.dragThreshold) {
          this.rightMouseMoved = true;
        }
      }
    }, "mousemoveHandler");
    const contextmenuHandler = /* @__PURE__ */ __name((e2) => {
      if (this.isViewerMode) return;
      const dx = Math.abs(e2.clientX - this.rightMouseDownX);
      const dy = Math.abs(e2.clientY - this.rightMouseDownY);
      const wasDragging = this.rightMouseMoved || dx > this.dragThreshold || dy > this.dragThreshold;
      this.rightMouseMoved = false;
      if (wasDragging) {
        return;
      }
      e2.preventDefault();
      e2.stopPropagation();
      this.showNodeContextMenu(e2);
    }, "contextmenuHandler");
    canvas2.addEventListener("mousedown", mousedownHandler, { signal });
    canvas2.addEventListener("mousemove", mousemoveHandler, { signal });
    canvas2.addEventListener("contextmenu", contextmenuHandler, { signal });
  }
  showNodeContextMenu(event) {
    if (this.onContextMenuCallback) {
      this.onContextMenuCallback(event);
    }
  }
  getEventManager() {
    return this.eventManager;
  }
  getSceneManager() {
    return this.sceneManager;
  }
  getCameraManager() {
    return this.cameraManager;
  }
  getControlsManager() {
    return this.controlsManager;
  }
  getLightingManager() {
    return this.lightingManager;
  }
  getViewHelperManager() {
    return this.viewHelperManager;
  }
  getLoaderManager() {
    return this.loaderManager;
  }
  getModelManager() {
    return this.modelManager;
  }
  getRecordingManager() {
    return this.recordingManager;
  }
  getTargetSize() {
    return {
      width: this.targetWidth,
      height: this.targetHeight
    };
  }
  shouldMaintainAspectRatio() {
    return this.isViewerMode || this.targetWidth > 0 && this.targetHeight > 0;
  }
  forceRender() {
    const delta2 = this.clock.getDelta();
    this.animationManager.update(delta2);
    this.viewHelperManager.update(delta2);
    this.controlsManager.update();
    this.renderMainScene();
    this.resetViewport();
    if (this.viewHelperManager.viewHelper.render) {
      this.viewHelperManager.viewHelper.render(this.renderer);
    }
    this.INITIAL_RENDER_DONE = true;
  }
  renderMainScene() {
    const containerWidth = this.renderer.domElement.clientWidth;
    const containerHeight = this.renderer.domElement.clientHeight;
    if (this.getDimensionsCallback) {
      const dims = this.getDimensionsCallback();
      if (dims) {
        this.targetWidth = dims.width;
        this.targetHeight = dims.height;
        this.targetAspectRatio = dims.width / dims.height;
      }
    }
    if (this.shouldMaintainAspectRatio()) {
      const containerAspectRatio = containerWidth / containerHeight;
      let renderWidth;
      let renderHeight;
      let offsetX = 0;
      let offsetY = 0;
      if (containerAspectRatio > this.targetAspectRatio) {
        renderHeight = containerHeight;
        renderWidth = renderHeight * this.targetAspectRatio;
        offsetX = (containerWidth - renderWidth) / 2;
      } else {
        renderWidth = containerWidth;
        renderHeight = renderWidth / this.targetAspectRatio;
        offsetY = (containerHeight - renderHeight) / 2;
      }
      this.renderer.setViewport(0, 0, containerWidth, containerHeight);
      this.renderer.setScissor(0, 0, containerWidth, containerHeight);
      this.renderer.setScissorTest(true);
      this.renderer.setClearColor(657930);
      this.renderer.clear();
      this.renderer.setViewport(offsetX, offsetY, renderWidth, renderHeight);
      this.renderer.setScissor(offsetX, offsetY, renderWidth, renderHeight);
      const renderAspectRatio = renderWidth / renderHeight;
      this.cameraManager.updateAspectRatio(renderAspectRatio);
    } else {
      this.renderer.setViewport(0, 0, containerWidth, containerHeight);
      this.renderer.setScissor(0, 0, containerWidth, containerHeight);
      this.renderer.setScissorTest(true);
    }
    this.sceneManager.renderBackground();
    this.renderer.render(
      this.sceneManager.scene,
      this.cameraManager.activeCamera
    );
  }
  resetViewport() {
    const width2 = this.renderer.domElement.clientWidth;
    const height = this.renderer.domElement.clientHeight;
    this.renderer.setViewport(0, 0, width2, height);
    this.renderer.setScissor(0, 0, width2, height);
    this.renderer.setScissorTest(false);
  }
  getActiveCamera() {
    return this.cameraManager.activeCamera;
  }
  getControls() {
    return this.controlsManager.controls;
  }
  setupCamera(size) {
    this.cameraManager.setupForModel(size);
  }
  startAnimation() {
    const animate = /* @__PURE__ */ __name(() => {
      this.animationFrameId = requestAnimationFrame(animate);
      if (!this.isActive()) {
        return;
      }
      const delta2 = this.clock.getDelta();
      this.animationManager.update(delta2);
      this.viewHelperManager.update(delta2);
      this.controlsManager.update();
      this.renderMainScene();
      this.resetViewport();
      if (this.viewHelperManager.viewHelper.render) {
        this.viewHelperManager.viewHelper.render(this.renderer);
      }
    }, "animate");
    animate();
  }
  updateStatusMouseOnNode(onNode) {
    this.STATUS_MOUSE_ON_NODE = onNode;
  }
  updateStatusMouseOnScene(onScene) {
    this.STATUS_MOUSE_ON_SCENE = onScene;
  }
  updateStatusMouseOnViewer(onViewer) {
    this.STATUS_MOUSE_ON_VIEWER = onViewer;
  }
  isActive() {
    return this.STATUS_MOUSE_ON_NODE || this.STATUS_MOUSE_ON_SCENE || this.STATUS_MOUSE_ON_VIEWER || this.isRecording() || !this.INITIAL_RENDER_DONE || this.animationManager.isAnimationPlaying;
  }
  async exportModel(format2) {
    if (!this.modelManager.currentModel) {
      throw new Error("No model to export");
    }
    const exportMessage = `Exporting as ${format2.toUpperCase()}...`;
    this.eventManager.emitEvent("exportLoadingStart", exportMessage);
    try {
      const model = this.modelManager.currentModel.clone();
      const originalFileName = this.modelManager.originalFileName || "model";
      const filename = `${originalFileName}.${format2}`;
      const originalURL = this.modelManager.originalURL;
      await new Promise((resolve) => setTimeout(resolve, 10));
      switch (format2) {
        case "glb":
          await ModelExporter.exportGLB(model, filename, originalURL);
          break;
        case "obj":
          await ModelExporter.exportOBJ(model, filename, originalURL);
          break;
        case "stl":
          ;
          await ModelExporter.exportSTL(model, filename), originalURL;
          break;
        default:
          throw new Error(`Unsupported export format: ${format2}`);
      }
      await new Promise((resolve) => setTimeout(resolve, 10));
    } catch (error) {
      console.error(`Error exporting model as ${format2}:`, error);
      throw error;
    } finally {
      this.eventManager.emitEvent("exportLoadingEnd", null);
    }
  }
  setBackgroundColor(color2) {
    this.sceneManager.setBackgroundColor(color2);
    this.forceRender();
  }
  async setBackgroundImage(uploadPath) {
    await this.sceneManager.setBackgroundImage(uploadPath);
    if (this.sceneManager.backgroundTexture && this.sceneManager.backgroundMesh) {
      const containerWidth = this.renderer.domElement.clientWidth;
      const containerHeight = this.renderer.domElement.clientHeight;
      if (this.shouldMaintainAspectRatio()) {
        const containerAspectRatio = containerWidth / containerHeight;
        let renderWidth;
        let renderHeight;
        if (containerAspectRatio > this.targetAspectRatio) {
          renderHeight = containerHeight;
          renderWidth = renderHeight * this.targetAspectRatio;
        } else {
          renderWidth = containerWidth;
          renderHeight = renderWidth / this.targetAspectRatio;
        }
        this.sceneManager.updateBackgroundSize(
          this.sceneManager.backgroundTexture,
          this.sceneManager.backgroundMesh,
          renderWidth,
          renderHeight
        );
      } else {
        this.sceneManager.updateBackgroundSize(
          this.sceneManager.backgroundTexture,
          this.sceneManager.backgroundMesh,
          containerWidth,
          containerHeight
        );
      }
    }
    this.forceRender();
  }
  removeBackgroundImage() {
    this.sceneManager.removeBackgroundImage();
    this.forceRender();
  }
  toggleGrid(showGrid) {
    this.sceneManager.toggleGrid(showGrid);
    this.forceRender();
  }
  setBackgroundRenderMode(mode) {
    this.sceneManager.setBackgroundRenderMode(mode);
    this.forceRender();
  }
  toggleCamera(cameraType) {
    this.cameraManager.toggleCamera(cameraType);
    this.controlsManager.updateCamera(this.cameraManager.activeCamera);
    this.viewHelperManager.recreateViewHelper();
    this.handleResize();
    this.forceRender();
  }
  getCurrentCameraType() {
    return this.cameraManager.getCurrentCameraType();
  }
  getCurrentModel() {
    return this.modelManager.currentModel;
  }
  setCameraState(state2) {
    this.cameraManager.setCameraState(state2);
    this.forceRender();
  }
  getCameraState() {
    return this.cameraManager.getCameraState();
  }
  setFOV(fov) {
    this.cameraManager.setFOV(fov);
    this.forceRender();
  }
  setMaterialMode(mode) {
    this.modelManager.setMaterialMode(mode);
    this.forceRender();
  }
  async loadModel(url, originalFileName) {
    if (this.loadingPromise) {
      try {
        await this.loadingPromise;
      } catch (e2) {
      }
    }
    this.loadingPromise = this._loadModelInternal(url, originalFileName);
    return this.loadingPromise;
  }
  async _loadModelInternal(url, originalFileName) {
    this.cameraManager.reset();
    this.controlsManager.reset();
    this.modelManager.clearModel();
    this.animationManager.dispose();
    await this.loaderManager.loadModel(url, originalFileName);
    if (this.modelManager.currentModel) {
      this.animationManager.setupModelAnimations(
        this.modelManager.currentModel,
        this.modelManager.originalModel
      );
    }
    this.handleResize();
    this.forceRender();
    this.loadingPromise = null;
  }
  isSplatModel() {
    return this.modelManager.containsSplatMesh();
  }
  isPlyModel() {
    return this.modelManager.originalModel instanceof BufferGeometry;
  }
  clearModel() {
    this.animationManager.dispose();
    this.modelManager.clearModel();
    this.forceRender();
  }
  setUpDirection(direction) {
    this.modelManager.setUpDirection(direction);
    this.forceRender();
  }
  setLightIntensity(intensity) {
    this.lightingManager.setLightIntensity(intensity);
    this.forceRender();
  }
  setTargetSize(width2, height) {
    this.targetWidth = width2;
    this.targetHeight = height;
    this.targetAspectRatio = width2 / height;
    this.handleResize();
    this.forceRender();
  }
  addEventListener(event, callback) {
    this.eventManager.addEventListener(event, callback);
  }
  removeEventListener(event, callback) {
    this.eventManager.removeEventListener(event, callback);
  }
  refreshViewport() {
    this.handleResize();
    this.forceRender();
  }
  handleResize() {
    const parentElement = this.renderer?.domElement?.parentElement;
    if (!parentElement) {
      console.warn("Parent element not found");
      return;
    }
    const containerWidth = parentElement.clientWidth;
    const containerHeight = parentElement.clientHeight;
    if (this.getDimensionsCallback) {
      const dims = this.getDimensionsCallback();
      if (dims) {
        this.targetWidth = dims.width;
        this.targetHeight = dims.height;
        this.targetAspectRatio = dims.width / dims.height;
      }
    }
    if (this.shouldMaintainAspectRatio()) {
      const containerAspectRatio = containerWidth / containerHeight;
      let renderWidth;
      let renderHeight;
      if (containerAspectRatio > this.targetAspectRatio) {
        renderHeight = containerHeight;
        renderWidth = renderHeight * this.targetAspectRatio;
      } else {
        renderWidth = containerWidth;
        renderHeight = renderWidth / this.targetAspectRatio;
      }
      this.renderer.setSize(containerWidth, containerHeight);
      this.cameraManager.handleResize(renderWidth, renderHeight);
      this.sceneManager.handleResize(renderWidth, renderHeight);
    } else {
      this.renderer.setSize(containerWidth, containerHeight);
      this.cameraManager.handleResize(containerWidth, containerHeight);
      this.sceneManager.handleResize(containerWidth, containerHeight);
    }
    this.forceRender();
  }
  captureScene(width2, height) {
    return this.sceneManager.captureScene(width2, height);
  }
  async startRecording() {
    this.viewHelperManager.visibleViewHelper(false);
    return this.recordingManager.startRecording(
      this.targetWidth,
      this.targetHeight
    );
  }
  stopRecording() {
    this.viewHelperManager.visibleViewHelper(true);
    this.recordingManager.stopRecording();
    this.eventManager.emitEvent("recordingStatusChange", false);
  }
  isRecording() {
    return this.recordingManager.getIsRecording();
  }
  getRecordingDuration() {
    return this.recordingManager.getRecordingDuration();
  }
  getRecordingData() {
    return this.recordingManager.getRecordingData();
  }
  exportRecording(filename) {
    this.recordingManager.exportRecording(filename);
  }
  clearRecording() {
    this.recordingManager.clearRecording();
  }
  // Animation methods
  setAnimationSpeed(speed) {
    this.animationManager.setAnimationSpeed(speed);
  }
  updateSelectedAnimation(index2) {
    this.animationManager.updateSelectedAnimation(index2);
  }
  toggleAnimation(play) {
    this.animationManager.toggleAnimation(play);
  }
  hasAnimations() {
    return this.animationManager.animationClips.length > 0;
  }
  remove() {
    if (this.contextMenuAbortController) {
      this.contextMenuAbortController.abort();
      this.contextMenuAbortController = null;
    }
    this.renderer.forceContextLoss();
    const canvas2 = this.renderer.domElement;
    const event = new Event("webglcontextlost", {
      bubbles: true,
      cancelable: true
    });
    canvas2.dispatchEvent(event);
    if (this.animationFrameId !== null) {
      cancelAnimationFrame(this.animationFrameId);
    }
    this.sceneManager.dispose();
    this.cameraManager.dispose();
    this.controlsManager.dispose();
    this.lightingManager.dispose();
    this.viewHelperManager.dispose();
    this.loaderManager.dispose();
    this.modelManager.dispose();
    this.recordingManager.dispose();
    this.animationManager.dispose();
    this.renderer.dispose();
    this.renderer.domElement.remove();
  }
}
const nodeToLoad3dMap = /* @__PURE__ */ new Map();
const pendingCallbacks = /* @__PURE__ */ new Map();
const useLoad3d = /* @__PURE__ */ __name((nodeOrRef) => {
  const nodeRef = toRef$1(nodeOrRef);
  let load3d2 = null;
  const sceneConfig = ref({
    showGrid: true,
    backgroundColor: "#000000",
    backgroundImage: "",
    backgroundRenderMode: "tiled"
  });
  const modelConfig = ref({
    upDirection: "original",
    materialMode: "original"
  });
  const cameraConfig = ref({
    cameraType: "perspective",
    fov: 75
  });
  const lightConfig = ref({
    intensity: 5
  });
  const isRecording = ref(false);
  const hasRecording = ref(false);
  const recordingDuration = ref(0);
  const animations = ref([]);
  const playing = ref(false);
  const selectedSpeed = ref(1);
  const selectedAnimation = ref(0);
  const loading = ref(false);
  const loadingMessage = ref("");
  const isPreview = ref(false);
  const isSplatModel = ref(false);
  const isPlyModel = ref(false);
  const initializeLoad3d = /* @__PURE__ */ __name(async (containerRef) => {
    const rawNode = toRaw(nodeRef.value);
    if (!containerRef || !rawNode) return;
    const node2 = rawNode;
    try {
      const widthWidget = node2.widgets?.find((w) => w.name === "width");
      const heightWidget = node2.widgets?.find((w) => w.name === "height");
      if (!(widthWidget && heightWidget)) {
        isPreview.value = true;
      }
      load3d2 = new Load3d(containerRef, {
        width: widthWidget?.value,
        height: heightWidget?.value,
        // Provide dynamic dimension getter for reactive updates
        getDimensions: widthWidget && heightWidget ? () => ({
          width: widthWidget.value,
          height: heightWidget.value
        }) : void 0,
        onContextMenu: /* @__PURE__ */ __name((event) => {
          const menuOptions = app$1.canvas.getNodeMenuOptions(node2);
          new LiteGraph.ContextMenu(menuOptions, {
            event,
            title: node2.type,
            extra: node2
          });
        }, "onContextMenu")
      });
      await restoreConfigurationsFromNode(node2);
      node2.onMouseEnter = function() {
        load3d2?.refreshViewport();
        load3d2?.updateStatusMouseOnNode(true);
      };
      node2.onMouseLeave = function() {
        load3d2?.updateStatusMouseOnNode(false);
      };
      node2.onResize = function() {
        load3d2?.handleResize();
      };
      node2.onDrawBackground = function() {
        if (load3d2) {
          load3d2.renderer.domElement.hidden = this.flags.collapsed ?? false;
        }
      };
      node2.onRemoved = function() {
        useLoad3dService().removeLoad3d(node2);
        pendingCallbacks.delete(node2);
      };
      nodeToLoad3dMap.set(node2, load3d2);
      const callbacks = pendingCallbacks.get(node2);
      if (callbacks && load3d2) {
        callbacks.forEach((callback) => {
          if (load3d2) {
            callback(load3d2);
          }
        });
        pendingCallbacks.delete(node2);
      }
      handleEvents("add");
    } catch (error) {
      console.error("Error initializing Load3d:", error);
      useToastStore().addAlert(t("toastMessages.failedToInitializeLoad3d"));
    }
  }, "initializeLoad3d");
  const restoreConfigurationsFromNode = /* @__PURE__ */ __name(async (node2) => {
    if (!load3d2) return;
    const savedSceneConfig = node2.properties["Scene Config"];
    if (savedSceneConfig) {
      sceneConfig.value = {
        ...sceneConfig.value,
        ...savedSceneConfig,
        backgroundRenderMode: savedSceneConfig.backgroundRenderMode || "tiled"
      };
    }
    const savedModelConfig = node2.properties["Model Config"];
    if (savedModelConfig) {
      modelConfig.value = savedModelConfig;
    }
    const savedCameraConfig = node2.properties["Camera Config"];
    const cameraStateToRestore = savedCameraConfig?.state;
    if (savedCameraConfig) {
      cameraConfig.value = savedCameraConfig;
    }
    const savedLightConfig = node2.properties["Light Config"];
    if (savedLightConfig) {
      lightConfig.value = savedLightConfig;
    }
    const modelWidget = node2.widgets?.find((w) => w.name === "model_file");
    if (modelWidget?.value) {
      const modelUrl = getModelUrl(modelWidget.value);
      if (modelUrl) {
        loading.value = true;
        loadingMessage.value = t("load3d.reloadingModel");
        try {
          await load3d2.loadModel(modelUrl);
          if (cameraStateToRestore) {
            await nextTick();
            load3d2.setCameraState(cameraStateToRestore);
          }
        } catch (error) {
          console.error("Failed to reload model:", error);
          useToastStore().addAlert(t("toastMessages.failedToLoadModel"));
        } finally {
          loading.value = false;
          loadingMessage.value = "";
        }
      }
    } else if (cameraStateToRestore) {
      load3d2.setCameraState(cameraStateToRestore);
    }
  }, "restoreConfigurationsFromNode");
  const getModelUrl = /* @__PURE__ */ __name((modelPath) => {
    if (!modelPath) return null;
    try {
      if (modelPath.startsWith("http")) {
        return modelPath;
      }
      const [subfolder, filename] = Load3dUtils.splitFilePath(modelPath);
      return api.apiURL(
        Load3dUtils.getResourceURL(
          subfolder,
          filename,
          isPreview.value ? "output" : "input"
        )
      );
    } catch (error) {
      console.error("Failed to construct model URL:", error);
      return null;
    }
  }, "getModelUrl");
  const waitForLoad3d = /* @__PURE__ */ __name((callback) => {
    const rawNode = toRaw(nodeRef.value);
    if (!rawNode) return;
    const node2 = rawNode;
    const existingInstance = nodeToLoad3dMap.get(node2);
    if (existingInstance) {
      callback(existingInstance);
      return;
    }
    if (!pendingCallbacks.has(node2)) {
      pendingCallbacks.set(node2, []);
    }
    pendingCallbacks.get(node2).push(callback);
  }, "waitForLoad3d");
  watch(
    sceneConfig,
    async (newValue2) => {
      if (load3d2 && nodeRef.value) {
        nodeRef.value.properties["Scene Config"] = newValue2;
        load3d2.toggleGrid(newValue2.showGrid);
        load3d2.setBackgroundColor(newValue2.backgroundColor);
        await load3d2.setBackgroundImage(newValue2.backgroundImage || "");
        if (newValue2.backgroundRenderMode) {
          load3d2.setBackgroundRenderMode(newValue2.backgroundRenderMode);
        }
      }
    },
    { deep: true }
  );
  watch(
    modelConfig,
    (newValue2) => {
      if (load3d2 && nodeRef.value) {
        nodeRef.value.properties["Model Config"] = newValue2;
        load3d2.setUpDirection(newValue2.upDirection);
        load3d2.setMaterialMode(newValue2.materialMode);
      }
    },
    { deep: true }
  );
  watch(
    cameraConfig,
    (newValue2) => {
      if (load3d2 && nodeRef.value) {
        nodeRef.value.properties["Camera Config"] = newValue2;
        load3d2.toggleCamera(newValue2.cameraType);
        load3d2.setFOV(newValue2.fov);
      }
    },
    { deep: true }
  );
  watch(
    lightConfig,
    (newValue2) => {
      if (load3d2 && nodeRef.value) {
        nodeRef.value.properties["Light Config"] = newValue2;
        load3d2.setLightIntensity(newValue2.intensity);
      }
    },
    { deep: true }
  );
  watch(playing, (newValue2) => {
    if (load3d2) {
      load3d2.toggleAnimation(newValue2);
    }
  });
  watch(selectedSpeed, (newValue2) => {
    if (load3d2 && newValue2) {
      load3d2.setAnimationSpeed(newValue2);
    }
  });
  watch(selectedAnimation, (newValue2) => {
    if (load3d2 && newValue2 !== void 0) {
      load3d2.updateSelectedAnimation(newValue2);
    }
  });
  const handleMouseEnter = /* @__PURE__ */ __name(() => {
    load3d2?.updateStatusMouseOnScene(true);
  }, "handleMouseEnter");
  const handleMouseLeave = /* @__PURE__ */ __name(() => {
    load3d2?.updateStatusMouseOnScene(false);
  }, "handleMouseLeave");
  const handleStartRecording = /* @__PURE__ */ __name(async () => {
    if (load3d2) {
      await load3d2.startRecording();
      isRecording.value = true;
    }
  }, "handleStartRecording");
  const handleStopRecording = /* @__PURE__ */ __name(() => {
    if (load3d2) {
      load3d2.stopRecording();
      isRecording.value = false;
      recordingDuration.value = load3d2.getRecordingDuration();
      hasRecording.value = recordingDuration.value > 0;
    }
  }, "handleStopRecording");
  const handleExportRecording = /* @__PURE__ */ __name(() => {
    if (load3d2) {
      const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
      const filename = `${timestamp}-scene-recording.mp4`;
      load3d2.exportRecording(filename);
    }
  }, "handleExportRecording");
  const handleClearRecording = /* @__PURE__ */ __name(() => {
    if (load3d2) {
      load3d2.clearRecording();
      hasRecording.value = false;
      recordingDuration.value = 0;
    }
  }, "handleClearRecording");
  const handleBackgroundImageUpdate = /* @__PURE__ */ __name(async (file) => {
    if (!file) {
      sceneConfig.value.backgroundImage = "";
      await load3d2?.setBackgroundImage("");
      return;
    }
    const resourceFolder = nodeRef.value?.properties["Resource Folder"] || "";
    const subfolder = resourceFolder.trim() ? `3d/${resourceFolder.trim()}` : "3d";
    const uploadedPath = await Load3dUtils.uploadFile(file, subfolder);
    sceneConfig.value.backgroundImage = uploadedPath;
    await load3d2?.setBackgroundImage(uploadedPath);
  }, "handleBackgroundImageUpdate");
  const handleExportModel = /* @__PURE__ */ __name(async (format2) => {
    if (!load3d2) {
      useToastStore().addAlert(t("toastMessages.no3dSceneToExport"));
      return;
    }
    try {
      await load3d2.exportModel(format2);
    } catch (error) {
      console.error("Error exporting model:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToExportModel", {
          format: format2.toUpperCase()
        })
      );
    }
  }, "handleExportModel");
  const handleModelDrop = /* @__PURE__ */ __name(async (file) => {
    if (!load3d2) {
      useToastStore().addAlert(t("toastMessages.no3dScene"));
      return;
    }
    const node2 = toRaw(nodeRef.value);
    if (!node2) return;
    try {
      const resourceFolder = node2.properties["Resource Folder"] || "";
      const subfolder = resourceFolder.trim() ? `3d/${resourceFolder.trim()}` : "3d";
      loading.value = true;
      loadingMessage.value = t("load3d.uploadingModel");
      const uploadedPath = await Load3dUtils.uploadFile(file, subfolder);
      if (!uploadedPath) {
        useToastStore().addAlert(t("toastMessages.fileUploadFailed"));
        return;
      }
      const modelUrl = api.apiURL(
        Load3dUtils.getResourceURL(
          ...Load3dUtils.splitFilePath(uploadedPath),
          isPreview.value ? "output" : "input"
        )
      );
      loadingMessage.value = t("load3d.loadingModel");
      await load3d2.loadModel(modelUrl);
      const modelWidget = node2.widgets?.find((w) => w.name === "model_file");
      if (modelWidget) {
        const options = modelWidget.options;
        if (options?.values && !options.values.includes(uploadedPath)) {
          options.values.push(uploadedPath);
        }
        modelWidget.value = uploadedPath;
      }
    } catch (error) {
      console.error("Model drop failed:", error);
      useToastStore().addAlert(t("toastMessages.failedToLoadModel"));
    } finally {
      loading.value = false;
      loadingMessage.value = "";
    }
  }, "handleModelDrop");
  const eventConfig = {
    materialModeChange: /* @__PURE__ */ __name((value) => {
      modelConfig.value.materialMode = value;
    }, "materialModeChange"),
    backgroundColorChange: /* @__PURE__ */ __name((value) => {
      sceneConfig.value.backgroundColor = value;
    }, "backgroundColorChange"),
    backgroundRenderModeChange: /* @__PURE__ */ __name((value) => {
      sceneConfig.value.backgroundRenderMode = value;
    }, "backgroundRenderModeChange"),
    lightIntensityChange: /* @__PURE__ */ __name((value) => {
      lightConfig.value.intensity = value;
    }, "lightIntensityChange"),
    fovChange: /* @__PURE__ */ __name((value) => {
      cameraConfig.value.fov = value;
    }, "fovChange"),
    cameraTypeChange: /* @__PURE__ */ __name((value) => {
      cameraConfig.value.cameraType = value;
    }, "cameraTypeChange"),
    showGridChange: /* @__PURE__ */ __name((value) => {
      sceneConfig.value.showGrid = value;
    }, "showGridChange"),
    upDirectionChange: /* @__PURE__ */ __name((value) => {
      modelConfig.value.upDirection = value;
    }, "upDirectionChange"),
    backgroundImageChange: /* @__PURE__ */ __name((value) => {
      sceneConfig.value.backgroundImage = value;
    }, "backgroundImageChange"),
    backgroundImageLoadingStart: /* @__PURE__ */ __name(() => {
      loadingMessage.value = t("load3d.loadingBackgroundImage");
      loading.value = true;
    }, "backgroundImageLoadingStart"),
    backgroundImageLoadingEnd: /* @__PURE__ */ __name(() => {
      loadingMessage.value = "";
      loading.value = false;
    }, "backgroundImageLoadingEnd"),
    modelLoadingStart: /* @__PURE__ */ __name(() => {
      loadingMessage.value = t("load3d.loadingModel");
      loading.value = true;
    }, "modelLoadingStart"),
    modelLoadingEnd: /* @__PURE__ */ __name(() => {
      loadingMessage.value = "";
      loading.value = false;
      isSplatModel.value = load3d2?.isSplatModel() ?? false;
      isPlyModel.value = load3d2?.isPlyModel() ?? false;
    }, "modelLoadingEnd"),
    exportLoadingStart: /* @__PURE__ */ __name((message) => {
      loadingMessage.value = message || t("load3d.exportingModel");
      loading.value = true;
    }, "exportLoadingStart"),
    exportLoadingEnd: /* @__PURE__ */ __name(() => {
      loadingMessage.value = "";
      loading.value = false;
    }, "exportLoadingEnd"),
    recordingStatusChange: /* @__PURE__ */ __name((value) => {
      isRecording.value = value;
      if (!value && load3d2) {
        recordingDuration.value = load3d2.getRecordingDuration();
        hasRecording.value = recordingDuration.value > 0;
      }
    }, "recordingStatusChange"),
    animationListChange: /* @__PURE__ */ __name((newValue2) => {
      animations.value = newValue2;
    }, "animationListChange"),
    cameraChanged: /* @__PURE__ */ __name((cameraState) => {
      const rawNode = toRaw(nodeRef.value);
      if (rawNode) {
        const node2 = rawNode;
        if (!node2.properties) node2.properties = {};
        const cameraConfigProp = node2.properties["Camera Config"];
        if (cameraConfigProp) {
          cameraConfigProp.state = cameraState;
        } else {
          node2.properties["Camera Config"] = {
            cameraType: cameraConfig.value.cameraType,
            fov: cameraConfig.value.fov,
            state: cameraState
          };
        }
      }
    }, "cameraChanged")
  };
  const handleEvents = /* @__PURE__ */ __name((action) => {
    Object.entries(eventConfig).forEach(([event, handler]) => {
      const method = `${action}EventListener`;
      load3d2?.[method](event, handler);
    });
  }, "handleEvents");
  const cleanup = /* @__PURE__ */ __name(() => {
    handleEvents("remove");
    const rawNode = toRaw(nodeRef.value);
    if (!rawNode) return;
    const node2 = rawNode;
    if (nodeToLoad3dMap.get(node2) === load3d2) {
      nodeToLoad3dMap.delete(node2);
    }
    load3d2?.remove();
    load3d2 = null;
  }, "cleanup");
  return {
    // state
    load3d: load3d2,
    sceneConfig,
    modelConfig,
    cameraConfig,
    lightConfig,
    isRecording,
    isPreview,
    isSplatModel,
    isPlyModel,
    hasRecording,
    recordingDuration,
    animations,
    playing,
    selectedSpeed,
    selectedAnimation,
    loading,
    loadingMessage,
    // Methods
    initializeLoad3d,
    waitForLoad3d,
    handleMouseEnter,
    handleMouseLeave,
    handleStartRecording,
    handleStopRecording,
    handleExportRecording,
    handleClearRecording,
    handleBackgroundImageUpdate,
    handleExportModel,
    handleModelDrop,
    cleanup
  };
}, "useLoad3d");
const viewerInstances = /* @__PURE__ */ new Map();
class Load3dService {
  static {
    __name(this, "Load3dService");
  }
  static instance;
  constructor() {
  }
  static getInstance() {
    if (!Load3dService.instance) {
      Load3dService.instance = new Load3dService();
    }
    return Load3dService.instance;
  }
  getLoad3d(node2) {
    const rawNode = toRaw(node2);
    return nodeToLoad3dMap.get(rawNode) || null;
  }
  getNodeByLoad3d(load3d2) {
    for (const [node2, instance] of nodeToLoad3dMap) {
      if (instance === load3d2) {
        return node2;
      }
    }
    return null;
  }
  removeLoad3d(node2) {
    const rawNode = toRaw(node2);
    const instance = nodeToLoad3dMap.get(rawNode);
    if (instance) {
      instance.remove();
      nodeToLoad3dMap.delete(rawNode);
    }
  }
  clear() {
    for (const [node2] of nodeToLoad3dMap) {
      this.removeLoad3d(node2);
    }
  }
  getOrCreateViewer(node2) {
    if (!viewerInstances.has(node2.id)) {
      viewerInstances.set(node2.id, useLoad3dViewer(node2));
    }
    return viewerInstances.get(node2.id);
  }
  removeViewer(node2) {
    const viewer = viewerInstances.get(node2.id);
    if (viewer) {
      viewer.cleanup();
    }
    viewerInstances.delete(node2.id);
  }
  async copyLoad3dState(source, target) {
    const sourceModel = source.modelManager.currentModel;
    if (sourceModel) {
      if (source.isSplatModel()) {
        const originalURL = source.modelManager.originalURL;
        if (originalURL) {
          await target.loadModel(originalURL);
        }
      } else {
        const modelClone = sourceModel.clone();
        target.getModelManager().currentModel = modelClone;
        target.getSceneManager().scene.add(modelClone);
        const sourceOriginalModel = source.getModelManager().originalModel;
        if (sourceOriginalModel) {
          target.getModelManager().originalModel = sourceOriginalModel;
        }
        target.getModelManager().materialMode = source.getModelManager().materialMode;
        target.getModelManager().currentUpDirection = source.getModelManager().currentUpDirection;
        target.setMaterialMode(source.getModelManager().materialMode);
        target.setUpDirection(source.getModelManager().currentUpDirection);
        if (source.getModelManager().appliedTexture) {
          target.getModelManager().appliedTexture = source.getModelManager().appliedTexture;
        }
      }
    }
    const sourceCameraType = source.getCurrentCameraType();
    const sourceCameraState = source.getCameraState();
    target.toggleCamera(sourceCameraType);
    target.setCameraState(sourceCameraState);
    target.setBackgroundColor(source.getSceneManager().currentBackgroundColor);
    target.toggleGrid(source.getSceneManager().gridHelper.visible);
    const sourceBackgroundInfo = source.getSceneManager().getCurrentBackgroundInfo();
    if (sourceBackgroundInfo.type === "image") {
      const sourceNode = this.getNodeByLoad3d(source);
      const sceneConfig = sourceNode?.properties?.["Scene Config"];
      const backgroundPath = sceneConfig?.backgroundImage;
      if (backgroundPath) {
        await target.setBackgroundImage(backgroundPath);
      }
    } else {
      await target.setBackgroundImage("");
    }
    target.setLightIntensity(
      source.getLightingManager().lights[1]?.intensity || 1
    );
    if (sourceCameraType === "perspective") {
      target.setFOV(source.getCameraManager().perspectiveCamera.fov);
    }
  }
  handleViewportRefresh(load3d2) {
    if (!load3d2) return;
    load3d2.handleResize();
    const currentType = load3d2.getCurrentCameraType();
    load3d2.toggleCamera(
      currentType === "perspective" ? "orthographic" : "perspective"
    );
    load3d2.toggleCamera(currentType);
    load3d2.getControlsManager().controls.update();
  }
  async handleViewerClose(node2) {
    const viewer = useLoad3dService().getOrCreateViewer(node2);
    if (viewer.needApplyChanges.value) {
      await viewer.applyChanges();
      if (node2.syncLoad3dConfig) {
        node2.syncLoad3dConfig();
      }
    }
    useLoad3dService().removeViewer(node2);
  }
}
const useLoad3dService = /* @__PURE__ */ __name(() => {
  return Load3dService.getInstance();
}, "useLoad3dService");
const useLoad3dViewer = /* @__PURE__ */ __name((node2) => {
  const backgroundColor = ref("");
  const showGrid = ref(true);
  const cameraType = ref("perspective");
  const fov = ref(75);
  const lightIntensity = ref(1);
  const backgroundImage = ref("");
  const hasBackgroundImage = ref(false);
  const backgroundRenderMode = ref("tiled");
  const upDirection = ref("original");
  const materialMode = ref("original");
  const needApplyChanges = ref(true);
  const isPreview = ref(false);
  const isStandaloneMode = ref(false);
  const isSplatModel = ref(false);
  const isPlyModel = ref(false);
  let load3d2 = null;
  let sourceLoad3d = null;
  const initialState = ref({
    backgroundColor: "#282828",
    showGrid: true,
    cameraType: "perspective",
    fov: 75,
    lightIntensity: 1,
    cameraState: null,
    backgroundImage: "",
    backgroundRenderMode: "tiled",
    upDirection: "original",
    materialMode: "original"
  });
  watch(backgroundColor, (newColor) => {
    if (!load3d2) return;
    try {
      load3d2.setBackgroundColor(newColor);
    } catch (error) {
      console.error("Error updating background color:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToUpdateBackgroundColor", { color: newColor })
      );
    }
  });
  watch(showGrid, (newValue2) => {
    if (!load3d2) return;
    try {
      load3d2.toggleGrid(newValue2);
    } catch (error) {
      console.error("Error toggling grid:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToToggleGrid", { show: newValue2 ? "on" : "off" })
      );
    }
  });
  watch(cameraType, (newCameraType) => {
    if (!load3d2) return;
    try {
      load3d2.toggleCamera(newCameraType);
    } catch (error) {
      console.error("Error toggling camera:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToToggleCamera", { camera: newCameraType })
      );
    }
  });
  watch(fov, (newFov) => {
    if (!load3d2) return;
    try {
      load3d2.setFOV(Number(newFov));
    } catch (error) {
      console.error("Error updating FOV:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToUpdateFOV", { fov: newFov })
      );
    }
  });
  watch(lightIntensity, (newValue2) => {
    if (!load3d2) return;
    try {
      load3d2.setLightIntensity(Number(newValue2));
    } catch (error) {
      console.error("Error updating light intensity:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToUpdateLightIntensity", { intensity: newValue2 })
      );
    }
  });
  watch(backgroundImage, async (newValue2) => {
    if (!load3d2) return;
    try {
      await load3d2.setBackgroundImage(newValue2);
      hasBackgroundImage.value = !!newValue2;
    } catch (error) {
      console.error("Error updating background image:", error);
      useToastStore().addAlert(t("toastMessages.failedToUpdateBackgroundImage"));
    }
  });
  watch(backgroundRenderMode, (newValue2) => {
    if (!load3d2) return;
    try {
      load3d2.setBackgroundRenderMode(newValue2);
    } catch (error) {
      console.error("Error updating background render mode:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToUpdateBackgroundRenderMode", {
          mode: newValue2
        })
      );
    }
  });
  watch(upDirection, (newValue2) => {
    if (!load3d2) return;
    try {
      load3d2.setUpDirection(newValue2);
    } catch (error) {
      console.error("Error updating up direction:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToUpdateUpDirection", { direction: newValue2 })
      );
    }
  });
  watch(materialMode, (newValue2) => {
    if (!load3d2) return;
    try {
      load3d2.setMaterialMode(newValue2);
    } catch (error) {
      console.error("Error updating material mode:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToUpdateMaterialMode", { mode: newValue2 })
      );
    }
  });
  const initializeViewer = /* @__PURE__ */ __name(async (containerRef, source) => {
    if (!containerRef || !node2) return;
    sourceLoad3d = source;
    try {
      const width2 = node2.widgets?.find((w) => w.name === "width");
      const height = node2.widgets?.find((w) => w.name === "height");
      const hasTargetDimensions = !!(width2 && height);
      load3d2 = new Load3d(containerRef, {
        width: width2 ? toRaw(width2).value : void 0,
        height: height ? toRaw(height).value : void 0,
        getDimensions: hasTargetDimensions ? () => ({
          width: width2.value,
          height: height.value
        }) : void 0,
        isViewerMode: hasTargetDimensions
      });
      await useLoad3dService().copyLoad3dState(source, load3d2);
      const sourceCameraState = source.getCameraState();
      const sceneConfig = node2.properties["Scene Config"];
      const modelConfig = node2.properties["Model Config"];
      const cameraConfig = node2.properties["Camera Config"];
      const lightConfig = node2.properties["Light Config"];
      isPreview.value = node2.type === "Preview3D";
      if (sceneConfig) {
        backgroundColor.value = sceneConfig.backgroundColor || source.sceneManager.currentBackgroundColor;
        showGrid.value = sceneConfig.showGrid ?? source.sceneManager.gridHelper.visible;
        backgroundRenderMode.value = sceneConfig.backgroundRenderMode || source.sceneManager.backgroundRenderMode || "tiled";
        const backgroundInfo = source.sceneManager.getCurrentBackgroundInfo();
        if (backgroundInfo.type === "image" && sceneConfig.backgroundImage) {
          backgroundImage.value = sceneConfig.backgroundImage;
          hasBackgroundImage.value = true;
        } else {
          backgroundImage.value = "";
          hasBackgroundImage.value = false;
        }
      }
      if (cameraConfig) {
        cameraType.value = cameraConfig.cameraType || source.getCurrentCameraType();
        fov.value = cameraConfig.fov || source.cameraManager.perspectiveCamera.fov;
      }
      if (lightConfig) {
        lightIntensity.value = lightConfig.intensity || 1;
      } else {
        lightIntensity.value = 1;
      }
      if (modelConfig) {
        upDirection.value = modelConfig.upDirection || source.modelManager.currentUpDirection;
        materialMode.value = modelConfig.materialMode || source.modelManager.materialMode;
      }
      isSplatModel.value = source.isSplatModel();
      isPlyModel.value = source.isPlyModel();
      initialState.value = {
        backgroundColor: backgroundColor.value,
        showGrid: showGrid.value,
        cameraType: cameraType.value,
        fov: fov.value,
        lightIntensity: lightIntensity.value,
        cameraState: sourceCameraState,
        backgroundImage: backgroundImage.value,
        backgroundRenderMode: backgroundRenderMode.value,
        upDirection: upDirection.value,
        materialMode: materialMode.value
      };
    } catch (error) {
      console.error("Error initializing Load3d viewer:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToInitializeLoad3dViewer")
      );
    }
  }, "initializeViewer");
  const initializeStandaloneViewer = /* @__PURE__ */ __name(async (containerRef, modelUrl) => {
    if (!containerRef) return;
    try {
      isStandaloneMode.value = true;
      load3d2 = new Load3d(containerRef, {
        width: 800,
        height: 600,
        isViewerMode: true
      });
      await load3d2.loadModel(modelUrl);
      backgroundColor.value = "#282828";
      showGrid.value = true;
      cameraType.value = "perspective";
      fov.value = 75;
      lightIntensity.value = 1;
      backgroundRenderMode.value = "tiled";
      upDirection.value = "original";
      materialMode.value = "original";
      isSplatModel.value = load3d2.isSplatModel();
      isPlyModel.value = load3d2.isPlyModel();
      isPreview.value = true;
    } catch (error) {
      console.error("Error initializing standalone 3D viewer:", error);
      useToastStore().addAlert("Failed to load 3D model");
    }
  }, "initializeStandaloneViewer");
  const exportModel = /* @__PURE__ */ __name(async (format2) => {
    if (!load3d2) return;
    try {
      await load3d2.exportModel(format2);
    } catch (error) {
      console.error("Error exporting model:", error);
      useToastStore().addAlert(
        t("toastMessages.failedToExportModel", { format: format2.toUpperCase() })
      );
    }
  }, "exportModel");
  const handleResize = /* @__PURE__ */ __name(() => {
    load3d2?.handleResize();
  }, "handleResize");
  const handleMouseEnter = /* @__PURE__ */ __name(() => {
    load3d2?.updateStatusMouseOnViewer(true);
  }, "handleMouseEnter");
  const handleMouseLeave = /* @__PURE__ */ __name(() => {
    load3d2?.updateStatusMouseOnViewer(false);
  }, "handleMouseLeave");
  const restoreInitialState = /* @__PURE__ */ __name(() => {
    if (!node2) return;
    const nodeValue = node2;
    needApplyChanges.value = false;
    if (nodeValue.properties) {
      nodeValue.properties["Scene Config"] = {
        showGrid: initialState.value.showGrid,
        backgroundColor: initialState.value.backgroundColor,
        backgroundImage: initialState.value.backgroundImage,
        backgroundRenderMode: initialState.value.backgroundRenderMode
      };
      nodeValue.properties["Camera Config"] = {
        cameraType: initialState.value.cameraType,
        fov: initialState.value.fov
      };
      nodeValue.properties["Light Config"] = {
        intensity: initialState.value.lightIntensity
      };
      nodeValue.properties["Model Config"] = {
        upDirection: initialState.value.upDirection,
        materialMode: initialState.value.materialMode
      };
      const currentCameraConfig = nodeValue.properties["Camera Config"];
      nodeValue.properties["Camera Config"] = {
        ...currentCameraConfig,
        state: initialState.value.cameraState
      };
    }
  }, "restoreInitialState");
  const applyChanges = /* @__PURE__ */ __name(async () => {
    if (!node2 || !sourceLoad3d || !load3d2) return false;
    const viewerCameraState = load3d2.getCameraState();
    const nodeValue = node2;
    if (nodeValue.properties) {
      nodeValue.properties["Scene Config"] = {
        showGrid: showGrid.value,
        backgroundColor: backgroundColor.value,
        backgroundImage: backgroundImage.value,
        backgroundRenderMode: backgroundRenderMode.value
      };
      nodeValue.properties["Camera Config"] = {
        cameraType: cameraType.value,
        fov: fov.value,
        state: viewerCameraState
      };
      nodeValue.properties["Light Config"] = {
        intensity: lightIntensity.value
      };
      nodeValue.properties["Model Config"] = {
        upDirection: upDirection.value,
        materialMode: materialMode.value
      };
    }
    await useLoad3dService().copyLoad3dState(load3d2, sourceLoad3d);
    await sourceLoad3d.setBackgroundImage(backgroundImage.value);
    sourceLoad3d.setBackgroundRenderMode(backgroundRenderMode.value);
    sourceLoad3d.forceRender();
    if (nodeValue.graph) {
      nodeValue.graph.setDirtyCanvas(true, true);
    }
    return true;
  }, "applyChanges");
  const refreshViewport = /* @__PURE__ */ __name(() => {
    useLoad3dService().handleViewportRefresh(load3d2);
  }, "refreshViewport");
  const handleBackgroundImageUpdate = /* @__PURE__ */ __name(async (file) => {
    if (!file) {
      backgroundImage.value = "";
      hasBackgroundImage.value = false;
      return;
    }
    if (!node2) {
      return;
    }
    try {
      const resourceFolder = node2.properties["Resource Folder"] || "";
      const subfolder = resourceFolder.trim() ? `3d/${resourceFolder.trim()}` : "3d";
      const uploadPath = await Load3dUtils.uploadFile(file, subfolder);
      if (uploadPath) {
        backgroundImage.value = uploadPath;
        hasBackgroundImage.value = true;
      }
    } catch (error) {
      console.error("Error uploading background image:", error);
      useToastStore().addAlert(t("toastMessages.failedToUploadBackgroundImage"));
    }
  }, "handleBackgroundImageUpdate");
  const handleModelDrop = /* @__PURE__ */ __name(async (file) => {
    if (!load3d2) {
      useToastStore().addAlert(t("toastMessages.no3dScene"));
      return;
    }
    if (!node2) {
      return;
    }
    try {
      const resourceFolder = node2.properties["Resource Folder"] || "";
      const subfolder = resourceFolder.trim() ? `3d/${resourceFolder.trim()}` : "3d";
      const uploadedPath = await Load3dUtils.uploadFile(file, subfolder);
      if (!uploadedPath) {
        useToastStore().addAlert(t("toastMessages.fileUploadFailed"));
        return;
      }
      const modelUrl = api.apiURL(
        Load3dUtils.getResourceURL(
          ...Load3dUtils.splitFilePath(uploadedPath),
          "input"
        )
      );
      await load3d2.loadModel(modelUrl);
      const modelWidget = node2.widgets?.find((w) => w.name === "model_file");
      if (modelWidget) {
        const options = modelWidget.options;
        if (options?.values && !options.values.includes(uploadedPath)) {
          options.values.push(uploadedPath);
        }
        modelWidget.value = uploadedPath;
      }
    } catch (error) {
      console.error("Model drop failed:", error);
      useToastStore().addAlert(t("toastMessages.failedToLoadModel"));
    }
  }, "handleModelDrop");
  const cleanup = /* @__PURE__ */ __name(() => {
    load3d2?.remove();
    load3d2 = null;
    sourceLoad3d = null;
  }, "cleanup");
  return {
    // State
    backgroundColor,
    showGrid,
    cameraType,
    fov,
    lightIntensity,
    backgroundImage,
    hasBackgroundImage,
    backgroundRenderMode,
    upDirection,
    materialMode,
    needApplyChanges,
    isPreview,
    isStandaloneMode,
    isSplatModel,
    isPlyModel,
    // Methods
    initializeViewer,
    initializeStandaloneViewer,
    exportModel,
    handleResize,
    handleMouseEnter,
    handleMouseLeave,
    restoreInitialState,
    applyChanges,
    refreshViewport,
    handleBackgroundImageUpdate,
    handleModelDrop,
    cleanup
  };
}, "useLoad3dViewer");
const _hoisted_1$1R = { class: "relative flex-1" };
const _hoisted_2$1s = {
  key: 0,
  class: "pointer-events-none absolute inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
};
const _hoisted_3$_ = { class: "rounded-lg border-2 border-dashed border-blue-400 bg-blue-500/20 px-6 py-4 text-lg font-medium text-blue-100" };
const _hoisted_4$K = { class: "flex w-72 flex-col" };
const _hoisted_5$D = { class: "flex-1 overflow-y-auto p-4" };
const _hoisted_6$s = { class: "space-y-2" };
const _hoisted_7$p = { class: "space-y-4 p-2" };
const _hoisted_8$j = { class: "space-y-4 p-2" };
const _hoisted_9$f = { class: "space-y-4 p-2" };
const _hoisted_10$c = {
  key: 0,
  class: "space-y-4 p-2"
};
const _hoisted_11$8 = {
  key: 1,
  class: "space-y-4 p-2"
};
const _hoisted_12$6 = { class: "p-4" };
const _hoisted_13$6 = { class: "flex gap-2" };
const _sfc_main$2e = /* @__PURE__ */ defineComponent({
  __name: "Load3dViewerContent",
  props: {
    node: {},
    modelUrl: {}
  },
  setup(__props) {
    const props = __props;
    const viewerContentRef = ref();
    const containerRef = ref();
    const maximized = ref(false);
    const mutationObserver = ref(null);
    const isStandaloneMode = !props.node && props.modelUrl;
    const viewer = props.node ? useLoad3dService().getOrCreateViewer(toRaw(props.node)) : useLoad3dViewer();
    const { isDragging, dragMessage, handleDragOver, handleDragLeave, handleDrop } = useLoad3dDrag({
      onModelDrop: /* @__PURE__ */ __name(async (file) => {
        await viewer.handleModelDrop(file);
      }, "onModelDrop"),
      disabled: viewer.isPreview.value || isStandaloneMode
    });
    onMounted(async () => {
      if (!containerRef.value) return;
      if (isStandaloneMode && props.modelUrl) {
        await viewer.initializeStandaloneViewer(containerRef.value, props.modelUrl);
      } else if (props.node) {
        const source = useLoad3dService().getLoad3d(props.node);
        if (source) {
          await viewer.initializeViewer(containerRef.value, source);
        }
      }
      if (viewerContentRef.value) {
        mutationObserver.value = new MutationObserver((mutations) => {
          mutations.forEach((mutation) => {
            if (mutation.type === "attributes" && mutation.attributeName === "maximized") {
              maximized.value = mutation.target.getAttribute("maximized") === "true";
              setTimeout(() => {
                viewer.refreshViewport();
              }, 0);
            }
          });
        });
        mutationObserver.value.observe(viewerContentRef.value, {
          attributes: true,
          attributeFilter: ["maximized"]
        });
      }
      window.addEventListener("resize", viewer.handleResize);
    });
    const handleCancel = /* @__PURE__ */ __name(() => {
      if (!isStandaloneMode) {
        viewer.restoreInitialState();
      }
      useDialogStore().closeDialog();
    }, "handleCancel");
    onBeforeUnmount(() => {
      window.removeEventListener("resize", viewer.handleResize);
      if (mutationObserver.value) {
        mutationObserver.value.disconnect();
        mutationObserver.value = null;
      }
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        ref_key: "viewerContentRef",
        ref: viewerContentRef,
        class: normalizeClass(["flex w-full", [maximized.value ? "h-full" : "h-[70vh]"]]),
        onMouseenter: _cache[13] || (_cache[13] = //@ts-ignore
        (...args) => unref(viewer).handleMouseEnter && unref(viewer).handleMouseEnter(...args)),
        onMouseleave: _cache[14] || (_cache[14] = //@ts-ignore
        (...args) => unref(viewer).handleMouseLeave && unref(viewer).handleMouseLeave(...args))
      }, [
        createBaseVNode("div", _hoisted_1$1R, [
          createBaseVNode("div", {
            ref_key: "containerRef",
            ref: containerRef,
            class: "absolute h-full w-full",
            onResize: _cache[0] || (_cache[0] = //@ts-ignore
            (...args) => unref(viewer).handleResize && unref(viewer).handleResize(...args)),
            onDragover: _cache[1] || (_cache[1] = withModifiers(
              //@ts-ignore
              (...args) => unref(handleDragOver) && unref(handleDragOver)(...args),
              ["prevent", "stop"]
            )),
            onDragleave: _cache[2] || (_cache[2] = withModifiers(
              //@ts-ignore
              (...args) => unref(handleDragLeave) && unref(handleDragLeave)(...args),
              ["stop"]
            )),
            onDrop: _cache[3] || (_cache[3] = withModifiers(
              //@ts-ignore
              (...args) => unref(handleDrop) && unref(handleDrop)(...args),
              ["prevent", "stop"]
            ))
          }, null, 544),
          unref(isDragging) ? (openBlock(), createElementBlock("div", _hoisted_2$1s, [
            createBaseVNode("div", _hoisted_3$_, toDisplayString(unref(dragMessage)), 1)
          ])) : createCommentVNode("", true)
        ]),
        createBaseVNode("div", _hoisted_4$K, [
          createBaseVNode("div", _hoisted_5$D, [
            createBaseVNode("div", _hoisted_6$s, [
              createBaseVNode("div", _hoisted_7$p, [
                createVNode(_sfc_main$2f, {
                  "background-color": unref(viewer).backgroundColor.value,
                  "onUpdate:backgroundColor": _cache[4] || (_cache[4] = ($event) => unref(viewer).backgroundColor.value = $event),
                  "show-grid": unref(viewer).showGrid.value,
                  "onUpdate:showGrid": _cache[5] || (_cache[5] = ($event) => unref(viewer).showGrid.value = $event),
                  "background-render-mode": unref(viewer).backgroundRenderMode.value,
                  "onUpdate:backgroundRenderMode": _cache[6] || (_cache[6] = ($event) => unref(viewer).backgroundRenderMode.value = $event),
                  fov: unref(viewer).fov.value,
                  "onUpdate:fov": _cache[7] || (_cache[7] = ($event) => unref(viewer).fov.value = $event),
                  "has-background-image": unref(viewer).hasBackgroundImage.value,
                  "disable-background-upload": unref(viewer).isStandaloneMode.value,
                  onUpdateBackgroundImage: unref(viewer).handleBackgroundImageUpdate
                }, null, 8, ["background-color", "show-grid", "background-render-mode", "fov", "has-background-image", "disable-background-upload", "onUpdateBackgroundImage"])
              ]),
              createBaseVNode("div", _hoisted_8$j, [
                createVNode(_sfc_main$2g, {
                  "up-direction": unref(viewer).upDirection.value,
                  "onUpdate:upDirection": _cache[8] || (_cache[8] = ($event) => unref(viewer).upDirection.value = $event),
                  "material-mode": unref(viewer).materialMode.value,
                  "onUpdate:materialMode": _cache[9] || (_cache[9] = ($event) => unref(viewer).materialMode.value = $event),
                  "hide-material-mode": unref(viewer).isSplatModel.value,
                  "is-ply-model": unref(viewer).isPlyModel.value
                }, null, 8, ["up-direction", "material-mode", "hide-material-mode", "is-ply-model"])
              ]),
              createBaseVNode("div", _hoisted_9$f, [
                createVNode(_sfc_main$2j, {
                  "camera-type": unref(viewer).cameraType.value,
                  "onUpdate:cameraType": _cache[10] || (_cache[10] = ($event) => unref(viewer).cameraType.value = $event),
                  fov: unref(viewer).fov.value,
                  "onUpdate:fov": _cache[11] || (_cache[11] = ($event) => unref(viewer).fov.value = $event)
                }, null, 8, ["camera-type", "fov"])
              ]),
              !unref(viewer).isSplatModel.value ? (openBlock(), createElementBlock("div", _hoisted_10$c, [
                createVNode(_sfc_main$2h, {
                  "light-intensity": unref(viewer).lightIntensity.value,
                  "onUpdate:lightIntensity": _cache[12] || (_cache[12] = ($event) => unref(viewer).lightIntensity.value = $event)
                }, null, 8, ["light-intensity"])
              ])) : createCommentVNode("", true),
              !unref(viewer).isSplatModel.value ? (openBlock(), createElementBlock("div", _hoisted_11$8, [
                createVNode(_sfc_main$2i, {
                  onExportModel: unref(viewer).exportModel
                }, null, 8, ["onExportModel"])
              ])) : createCommentVNode("", true)
            ])
          ]),
          createBaseVNode("div", _hoisted_12$6, [
            createBaseVNode("div", _hoisted_13$6, [
              createVNode(_sfc_main$2A, {
                variant: "secondary",
                onClick: handleCancel
              }, {
                default: withCtx(() => [
                  _cache[15] || (_cache[15] = createBaseVNode("i", { class: "pi pi-times" }, null, -1)),
                  createTextVNode(" " + toDisplayString(unref(t)("g.cancel")), 1)
                ]),
                _: 1
              })
            ])
          ])
        ])
      ], 34);
    };
  }
});
const Load3DViewerContent = /* @__PURE__ */ _export_sfc(_sfc_main$2e, [["__scopeId", "data-v-3e08c92a"]]);
const _hoisted_1$1Q = { class: "comfy-vue-side-bar-header flex flex-col gap-2" };
const _hoisted_2$1r = ["title"];
const _hoisted_3$Z = { class: "touch:w-auto touch:opacity-100 flex flex-row overflow-hidden transition-all duration-200 motion-safe:w-0 motion-safe:opacity-0 motion-safe:group-focus-within/sidebar-tab:w-auto motion-safe:group-focus-within/sidebar-tab:opacity-100 motion-safe:group-hover/sidebar-tab:w-auto motion-safe:group-hover/sidebar-tab:opacity-100" };
const _sfc_main$2d = /* @__PURE__ */ defineComponent({
  __name: "SidebarTabTemplate",
  props: {
    title: {},
    class: {}
  },
  setup(__props) {
    const props = __props;
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(["comfy-vue-side-bar-container group/sidebar-tab flex h-full flex-col", props.class])
      }, [
        createBaseVNode("div", _hoisted_1$1Q, [
          createVNode(unref(script$c), { class: "min-h-16 bg-transparent rounded-none border-x-0 border-t-0 px-2 2xl:px-4" }, {
            start: withCtx(() => [
              createBaseVNode("span", {
                class: "truncate font-bold",
                title: props.title
              }, toDisplayString(props.title), 9, _hoisted_2$1r),
              renderSlot(_ctx.$slots, "alt-title", {}, void 0, true)
            ]),
            end: withCtx(() => [
              createBaseVNode("div", _hoisted_3$Z, [
                renderSlot(_ctx.$slots, "tool-buttons", {}, void 0, true)
              ])
            ]),
            _: 3
          }),
          renderSlot(_ctx.$slots, "header", {}, void 0, true)
        ]),
        createVNode(unref(script$d), { class: "comfy-vue-side-bar-body h-0 grow" }, {
          default: withCtx(() => [
            renderSlot(_ctx.$slots, "body", {}, void 0, true)
          ]),
          _: 3
        }),
        renderSlot(_ctx.$slots, "footer", {}, void 0, true)
      ], 2);
    };
  }
});
const SidebarTabTemplate = /* @__PURE__ */ _export_sfc(_sfc_main$2d, [["__scopeId", "data-v-03e5cd57"]]);
const _hoisted_1$1P = ["src", "data-test", "alt"];
const _hoisted_2$1q = ["src", "alt"];
const _hoisted_3$Y = {
  key: 1,
  class: "broken-image-placeholder"
};
const _sfc_main$2c = /* @__PURE__ */ defineComponent({
  __name: "ComfyImage",
  props: {
    src: {},
    class: {},
    contain: { type: Boolean, default: false },
    alt: { default: "Image content" }
  },
  setup(__props) {
    const imageBroken = ref(false);
    const handleImageError = /* @__PURE__ */ __name(() => {
      imageBroken.value = true;
    }, "handleImageError");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock(Fragment, null, [
        !imageBroken.value ? (openBlock(), createElementBlock("span", {
          key: 0,
          class: normalizeClass(["comfy-image-wrap", [{ contain: _ctx.contain }]])
        }, [
          _ctx.contain ? (openBlock(), createElementBlock("img", {
            key: 0,
            src: _ctx.src,
            "data-test": _ctx.src,
            class: "comfy-image-blur",
            style: normalizeStyle({ "background-image": `url(${_ctx.src})` }),
            alt: _ctx.alt,
            onError: handleImageError
          }, null, 44, _hoisted_1$1P)) : createCommentVNode("", true),
          createBaseVNode("img", {
            src: _ctx.src,
            class: normalizeClass(["comfy-image-main", __props.class]),
            alt: _ctx.alt,
            onError: handleImageError
          }, null, 42, _hoisted_2$1q)
        ], 2)) : createCommentVNode("", true),
        imageBroken.value ? (openBlock(), createElementBlock("div", _hoisted_3$Y, [
          _cache[0] || (_cache[0] = createBaseVNode("i", { class: "pi pi-image" }, null, -1)),
          createBaseVNode("span", null, toDisplayString(_ctx.$t("g.imageFailedToLoad")), 1)
        ])) : createCommentVNode("", true)
      ], 64);
    };
  }
});
const ComfyImage = /* @__PURE__ */ _export_sfc(_sfc_main$2c, [["__scopeId", "data-v-8b2eb5c8"]]);
const _hoisted_1$1O = {
  controls: "",
  width: "100%",
  height: "100%"
};
const _hoisted_2$1p = ["src", "type"];
const _sfc_main$2b = /* @__PURE__ */ defineComponent({
  __name: "ResultAudio",
  props: {
    result: {}
  },
  setup(__props) {
    const url = computed(() => __props.result.url);
    const htmlAudioType = computed(() => __props.result.htmlAudioType);
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("audio", _hoisted_1$1O, [
        createBaseVNode("source", {
          src: url.value,
          type: htmlAudioType.value
        }, null, 8, _hoisted_2$1p),
        createTextVNode(" " + toDisplayString(_ctx.$t("g.audioFailedToLoad")), 1)
      ]);
    };
  }
});
const _hoisted_1$1N = {
  controls: "",
  width: "100%",
  height: "100%"
};
const _hoisted_2$1o = ["src", "type"];
const _sfc_main$2a = /* @__PURE__ */ defineComponent({
  __name: "ResultVideo",
  props: {
    result: {}
  },
  setup(__props) {
    const props = __props;
    const settingStore = useSettingStore();
    const { isExtensionInstalled, isExtensionEnabled } = useExtensionStore();
    const vhsAdvancedPreviews = computed(() => {
      return isExtensionInstalled("VideoHelperSuite.Core") && isExtensionEnabled("VideoHelperSuite.Core") && settingStore.get("VHS.AdvancedPreviews") && settingStore.get("VHS.AdvancedPreviews") !== "Never";
    });
    const url = computed(
      () => vhsAdvancedPreviews.value ? props.result.vhsAdvancedPreviewUrl : props.result.url
    );
    const htmlVideoType = computed(
      () => vhsAdvancedPreviews.value ? "video/webm" : props.result.htmlVideoType
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("video", _hoisted_1$1N, [
        createBaseVNode("source", {
          src: url.value,
          type: htmlVideoType.value
        }, null, 8, _hoisted_2$1o),
        createTextVNode(" " + toDisplayString(_ctx.$t("g.videoFailedToLoad")), 1)
      ]);
    };
  }
});
const _sfc_main$29 = /* @__PURE__ */ defineComponent({
  __name: "ResultGallery",
  props: {
    allGalleryItems: {},
    activeIndex: {}
  },
  emits: ["update:activeIndex"],
  setup(__props, { emit: __emit }) {
    const galleryVisible = ref(false);
    const emit = __emit;
    const props = __props;
    const hasMultiple = computed(() => props.allGalleryItems.length > 1);
    let maskMouseDownTarget = null;
    const onMaskMouseDown = /* @__PURE__ */ __name((event) => {
      maskMouseDownTarget = event.target;
    }, "onMaskMouseDown");
    const onMaskMouseUp = /* @__PURE__ */ __name((event) => {
      const maskEl = document.querySelector("[data-mask]");
      if (galleryVisible.value && maskMouseDownTarget === event.target && maskMouseDownTarget === maskEl) {
        galleryVisible.value = false;
        handleVisibilityChange(false);
      }
    }, "onMaskMouseUp");
    watch(
      () => props.activeIndex,
      (index2) => {
        if (index2 !== -1) {
          galleryVisible.value = true;
        }
      }
    );
    const handleVisibilityChange = /* @__PURE__ */ __name((visible) => {
      if (!visible) {
        emit("update:activeIndex", -1);
      }
    }, "handleVisibilityChange");
    const handleActiveIndexChange = /* @__PURE__ */ __name((index2) => {
      emit("update:activeIndex", index2);
    }, "handleActiveIndexChange");
    const handleKeyDown = /* @__PURE__ */ __name((event) => {
      if (!galleryVisible.value) return;
      switch (event.key) {
        case "ArrowLeft":
          navigateImage(-1);
          break;
        case "ArrowRight":
          navigateImage(1);
          break;
        case "Escape":
          galleryVisible.value = false;
          handleVisibilityChange(false);
          break;
      }
    }, "handleKeyDown");
    const navigateImage = /* @__PURE__ */ __name((direction) => {
      const newIndex = (props.activeIndex + direction + props.allGalleryItems.length) % props.allGalleryItems.length;
      emit("update:activeIndex", newIndex);
    }, "navigateImage");
    onMounted(() => {
      window.addEventListener("keydown", handleKeyDown);
    });
    onUnmounted(() => {
      window.removeEventListener("keydown", handleKeyDown);
    });
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$e), {
        visible: galleryVisible.value,
        "onUpdate:visible": [
          _cache[0] || (_cache[0] = ($event) => galleryVisible.value = $event),
          handleVisibilityChange
        ],
        "active-index": _ctx.activeIndex,
        value: _ctx.allGalleryItems,
        "show-indicators": false,
        "change-item-on-indicator-hover": "",
        "show-item-navigators": hasMultiple.value,
        "full-screen": "",
        circular: hasMultiple.value,
        "show-thumbnails": false,
        pt: {
          mask: {
            onMousedown: onMaskMouseDown,
            onMouseup: onMaskMouseUp,
            "data-mask": true
          },
          prevButton: {
            style: "position: fixed !important"
          },
          nextButton: {
            style: "position: fixed !important"
          }
        },
        "onUpdate:activeIndex": handleActiveIndexChange
      }, {
        item: withCtx(({ item }) => [
          item.isImage ? (openBlock(), createBlock(ComfyImage, {
            key: item.url,
            src: item.url,
            contain: false,
            alt: item.filename,
            class: "galleria-image"
          }, null, 8, ["src", "alt"])) : item.isVideo ? (openBlock(), createBlock(_sfc_main$2a, {
            key: 1,
            result: item
          }, null, 8, ["result"])) : item.isAudio ? (openBlock(), createBlock(_sfc_main$2b, {
            key: 2,
            result: item
          }, null, 8, ["result"])) : createCommentVNode("", true)
        ]),
        _: 1
      }, 8, ["visible", "active-index", "value", "show-item-navigators", "circular", "pt"]);
    };
  }
});
const _hoisted_1$1M = ["id", "aria-selected", "aria-controls"];
const _sfc_main$28 = /* @__PURE__ */ defineComponent({
  __name: "Tab",
  props: {
    value: {},
    panelId: {}
  },
  setup(__props) {
    const currentValue = inject("tabs-value");
    const updateValue = inject("tabs-update");
    const tabId = computed(() => `tab-${__props.value}`);
    const isActive = computed(() => currentValue?.value === __props.value);
    const tabClasses = computed(() => {
      return cn(
        // Base styles from TextButton
        "flex items-center justify-center shrink-0",
        "px-2.5 py-2 text-sm rounded-lg cursor-pointer transition-all duration-200",
        "outline-hidden border-none",
        // State styles with semantic tokens
        isActive.value ? "bg-interface-menu-component-surface-hovered text-text-primary text-bold" : "bg-transparent text-text-secondary hover:bg-button-hover-surface focus:bg-button-hover-surface"
      );
    });
    const handleClick = /* @__PURE__ */ __name(() => {
      updateValue?.(__props.value);
    }, "handleClick");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("button", {
        id: tabId.value,
        class: normalizeClass(tabClasses.value),
        role: "tab",
        "aria-selected": isActive.value,
        "aria-controls": _ctx.panelId,
        tabindex: 0,
        onClick: handleClick
      }, [
        renderSlot(_ctx.$slots, "default")
      ], 10, _hoisted_1$1M);
    };
  }
});
const _hoisted_1$1L = {
  role: "tablist",
  class: "flex w-full items-center gap-2 pb-1"
};
const _sfc_main$27 = /* @__PURE__ */ defineComponent({
  __name: "TabList",
  props: {
    "modelValue": { required: true },
    "modelModifiers": {}
  },
  emits: ["update:modelValue"],
  setup(__props) {
    const modelValue = useModel(__props, "modelValue");
    provide("tabs-value", modelValue);
    provide("tabs-update", (value) => {
      modelValue.value = value;
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1L, [
        renderSlot(_ctx.$slots, "default")
      ]);
    };
  }
});
const _sfc_main$26 = /* @__PURE__ */ defineComponent({
  __name: "CardBottom",
  props: {
    fullHeight: { type: Boolean, default: true }
  },
  setup(__props) {
    const containerClasses = computed(
      () => cn("flex-1 w-full", __props.fullHeight && "h-full")
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(containerClasses.value)
      }, [
        renderSlot(_ctx.$slots, "default")
      ], 2);
    };
  }
});
const structureClasses = "flex flex-col overflow-hidden";
const _sfc_main$25 = /* @__PURE__ */ defineComponent({
  __name: "CardContainer",
  props: {
    size: { default: "regular" },
    variant: { default: "default" },
    rounded: { default: "md" },
    customAspectRatio: {},
    hasBorder: { type: Boolean, default: true },
    hasBackground: { type: Boolean, default: true },
    hasShadow: { type: Boolean, default: true },
    hasCursor: { type: Boolean, default: true },
    class: { default: "" }
  },
  setup(__props) {
    const roundedClasses = {
      none: "rounded-none",
      md: "rounded",
      lg: "rounded-lg",
      xl: "rounded-xl"
    };
    const containerClasses = computed(() => {
      const variantClasses = {
        default: cn(
          __props.hasBackground && "bg-modal-card-background",
          __props.hasBorder && "border border-border-default",
          __props.hasShadow && "shadow-sm",
          __props.hasCursor && "cursor-pointer"
        ),
        ghost: cn(
          __props.hasCursor && "cursor-pointer",
          "p-2 transition-colors duration-200"
        ),
        outline: cn(
          __props.hasBorder && "border-2 border-border-subtle",
          __props.hasCursor && "cursor-pointer",
          "hover:border-border-subtle/50 transition-colors"
        )
      };
      const aspectRatio = __props.customAspectRatio ? `aspect-[${__props.customAspectRatio}]` : {
        mini: "aspect-100/120",
        compact: "aspect-240/311",
        regular: "aspect-256/308",
        portrait: "aspect-256/325",
        tall: "aspect-256/353"
      }[__props.size];
      return cn(
        structureClasses,
        roundedClasses[__props.rounded],
        variantClasses[__props.variant],
        aspectRatio,
        __props.class
      );
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(containerClasses.value)
      }, [
        renderSlot(_ctx.$slots, "top"),
        renderSlot(_ctx.$slots, "bottom")
      ], 2);
    };
  }
});
const _sfc_main$24 = /* @__PURE__ */ defineComponent({
  __name: "CardTop",
  props: {
    ratio: { default: "square" },
    topLeftClass: {},
    topRightClass: {},
    centerLeftClass: {},
    centerRightClass: {},
    bottomLeftClass: {},
    bottomRightClass: {}
  },
  setup(__props) {
    const slots = useSlots();
    const topStyle = computed(() => {
      const baseClasses2 = "relative p-0 overflow-hidden";
      const ratioClasses = {
        square: "aspect-square",
        landscape: "aspect-48/27"
      };
      return `${baseClasses2} ${ratioClasses[__props.ratio]}`;
    });
    const defaultSlotClasses = {
      "top-left": "absolute top-2 left-2 flex flex-wrap justify-start gap-2",
      "top-right": "absolute top-2 right-2 flex flex-wrap justify-end gap-2",
      "center-left": "absolute top-1/2 left-2 flex -translate-y-1/2 flex-wrap justify-start gap-2",
      "center-right": "absolute top-1/2 right-2 flex -translate-y-1/2 flex-wrap justify-end gap-2",
      "bottom-left": "absolute bottom-2 left-2 flex flex-wrap justify-start gap-2",
      "bottom-right": "absolute right-2 bottom-2 flex flex-wrap justify-end gap-2"
    };
    const slotClasses = computed(() => ({
      "top-left": cn(defaultSlotClasses["top-left"], __props.topLeftClass),
      "top-right": cn(defaultSlotClasses["top-right"], __props.topRightClass),
      "center-left": cn(defaultSlotClasses["center-left"], __props.centerLeftClass),
      "center-right": cn(defaultSlotClasses["center-right"], __props.centerRightClass),
      "bottom-left": cn(defaultSlotClasses["bottom-left"], __props.bottomLeftClass),
      "bottom-right": cn(defaultSlotClasses["bottom-right"], __props.bottomRightClass)
    }));
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(topStyle.value)
      }, [
        renderSlot(_ctx.$slots, "default", { class: "absolute top-0 left-0 h-full w-full" }),
        unref(slots)["top-left"] ? (openBlock(), createElementBlock("div", {
          key: 0,
          class: normalizeClass(slotClasses.value["top-left"])
        }, [
          renderSlot(_ctx.$slots, "top-left")
        ], 2)) : createCommentVNode("", true),
        unref(slots)["top-right"] ? (openBlock(), createElementBlock("div", {
          key: 1,
          class: normalizeClass(slotClasses.value["top-right"])
        }, [
          renderSlot(_ctx.$slots, "top-right")
        ], 2)) : createCommentVNode("", true),
        unref(slots)["center-left"] ? (openBlock(), createElementBlock("div", {
          key: 2,
          class: normalizeClass(slotClasses.value["center-left"])
        }, [
          renderSlot(_ctx.$slots, "center-left")
        ], 2)) : createCommentVNode("", true),
        unref(slots)["center-right"] ? (openBlock(), createElementBlock("div", {
          key: 3,
          class: normalizeClass(slotClasses.value["center-right"])
        }, [
          renderSlot(_ctx.$slots, "center-right")
        ], 2)) : createCommentVNode("", true),
        unref(slots)["bottom-left"] ? (openBlock(), createElementBlock("div", {
          key: 4,
          class: normalizeClass(slotClasses.value["bottom-left"])
        }, [
          renderSlot(_ctx.$slots, "bottom-left")
        ], 2)) : createCommentVNode("", true),
        unref(slots)["bottom-right"] ? (openBlock(), createElementBlock("div", {
          key: 5,
          class: normalizeClass(slotClasses.value["bottom-right"])
        }, [
          renderSlot(_ctx.$slots, "bottom-right")
        ], 2)) : createCommentVNode("", true)
      ], 2);
    };
  }
});
const baseClasses = "inline-flex shrink-0 items-center justify-center gap-1 rounded px-2 py-1 text-xs font-bold";
const _sfc_main$23 = /* @__PURE__ */ defineComponent({
  __name: "SquareChip",
  props: {
    label: {},
    variant: { default: "dark" }
  },
  setup(__props) {
    const variantStyles = {
      dark: "bg-zinc-500/40 text-white/90",
      light: cn("backdrop-blur-[2px] bg-base-background/50 text-base-foreground"),
      gray: cn(
        "backdrop-blur-[2px] bg-modal-card-tag-background text-base-foreground"
      )
    };
    const chipClasses = computed(() => {
      return cn(baseClasses, variantStyles[__props.variant]);
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(chipClasses.value)
      }, [
        renderSlot(_ctx.$slots, "icon"),
        createBaseVNode("span", null, toDisplayString(_ctx.label), 1)
      ], 2);
    };
  }
});
const _hoisted_1$1K = { class: "prompt-dialog-content m-2 mt-4 flex flex-col gap-6" };
const _hoisted_2$1n = {
  key: 0,
  class: "m-0 flex flex-col gap-2 pl-4"
};
const _hoisted_3$X = { class: "flex justify-end gap-4" };
const _hoisted_4$J = {
  key: 0,
  class: "flex justify-start gap-4"
};
const _hoisted_5$C = {
  for: "doNotAskAgain",
  severity: "secondary"
};
const _sfc_main$22 = /* @__PURE__ */ defineComponent({
  __name: "ConfirmationDialogContent",
  props: {
    message: {},
    type: {},
    onConfirm: { type: Function },
    itemList: {},
    hint: {}
  },
  setup(__props) {
    const props = __props;
    const { t: t2 } = useI18n();
    const onCancel = /* @__PURE__ */ __name(() => useDialogStore().closeDialog(), "onCancel");
    const doNotAskAgain = ref(false);
    const onDeny = /* @__PURE__ */ __name(() => {
      props.onConfirm(false);
      useDialogStore().closeDialog();
    }, "onDeny");
    const onConfirm = /* @__PURE__ */ __name(() => {
      if (props.type === "overwriteBlueprint" && doNotAskAgain.value)
        void useSettingStore().set("Comfy.Workflow.WarnBlueprintOverwrite", false);
      props.onConfirm(true);
      useDialogStore().closeDialog();
    }, "onConfirm");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("section", _hoisted_1$1K, [
        createBaseVNode("span", null, toDisplayString(_ctx.message), 1),
        _ctx.itemList?.length ? (openBlock(), createElementBlock("ul", _hoisted_2$1n, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.itemList, (item) => {
            return openBlock(), createElementBlock("li", { key: item }, toDisplayString(item), 1);
          }), 128))
        ])) : createCommentVNode("", true),
        _ctx.hint ? (openBlock(), createBlock(unref(script), {
          key: 1,
          icon: "pi pi-info-circle",
          severity: "secondary",
          size: "small",
          variant: "simple"
        }, {
          default: withCtx(() => [
            createTextVNode(toDisplayString(_ctx.hint), 1)
          ]),
          _: 1
        })) : createCommentVNode("", true),
        createBaseVNode("div", _hoisted_3$X, [
          _ctx.type === "overwriteBlueprint" ? (openBlock(), createElementBlock("div", _hoisted_4$J, [
            createVNode(unref(script$b), {
              modelValue: doNotAskAgain.value,
              "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => doNotAskAgain.value = $event),
              class: "flex justify-start gap-4",
              "input-id": "doNotAskAgain",
              binary: ""
            }, null, 8, ["modelValue"]),
            createBaseVNode("label", _hoisted_5$C, toDisplayString(unref(t2)("missingModelsDialog.doNotAskAgain")), 1)
          ])) : createCommentVNode("", true),
          createVNode(_sfc_main$2A, {
            variant: "secondary",
            autofocus: "",
            onClick: onCancel
          }, {
            default: withCtx(() => [
              _cache[1] || (_cache[1] = createBaseVNode("i", { class: "pi pi-undo" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("g.cancel")), 1)
            ]),
            _: 1
          }),
          _ctx.type === "default" ? (openBlock(), createBlock(_sfc_main$2A, {
            key: 1,
            variant: "primary",
            onClick: onConfirm
          }, {
            default: withCtx(() => [
              _cache[2] || (_cache[2] = createBaseVNode("i", { class: "pi pi-check" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("g.confirm")), 1)
            ]),
            _: 1
          })) : _ctx.type === "delete" ? (openBlock(), createBlock(_sfc_main$2A, {
            key: 2,
            variant: "destructive",
            onClick: onConfirm
          }, {
            default: withCtx(() => [
              _cache[3] || (_cache[3] = createBaseVNode("i", { class: "pi pi-trash" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("g.delete")), 1)
            ]),
            _: 1
          })) : _ctx.type === "overwrite" || _ctx.type === "overwriteBlueprint" ? (openBlock(), createBlock(_sfc_main$2A, {
            key: 3,
            variant: "destructive",
            onClick: onConfirm
          }, {
            default: withCtx(() => [
              _cache[4] || (_cache[4] = createBaseVNode("i", { class: "pi pi-save" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("g.overwrite")), 1)
            ]),
            _: 1
          })) : _ctx.type === "dirtyClose" ? (openBlock(), createElementBlock(Fragment, { key: 4 }, [
            createVNode(_sfc_main$2A, {
              variant: "secondary",
              onClick: onDeny
            }, {
              default: withCtx(() => [
                _cache[5] || (_cache[5] = createBaseVNode("i", { class: "pi pi-times" }, null, -1)),
                createTextVNode(" " + toDisplayString(_ctx.$t("g.no")), 1)
              ]),
              _: 1
            }),
            createVNode(_sfc_main$2A, { onClick: onConfirm }, {
              default: withCtx(() => [
                _cache[6] || (_cache[6] = createBaseVNode("i", { class: "pi pi-save" }, null, -1)),
                createTextVNode(" " + toDisplayString(_ctx.$t("g.save")), 1)
              ]),
              _: 1
            })
          ], 64)) : _ctx.type === "reinstall" ? (openBlock(), createBlock(_sfc_main$2A, {
            key: 5,
            variant: "destructive",
            onClick: onConfirm
          }, {
            default: withCtx(() => [
              _cache[7] || (_cache[7] = createBaseVNode("i", { class: "pi pi-eraser" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("desktopMenu.reinstall")), 1)
            ]),
            _: 1
          })) : (openBlock(), createBlock(_sfc_main$2A, {
            key: 6,
            variant: "primary",
            onClick: onCancel
          }, {
            default: withCtx(() => [
              _cache[8] || (_cache[8] = createBaseVNode("i", { class: "pi pi-times" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("g.close")), 1)
            ]),
            _: 1
          }))
        ])
      ]);
    };
  }
});
const ConfirmationDialogContent = /* @__PURE__ */ _export_sfc(_sfc_main$22, [["__scopeId", "data-v-b97af7c1"]]);
function useCopyToClipboard() {
  const { copy, copied } = useClipboard();
  const toast = useToast();
  const showSuccessToast = /* @__PURE__ */ __name(() => {
    toast.add({
      severity: "success",
      summary: t("g.success"),
      detail: t("clipboard.successMessage"),
      life: 3e3
    });
  }, "showSuccessToast");
  const showErrorToast = /* @__PURE__ */ __name(() => {
    toast.add({
      severity: "error",
      summary: t("g.error"),
      detail: t("clipboard.errorMessage")
    });
  }, "showErrorToast");
  function fallbackCopy(text) {
    const textarea = document.createElement("textarea");
    textarea.setAttribute("readonly", "");
    textarea.value = text;
    textarea.style.position = "absolute";
    textarea.style.left = "-9999px";
    textarea.setAttribute("aria-hidden", "true");
    textarea.setAttribute("tabindex", "-1");
    textarea.style.width = "1px";
    textarea.style.height = "1px";
    document.body.appendChild(textarea);
    textarea.select();
    try {
      const successful = document.execCommand("copy");
      if (successful) {
        showSuccessToast();
      } else {
        showErrorToast();
      }
    } catch (err) {
      showErrorToast();
    } finally {
      textarea.remove();
    }
  }
  __name(fallbackCopy, "fallbackCopy");
  const copyToClipboard = /* @__PURE__ */ __name(async (text) => {
    try {
      await copy(text);
      if (copied.value) {
        showSuccessToast();
      } else {
        fallbackCopy(text);
      }
    } catch (err) {
      fallbackCopy(text);
    }
  }, "copyToClipboard");
  return {
    copyToClipboard
  };
}
__name(useCopyToClipboard, "useCopyToClipboard");
function useWorkflowActionsService() {
  const workflowStore = useWorkflowStore();
  const workflowService2 = useWorkflowService();
  const settingStore = useSettingStore();
  const dialogService = useDialogService();
  const exportWorkflowAction = /* @__PURE__ */ __name(async (workflow, defaultFilename) => {
    if (!workflow) {
      return { success: false, error: "No workflow data available" };
    }
    try {
      let filename = defaultFilename;
      if (settingStore.get("Comfy.PromptFilename")) {
        const input = await dialogService.prompt({
          title: t("workflowService.exportWorkflow"),
          message: t("workflowService.enterFilename") + ":",
          defaultValue: filename
        });
        if (!input) return { success: false };
        filename = appendJsonExt(input);
      }
      const json = JSON.stringify(workflow, null, 2);
      const blob = new Blob([json], { type: "application/json" });
      downloadBlob(filename, blob);
      return { success: true };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : "Failed to export workflow"
      };
    }
  }, "exportWorkflowAction");
  const openWorkflowAction = /* @__PURE__ */ __name(async (workflow, filename) => {
    if (!workflow) {
      return { success: false, error: "No workflow data available" };
    }
    try {
      const temp2 = workflowStore.createTemporary(filename, workflow);
      await workflowService2.openWorkflow(temp2);
      return { success: true };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : "Failed to open workflow"
      };
    }
  }, "openWorkflowAction");
  return {
    exportWorkflowAction,
    openWorkflowAction
  };
}
__name(useWorkflowActionsService, "useWorkflowActionsService");
function isOutputAssetMetadata(metadata) {
  if (!metadata) return false;
  return typeof metadata.promptId === "string" && (typeof metadata.nodeId === "string" || typeof metadata.nodeId === "number");
}
__name(isOutputAssetMetadata, "isOutputAssetMetadata");
function getOutputAssetMetadata(userMetadata) {
  if (isOutputAssetMetadata(userMetadata)) {
    return userMetadata;
  }
  return null;
}
__name(getOutputAssetMetadata, "getOutputAssetMetadata");
function getAssetType(asset, defaultType = "output") {
  return asset.tags?.[0] || defaultType;
}
__name(getAssetType, "getAssetType");
function getAssetUrl(asset, defaultType = "output") {
  const assetType = getAssetType(asset, defaultType);
  return api.apiURL(
    `/view?filename=${encodeURIComponent(asset.name)}&type=${assetType}`
  );
}
__name(getAssetUrl, "getAssetUrl");
var ComfyMetadataTags = /* @__PURE__ */ ((ComfyMetadataTags2) => {
  ComfyMetadataTags2["PROMPT"] = "PROMPT";
  ComfyMetadataTags2["WORKFLOW"] = "WORKFLOW";
  return ComfyMetadataTags2;
})(ComfyMetadataTags || {});
var ASCII$1 = /* @__PURE__ */ ((ASCII2) => {
  ASCII2[ASCII2["GLTF"] = 1179937895] = "GLTF";
  ASCII2[ASCII2["JSON"] = 1313821514] = "JSON";
  ASCII2[ASCII2["OPEN_BRACE"] = 123] = "OPEN_BRACE";
  return ASCII2;
})(ASCII$1 || {});
var GltfSizeBytes = /* @__PURE__ */ ((GltfSizeBytes2) => {
  GltfSizeBytes2[GltfSizeBytes2["HEADER"] = 12] = "HEADER";
  GltfSizeBytes2[GltfSizeBytes2["CHUNK_HEADER"] = 8] = "CHUNK_HEADER";
  return GltfSizeBytes2;
})(GltfSizeBytes || {});
const WEBM_SIGNATURE = [26, 69, 223, 163];
const MAX_READ_BYTES$2 = 2 * 1024 * 1024;
const EBML_ID = {
  SIMPLE_TAG: new Uint8Array([103, 200]),
  TAG_NAME: new Uint8Array([69, 163]),
  TAG_VALUE: new Uint8Array([68, 135])
};
const ASCII = {
  OPEN_BRACE: 123,
  NULL: 0,
  PRINTABLE_MIN: 32,
  PRINTABLE_MAX: 126
};
const MASK = {
  MSB: 128,
  ALL_BITS_SET: -1
};
const hasWebmSignature = /* @__PURE__ */ __name((data) => WEBM_SIGNATURE.every((byte, index2) => data[index2] === byte), "hasWebmSignature");
const readVint = /* @__PURE__ */ __name((data, pos) => {
  if (pos >= data.length) return null;
  const byte = data[pos];
  if ((byte & MASK.MSB) === MASK.MSB) {
    return { value: byte & -129, length: 1 };
  }
  const length = findFirstSetBitPosition(byte);
  if (length === 0 || pos + length > data.length) {
    return null;
  }
  return {
    value: calculateVintValue(data, pos, length),
    length
  };
}, "readVint");
const calculateVintValue = /* @__PURE__ */ __name((data, pos, length) => {
  let value = data[pos] & 255 >> length;
  for (let i = 1; i < length; i++) {
    value = value << 8 | data[pos + i];
  }
  const allBitsSet = Math.pow(2, 7 * length) - 1;
  return value === allBitsSet ? MASK.ALL_BITS_SET : value;
}, "calculateVintValue");
const findFirstSetBitPosition = /* @__PURE__ */ __name((byte) => {
  for (let mask = MASK.MSB, position = 1; mask !== 0; mask >>= 1, position++) {
    if ((byte & mask) !== 0) return position;
  }
  return 0;
}, "findFirstSetBitPosition");
const matchesId = /* @__PURE__ */ __name((data, pos, id2) => {
  if (pos + id2.length > data.length) return false;
  for (let i = 0; i < id2.length; i++) {
    if (data[pos + i] !== id2[i]) return false;
  }
  return true;
}, "matchesId");
const findNextTag = /* @__PURE__ */ __name((data, pos) => {
  if (!matchesId(data, pos, EBML_ID.SIMPLE_TAG)) return null;
  const tagSize = readVint(data, pos + 2);
  if (!tagSize || tagSize.value <= 0) return null;
  const tagEnd = pos + 2 + tagSize.length + tagSize.value;
  if (tagEnd > data.length) return null;
  const contents = extractTagContents(data, pos + 2 + tagSize.length, tagEnd);
  return { tagEnd, contents };
}, "findNextTag");
const extractTagContents = /* @__PURE__ */ __name((data, start, end) => {
  const nameInfo = findElementsInTag(data, start, end, EBML_ID.TAG_NAME);
  const valueInfo = findElementsInTag(data, start, end, EBML_ID.TAG_VALUE);
  if (!nameInfo || !valueInfo) return null;
  return {
    name: nameInfo,
    value: valueInfo
  };
}, "extractTagContents");
const findElementsInTag = /* @__PURE__ */ __name((data, start, end, id2) => {
  for (let pos = start; pos < end - 1; ) {
    if (matchesId(data, pos, id2)) {
      const size = readVint(data, pos + 2);
      if (size && size.value > 0) {
        const elementPos = pos + 2 + size.length;
        return { pos: elementPos, len: size.value };
      }
    }
    pos++;
  }
  return null;
}, "findElementsInTag");
const processTagContents = /* @__PURE__ */ __name((data, contents, meta) => {
  try {
    const name2 = extractEbmlTagName(data, contents.name.pos, contents.name.len);
    if (!name2) return;
    const value = extractEbmlTagValue(
      data,
      name2,
      contents.value.pos,
      contents.value.len
    );
    if (value !== null) {
      meta[name2.toLowerCase()] = value;
    }
  } catch {
  }
}, "processTagContents");
const extractEbmlTagValue = /* @__PURE__ */ __name((data, name2, pos, len) => {
  if (name2 === ComfyMetadataTags.PROMPT || name2 === ComfyMetadataTags.WORKFLOW) {
    return readJson(data, pos, len);
  }
  return ebmlToString(data, pos, len);
}, "extractEbmlTagValue");
const extractEbmlTagName = /* @__PURE__ */ __name((data, start, length) => {
  if (length <= 0) return null;
  const textRange = findReadableTextRange(data, start, length);
  if (!textRange) return null;
  return new TextDecoder().decode(data.subarray(textRange.start, textRange.end)).trim();
}, "extractEbmlTagName");
const findReadableTextRange = /* @__PURE__ */ __name((data, start, length) => {
  const isPrintableAscii = /* @__PURE__ */ __name((byte) => byte >= ASCII.PRINTABLE_MIN && byte <= ASCII.PRINTABLE_MAX, "isPrintableAscii");
  let textStart = start;
  while (textStart < start + length && !isPrintableAscii(data[textStart])) {
    textStart++;
  }
  if (textStart >= start + length) return null;
  let textEnd = textStart;
  while (textEnd < start + length && data[textEnd] !== ASCII.NULL) {
    textEnd++;
  }
  return { start: textStart, end: textEnd };
}, "findReadableTextRange");
const readJson = /* @__PURE__ */ __name((data, start, length) => {
  if (length <= 0) return null;
  const nullTerminatorPos = findNullTerminator(data, start, length);
  const jsonStartPos = findJsonStart(data, start, nullTerminatorPos - start);
  if (jsonStartPos === null) return null;
  const jsonText = decodeJsonText(data, jsonStartPos, nullTerminatorPos);
  return parseJsonText(jsonText);
}, "readJson");
const decodeJsonText = /* @__PURE__ */ __name((data, start, end) => {
  return new TextDecoder().decode(data.subarray(start, end));
}, "decodeJsonText");
const parseJsonText = /* @__PURE__ */ __name((jsonText) => {
  const jsonEndPos = findJsonEnd(jsonText);
  if (jsonEndPos === null) return null;
  try {
    return JSON.parse(jsonText.substring(0, jsonEndPos));
  } catch {
    return null;
  }
}, "parseJsonText");
const findNullTerminator = /* @__PURE__ */ __name((data, start, maxLength) => {
  const end = Math.min(start + maxLength, data.length);
  for (let pos = start; pos < end; pos++) {
    if (data[pos] === ASCII.NULL) return pos;
  }
  return end;
}, "findNullTerminator");
const findJsonStart = /* @__PURE__ */ __name((data, start, length) => {
  for (let pos = start; pos < start + length; pos++) {
    if (data[pos] === ASCII.OPEN_BRACE) return pos;
  }
  return null;
}, "findJsonStart");
const findJsonEnd = /* @__PURE__ */ __name((text) => {
  let braceCount = 1;
  let pos = 1;
  while (braceCount > 0 && pos < text.length) {
    if (text[pos] === "{") braceCount++;
    if (text[pos] === "}") braceCount--;
    pos++;
  }
  return braceCount === 0 ? pos : null;
}, "findJsonEnd");
const ebmlToString = /* @__PURE__ */ __name((data, start, length) => {
  if (length <= 0) return "";
  const endPos = findNullTerminator(data, start, length);
  return new TextDecoder().decode(data.subarray(start, endPos)).trim();
}, "ebmlToString");
const parseMetadata = /* @__PURE__ */ __name((data) => {
  const meta = {};
  for (let pos = 0; pos < data.length - 2; ) {
    const tagInfo = findNextTag(data, pos);
    if (!tagInfo) {
      pos++;
      continue;
    }
    const { tagEnd, contents } = tagInfo;
    if (contents) {
      processTagContents(data, contents, meta);
    }
    pos = tagEnd;
  }
  return meta;
}, "parseMetadata");
const handleFileLoad = /* @__PURE__ */ __name((event, resolve) => {
  if (!event.target?.result) {
    resolve({});
    return;
  }
  try {
    const data = new Uint8Array(event.target.result);
    if (data.length < 4 || !hasWebmSignature(data)) {
      resolve({});
      return;
    }
    resolve(parseMetadata(data));
  } catch {
    resolve({});
  }
}, "handleFileLoad");
function getFromWebmFile(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = (event) => handleFileLoad(event, resolve);
    reader.onerror = () => resolve({});
    reader.readAsArrayBuffer(file.slice(0, MAX_READ_BYTES$2));
  });
}
__name(getFromWebmFile, "getFromWebmFile");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.ebml = window.comfyAPI.ebml || {};
window.comfyAPI.ebml.getFromWebmFile = getFromWebmFile;
const MAX_READ_BYTES$1 = 1 << 20;
const isJsonChunk = /* @__PURE__ */ __name((chunk) => !!chunk && chunk.chunkTypeIdentifier === ASCII$1.JSON, "isJsonChunk");
const isValidChunkRange = /* @__PURE__ */ __name((start, length, bufferSize) => start + length <= bufferSize, "isValidChunkRange");
const byteArrayToString = /* @__PURE__ */ __name((bytes) => new TextDecoder().decode(bytes), "byteArrayToString");
const parseGltfBinaryHeader = /* @__PURE__ */ __name((dataView) => {
  if (dataView.byteLength < GltfSizeBytes.HEADER) return null;
  const magicNumber = dataView.getUint32(0, true);
  if (magicNumber !== ASCII$1.GLTF) return null;
  return {
    magicNumber,
    gltfFormatVersion: dataView.getUint32(4, true),
    totalLengthBytes: dataView.getUint32(8, true)
  };
}, "parseGltfBinaryHeader");
const parseChunkHeaderAtOffset = /* @__PURE__ */ __name((dataView, offset) => {
  if (offset + GltfSizeBytes.CHUNK_HEADER > dataView.byteLength) return null;
  return {
    chunkLengthBytes: dataView.getUint32(offset, true),
    chunkTypeIdentifier: dataView.getUint32(offset + 4, true)
  };
}, "parseChunkHeaderAtOffset");
const extractJsonChunk = /* @__PURE__ */ __name((buffer) => {
  const dataView = new DataView(buffer);
  const header = parseGltfBinaryHeader(dataView);
  if (!header) return null;
  const chunkOffset = GltfSizeBytes.HEADER;
  const firstChunk = parseChunkHeaderAtOffset(dataView, chunkOffset);
  if (!firstChunk || !isJsonChunk(firstChunk)) return null;
  const jsonStart = chunkOffset + GltfSizeBytes.CHUNK_HEADER;
  const isValid = isValidChunkRange(
    jsonStart,
    firstChunk.chunkLengthBytes,
    dataView.byteLength
  );
  if (!isValid) return null;
  return { start: jsonStart, length: firstChunk.chunkLengthBytes };
}, "extractJsonChunk");
const extractJsonChunkData = /* @__PURE__ */ __name((buffer) => {
  const chunkLocation = extractJsonChunk(buffer);
  if (!chunkLocation) return null;
  return new Uint8Array(buffer, chunkLocation.start, chunkLocation.length);
}, "extractJsonChunkData");
const parseJson = /* @__PURE__ */ __name((text) => {
  try {
    return JSON.parse(text);
  } catch {
    return null;
  }
}, "parseJson");
const parseJsonBytes = /* @__PURE__ */ __name((bytes) => {
  const jsonString = byteArrayToString(bytes);
  return parseJson(jsonString);
}, "parseJsonBytes");
const parseMetadataValue = /* @__PURE__ */ __name((value) => {
  if (typeof value !== "string")
    return value;
  const parsed = parseJson(value);
  if (!parsed) return void 0;
  return parsed;
}, "parseMetadataValue");
const extractComfyMetadata = /* @__PURE__ */ __name((jsonData) => {
  const metadata = {};
  if (!jsonData?.asset?.extras) return metadata;
  const { extras } = jsonData.asset;
  if (extras.workflow) {
    const parsedValue = parseMetadataValue(extras.workflow);
    if (parsedValue) {
      metadata[ComfyMetadataTags.WORKFLOW.toLowerCase()] = parsedValue;
    }
  }
  if (extras.prompt) {
    const parsedValue = parseMetadataValue(extras.prompt);
    if (parsedValue) {
      metadata[ComfyMetadataTags.PROMPT.toLowerCase()] = parsedValue;
    }
  }
  return metadata;
}, "extractComfyMetadata");
const processGltfFileBuffer = /* @__PURE__ */ __name((buffer) => {
  const jsonChunk = extractJsonChunkData(buffer);
  if (!jsonChunk) return {};
  const parsedJson = parseJsonBytes(jsonChunk);
  if (!parsedJson) return {};
  return extractComfyMetadata(parsedJson);
}, "processGltfFileBuffer");
function getGltfBinaryMetadata(file) {
  return new Promise((resolve) => {
    if (!file) return Promise.resolve({});
    const bytesToRead = Math.min(file.size, MAX_READ_BYTES$1);
    const reader = new FileReader();
    reader.onload = (event) => {
      try {
        if (!event.target?.result) {
          resolve({});
          return;
        }
        resolve(processGltfFileBuffer(event.target.result));
      } catch {
        resolve({});
      }
    };
    reader.onerror = () => resolve({});
    reader.readAsArrayBuffer(file.slice(0, bytesToRead));
  });
}
__name(getGltfBinaryMetadata, "getGltfBinaryMetadata");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.gltf = window.comfyAPI.gltf || {};
window.comfyAPI.gltf.getGltfBinaryMetadata = getGltfBinaryMetadata;
const MAX_READ_BYTES = 64 * 1024 * 1024;
const BOX_TYPES = {
  USER_DATA: [117, 100, 116, 97],
  META_DATA: [109, 101, 116, 97],
  ITEM_LIST: [105, 108, 115, 116],
  KEYS: [107, 101, 121, 115],
  DATA: [100, 97, 116, 97],
  MOVIE: [109, 111, 111, 118]
};
const SIZES = {
  HEADER: 8,
  VERSION: 4,
  LOCALE: 4,
  ITEM_MIN: 8
};
const bufferMatchesBoxType = /* @__PURE__ */ __name((data, pos, boxType) => {
  if (pos + 4 > data.length) return false;
  for (let i = 0; i < 4; i++) {
    if (data[pos + i] !== boxType[i]) return false;
  }
  return true;
}, "bufferMatchesBoxType");
const readUint32 = /* @__PURE__ */ __name((data, pos) => {
  if (pos + 4 > data.length) return 0;
  return data[pos] << 24 | data[pos + 1] << 16 | data[pos + 2] << 8 | data[pos + 3];
}, "readUint32");
const findIsobmffBoxByType = /* @__PURE__ */ __name((data, startPos, endPos, boxType) => {
  for (let pos = startPos; pos < endPos - 8; pos++) {
    const size = readUint32(data, pos);
    if (size < SIZES.ITEM_MIN) continue;
    if (bufferMatchesBoxType(data, pos + 4, boxType))
      return { start: pos + SIZES.HEADER, end: pos + size };
    if (pos + size > endPos) return null;
    pos += size - 1;
  }
  return null;
}, "findIsobmffBoxByType");
const extractJson = /* @__PURE__ */ __name((data, start, end) => {
  let jsonStart = start;
  while (jsonStart < end && data[jsonStart] !== ASCII$1.OPEN_BRACE) {
    jsonStart++;
  }
  if (jsonStart >= end) return null;
  try {
    const jsonText = new TextDecoder().decode(data.slice(jsonStart, end));
    return JSON.parse(jsonText);
  } catch {
    return null;
  }
}, "extractJson");
const readUtf8String = /* @__PURE__ */ __name((data, start, end) => new TextDecoder().decode(data.slice(start, end)), "readUtf8String");
const parseKeysBox = /* @__PURE__ */ __name((data, keysBoxStart, keysBoxEnd) => {
  const keysMap = /* @__PURE__ */ new Map();
  let pos = keysBoxStart + 4;
  if (pos + 4 > keysBoxEnd) return keysMap;
  const entryCount = readUint32(data, pos);
  pos += 4;
  for (let i = 1; i <= entryCount; i++) {
    if (pos + SIZES.HEADER > keysBoxEnd) break;
    const keySize = readUint32(data, pos);
    pos += SIZES.HEADER;
    const keyNameEnd = pos + keySize - SIZES.HEADER;
    if (keySize < SIZES.ITEM_MIN || keyNameEnd > keysBoxEnd) break;
    const keyName = readUtf8String(data, pos, keyNameEnd);
    keysMap.set(i, keyName);
    pos = keyNameEnd;
  }
  return keysMap;
}, "parseKeysBox");
const extractMetadataValueFromDataBox = /* @__PURE__ */ __name((data, dataBoxStart, dataBoxEnd, keyName) => {
  const valueStart = dataBoxStart + SIZES.VERSION + SIZES.LOCALE;
  if (valueStart >= dataBoxEnd) return null;
  const lowerKeyName = keyName.toLowerCase();
  if (lowerKeyName === ComfyMetadataTags.PROMPT.toLowerCase() || lowerKeyName === ComfyMetadataTags.WORKFLOW.toLowerCase()) {
    return extractJson(data, valueStart, dataBoxEnd) || null;
  }
  return null;
}, "extractMetadataValueFromDataBox");
const parseIlstItem = /* @__PURE__ */ __name((data, itemStart, itemEnd, keysMap, metadata) => {
  if (itemStart + SIZES.HEADER > itemEnd) return;
  const itemIndex = readUint32(data, itemStart + 4);
  const keyName = keysMap.get(itemIndex);
  if (!keyName) return;
  const dataBox = findIsobmffBoxByType(
    data,
    itemStart + SIZES.HEADER,
    itemEnd,
    BOX_TYPES.DATA
  );
  if (dataBox) {
    const value = extractMetadataValueFromDataBox(
      data,
      dataBox.start,
      dataBox.end,
      keyName
    );
    if (value !== null) {
      metadata[keyName.toLowerCase()] = value;
    }
  }
}, "parseIlstItem");
const parseIlstBox = /* @__PURE__ */ __name((data, ilstStart, ilstEnd, keysMap, metadata) => {
  let pos = ilstStart;
  while (pos < ilstEnd - SIZES.HEADER) {
    const itemSize = readUint32(data, pos);
    if (itemSize <= SIZES.HEADER || pos + itemSize > ilstEnd) break;
    parseIlstItem(data, pos, pos + itemSize, keysMap, metadata);
    pos += itemSize;
  }
}, "parseIlstBox");
const findUserDataBox = /* @__PURE__ */ __name((data) => {
  let userDataBox = null;
  userDataBox = findIsobmffBoxByType(data, 0, data.length, BOX_TYPES.USER_DATA);
  if (!userDataBox) {
    const moovBox = findIsobmffBoxByType(data, 0, data.length, BOX_TYPES.MOVIE);
    if (moovBox) {
      userDataBox = findIsobmffBoxByType(
        data,
        moovBox.start,
        moovBox.end,
        BOX_TYPES.USER_DATA
      );
    }
  }
  return userDataBox;
}, "findUserDataBox");
const parseIsobmffMetadata = /* @__PURE__ */ __name((data) => {
  const metadata = {};
  const userDataBox = findUserDataBox(data);
  if (!userDataBox) return metadata;
  const metaBox = findIsobmffBoxByType(
    data,
    userDataBox.start,
    userDataBox.end,
    BOX_TYPES.META_DATA
  );
  if (!metaBox) return metadata;
  const metaContentStart = metaBox.start + SIZES.VERSION;
  const keysBox = findIsobmffBoxByType(
    data,
    metaContentStart,
    metaBox.end,
    BOX_TYPES.KEYS
  );
  if (!keysBox) return metadata;
  const keysMap = parseKeysBox(data, keysBox.start, keysBox.end);
  if (keysMap.size === 0) return metadata;
  const ilstBox = findIsobmffBoxByType(
    data,
    metaContentStart,
    metaBox.end,
    BOX_TYPES.ITEM_LIST
  );
  if (!ilstBox) return metadata;
  parseIlstBox(data, ilstBox.start, ilstBox.end, keysMap, metadata);
  return metadata;
}, "parseIsobmffMetadata");
function getFromIsobmffFile(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = (event) => {
      if (!event.target?.result) {
        resolve({});
        return;
      }
      try {
        const data = new Uint8Array(event.target.result);
        resolve(parseIsobmffMetadata(data));
      } catch (e2) {
        console.error("Parser: Error parsing ISOBMFF metadata:", e2);
        resolve({});
      }
    };
    reader.onerror = (err) => {
      console.error("FileReader: Error reading ISOBMFF file:", err);
      resolve({});
    };
    reader.readAsArrayBuffer(file.slice(0, MAX_READ_BYTES));
  });
}
__name(getFromIsobmffFile, "getFromIsobmffFile");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.isobmff = window.comfyAPI.isobmff || {};
window.comfyAPI.isobmff.getFromIsobmffFile = getFromIsobmffFile;
function getDataFromJSON(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = async () => {
      const readerResult = reader.result;
      const jsonContent = JSON.parse(readerResult);
      if (jsonContent?.templates) {
        resolve({ templates: jsonContent.templates });
        return;
      }
      if (isApiJson(jsonContent)) {
        resolve({ prompt: jsonContent });
        return;
      }
      resolve({ workflow: jsonContent });
      return;
    };
    reader.readAsText(file);
    return;
  });
}
__name(getDataFromJSON, "getDataFromJSON");
function isApiJson(data) {
  return isObject(data) && Object.values(data).every((v2) => v2.class_type);
}
__name(isApiJson, "isApiJson");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.json = window.comfyAPI.json || {};
window.comfyAPI.json.getDataFromJSON = getDataFromJSON;
async function getMp3Metadata(file) {
  const reader = new FileReader();
  const read_process = new Promise(
    (r) => reader.onload = (event) => r(event?.target?.result)
  );
  reader.readAsArrayBuffer(file);
  const arrayBuffer = await read_process;
  const sig_bytes = new Uint8Array(arrayBuffer, 0, 3);
  if (sig_bytes[0] != 255 && sig_bytes[1] != 251 || sig_bytes[0] != 73 && sig_bytes[1] != 68 && sig_bytes[2] != 51)
    console.error("Invalid file signature.");
  let header = "";
  while (header.length < arrayBuffer.byteLength) {
    const page = String.fromCharCode(
      ...new Uint8Array(arrayBuffer, header.length, header.length + 4096)
    );
    header += page;
    if (page.match("ÿû")) break;
  }
  let workflow, prompt;
  let prompt_s = header.match(/prompt\u0000(\{.*?\})\u0000/s)?.[1];
  if (prompt_s) prompt = JSON.parse(prompt_s);
  let workflow_s = header.match(/workflow\u0000(\{.*?\})\u0000/s)?.[1];
  if (workflow_s) workflow = JSON.parse(workflow_s);
  return { prompt, workflow };
}
__name(getMp3Metadata, "getMp3Metadata");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.mp3 = window.comfyAPI.mp3 || {};
window.comfyAPI.mp3.getMp3Metadata = getMp3Metadata;
async function getOggMetadata(file) {
  const reader = new FileReader();
  const read_process = new Promise(
    (r) => reader.onload = (event) => r(event?.target?.result)
  );
  reader.readAsArrayBuffer(file);
  const arrayBuffer = await read_process;
  const signature = String.fromCharCode(...new Uint8Array(arrayBuffer, 0, 4));
  if (signature !== "OggS") console.error("Invalid file signature.");
  let oggs = 0;
  let header = "";
  while (header.length < arrayBuffer.byteLength) {
    const page = String.fromCharCode(
      ...new Uint8Array(arrayBuffer, header.length, header.length + 4096)
    );
    if (page.match("OggS\0")) oggs++;
    header += page;
    if (oggs > 1) break;
  }
  let workflow, prompt;
  let prompt_s = header.match(/prompt=(\{.*?(\}.*?\u0000))/s)?.[1]?.match(/\{.*\}/)?.[0];
  if (prompt_s) prompt = JSON.parse(prompt_s);
  let workflow_s = header.match(/workflow=(\{.*?(\}.*?\u0000))/s)?.[1]?.match(/\{.*\}/)?.[0];
  if (workflow_s) workflow = JSON.parse(workflow_s);
  return { prompt, workflow };
}
__name(getOggMetadata, "getOggMetadata");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.ogg = window.comfyAPI.ogg || {};
window.comfyAPI.ogg.getOggMetadata = getOggMetadata;
async function getSvgMetadata(file) {
  const text = await file.text();
  const metadataMatch = /<metadata>\s*<!\[CDATA\[([\s\S]*?)\]\]>\s*<\/metadata>/i.exec(text);
  if (metadataMatch && metadataMatch[1]) {
    try {
      return JSON.parse(metadataMatch[1].trim());
    } catch (error) {
      console.error("Error parsing SVG metadata:", error);
      return {};
    }
  }
  return {};
}
__name(getSvgMetadata, "getSvgMetadata");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.svg = window.comfyAPI.svg || {};
window.comfyAPI.svg.getSvgMetadata = getSvgMetadata;
const readNullTerminatedString = /* @__PURE__ */ __name((dataView, start, end) => {
  let length = 0;
  while (start + length < end && dataView.getUint8(start + length) !== 0) {
    length++;
  }
  const str = new TextDecoder("utf-8").decode(
    new Uint8Array(dataView.buffer, dataView.byteOffset + start, length)
  );
  return { str, length: length + 1 };
}, "readNullTerminatedString");
const parseInfeBox = /* @__PURE__ */ __name((dataView, start) => {
  const version = dataView.getUint8(start);
  const flags = dataView.getUint32(start) & 16777215;
  let offset = start + 4;
  let item_ID, item_protection_index, item_type;
  if (version >= 2) {
    if (version === 2) {
      item_ID = dataView.getUint16(offset);
      offset += 2;
    } else {
      item_ID = dataView.getUint32(offset);
      offset += 4;
    }
    item_protection_index = dataView.getUint16(offset);
    offset += 2;
    item_type = String.fromCharCode(
      ...new Uint8Array(dataView.buffer, dataView.byteOffset + offset, 4)
    );
    offset += 4;
    const { str: item_name, length: name_len } = readNullTerminatedString(
      dataView,
      offset,
      dataView.byteLength
    );
    offset += name_len;
    const content_type = readNullTerminatedString(
      dataView,
      offset,
      dataView.byteLength
    ).str;
    return {
      box_header: { size: 0, type: "infe" },
      // Size is dynamic
      version,
      flags,
      item_ID,
      item_protection_index,
      item_type,
      item_name,
      content_type
    };
  }
  throw new Error(`Unsupported infe box version: ${version}`);
}, "parseInfeBox");
const parseIinfBox = /* @__PURE__ */ __name((dataView, range) => {
  if (!range) throw new Error("iinf box not found");
  const version = dataView.getUint8(range.start);
  const flags = dataView.getUint32(range.start) & 16777215;
  let offset = range.start + 4;
  const entry_count = version === 0 ? dataView.getUint16(offset) : dataView.getUint32(offset);
  offset += version === 0 ? 2 : 4;
  const entries = [];
  for (let i = 0; i < entry_count; i++) {
    const boxSize = dataView.getUint32(offset);
    const boxType = String.fromCharCode(
      ...new Uint8Array(dataView.buffer, dataView.byteOffset + offset + 4, 4)
    );
    if (boxType === "infe") {
      const infe = parseInfeBox(dataView, offset + 8);
      infe.box_header.size = boxSize;
      entries.push(infe);
    }
    offset += boxSize;
  }
  return {
    box_header: { size: range.end - range.start + 8, type: "iinf" },
    version,
    flags,
    entry_count,
    entries
  };
}, "parseIinfBox");
const parseIlocBox = /* @__PURE__ */ __name((dataView, range) => {
  if (!range) throw new Error("iloc box not found");
  const version = dataView.getUint8(range.start);
  const flags = dataView.getUint32(range.start) & 16777215;
  let offset = range.start + 4;
  const sizes = dataView.getUint8(offset++);
  const offset_size = sizes >> 4 & 15;
  const length_size = sizes & 15;
  const base_offset_size = dataView.getUint8(offset) >> 4 & 15;
  const index_size = version === 1 || version === 2 ? dataView.getUint8(offset) & 15 : 0;
  offset++;
  const item_count = version < 2 ? dataView.getUint16(offset) : dataView.getUint32(offset);
  offset += version < 2 ? 2 : 4;
  const items = [];
  for (let i = 0; i < item_count; i++) {
    const item_ID = version < 2 ? dataView.getUint16(offset) : dataView.getUint32(offset);
    offset += version < 2 ? 2 : 4;
    if (version === 1 || version === 2) {
      offset += 2;
    }
    const data_reference_index = dataView.getUint16(offset);
    offset += 2;
    const base_offset = base_offset_size > 0 ? dataView.getUint32(offset) : 0;
    offset += base_offset_size;
    const extent_count = dataView.getUint16(offset);
    offset += 2;
    const extents = [];
    for (let j = 0; j < extent_count; j++) {
      if ((version === 1 || version === 2) && index_size > 0) {
        offset += index_size;
      }
      const extent_offset = dataView.getUint32(offset);
      offset += offset_size;
      const extent_length = dataView.getUint32(offset);
      offset += length_size;
      extents.push({ extent_offset, extent_length });
    }
    items.push({
      item_ID,
      data_reference_index,
      base_offset,
      extent_count,
      extents
    });
  }
  return {
    box_header: { size: range.end - range.start + 8, type: "iloc" },
    version,
    flags,
    offset_size,
    length_size,
    base_offset_size,
    index_size,
    item_count,
    items
  };
}, "parseIlocBox");
function findBox(dataView, start, end, type) {
  let offset = start;
  while (offset < end) {
    if (offset + 8 > end) break;
    const boxLength = dataView.getUint32(offset);
    const boxType = String.fromCharCode(
      ...new Uint8Array(dataView.buffer, dataView.byteOffset + offset + 4, 4)
    );
    if (boxLength === 0) break;
    if (boxType === type) {
      return { start: offset + 8, end: offset + boxLength };
    }
    if (offset + boxLength > end) break;
    offset += boxLength;
  }
  return null;
}
__name(findBox, "findBox");
function parseAvifMetadata(buffer) {
  const metadata = {};
  const dataView = new DataView(buffer);
  if (dataView.getUint32(4) !== 1718909296 || dataView.getUint32(8) !== 1635150182) {
    console.error("Not a valid AVIF file");
    return {};
  }
  const metaBox = findBox(dataView, 0, dataView.byteLength, "meta");
  if (!metaBox) return {};
  const metaBoxContentStart = metaBox.start + 4;
  const iinfBoxRange = findBox(
    dataView,
    metaBoxContentStart,
    metaBox.end,
    "iinf"
  );
  const iinf = parseIinfBox(dataView, iinfBoxRange);
  const exifInfe = iinf.entries.find((e2) => e2.item_type === "Exif");
  if (!exifInfe) return {};
  const ilocBoxRange = findBox(
    dataView,
    metaBoxContentStart,
    metaBox.end,
    "iloc"
  );
  const iloc = parseIlocBox(dataView, ilocBoxRange);
  const exifIloc = iloc.items.find((i) => i.item_ID === exifInfe.item_ID);
  if (!exifIloc || exifIloc.extents.length === 0) return {};
  const exifExtent = exifIloc.extents[0];
  const itemData = new Uint8Array(
    buffer,
    exifExtent.extent_offset,
    exifExtent.extent_length
  );
  let tiffHeaderOffset = -1;
  for (let i = 0; i < itemData.length - 4; i++) {
    if (itemData[i] === 77 && itemData[i + 1] === 77 && itemData[i + 2] === 0 && itemData[i + 3] === 42 || // MM*
    itemData[i] === 73 && itemData[i + 1] === 73 && itemData[i + 2] === 42 && itemData[i + 3] === 0) {
      tiffHeaderOffset = i;
      break;
    }
  }
  if (tiffHeaderOffset !== -1) {
    const exifData = itemData.subarray(tiffHeaderOffset);
    const data = parseExifData$1(exifData);
    for (const key in data) {
      const value = data[key];
      if (typeof value === "string") {
        if (key === "usercomment") {
          try {
            const metadataJson = JSON.parse(value);
            if (metadataJson.prompt) {
              metadata[ComfyMetadataTags.PROMPT] = metadataJson.prompt;
            }
            if (metadataJson.workflow) {
              metadata[ComfyMetadataTags.WORKFLOW] = metadataJson.workflow;
            }
          } catch (e2) {
            console.error("Failed to parse usercomment JSON", e2);
          }
        } else {
          const [metadataKey, ...metadataValueParts] = value.split(":");
          const metadataValue = metadataValueParts.join(":").trim();
          if (metadataKey.toLowerCase() === ComfyMetadataTags.PROMPT.toLowerCase() || metadataKey.toLowerCase() === ComfyMetadataTags.WORKFLOW.toLowerCase()) {
            try {
              const jsonValue = JSON.parse(metadataValue);
              metadata[metadataKey.toLowerCase()] = jsonValue;
            } catch (e2) {
              console.error(`Failed to parse JSON for ${metadataKey}`, e2);
            }
          }
        }
      }
    }
  } else {
    console.log("Warning: TIFF header not found in EXIF data.");
  }
  return metadata;
}
__name(parseAvifMetadata, "parseAvifMetadata");
function parseExifData$1(exifData) {
  const isLittleEndian = String.fromCharCode(...exifData.slice(0, 2)) === "II";
  function readInt(offset, isLittleEndian2, length) {
    let arr = exifData.slice(offset, offset + length);
    if (length === 2) {
      return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint16(
        0,
        isLittleEndian2
      );
    } else if (length === 4) {
      return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint32(
        0,
        isLittleEndian2
      );
    }
  }
  __name(readInt, "readInt");
  const ifdOffset = readInt(4, isLittleEndian, 4);
  function parseIFD(offset) {
    const numEntries = readInt(offset, isLittleEndian, 2);
    const result = {};
    for (let i = 0; i < numEntries; i++) {
      const entryOffset = offset + 2 + i * 12;
      const tag = readInt(entryOffset, isLittleEndian, 2);
      const type = readInt(entryOffset + 2, isLittleEndian, 2);
      const numValues = readInt(entryOffset + 4, isLittleEndian, 4);
      const valueOffset = readInt(entryOffset + 8, isLittleEndian, 4);
      let value;
      if (type === 2) {
        value = new TextDecoder("utf-8").decode(
          // @ts-expect-error fixme ts strict error
          exifData.subarray(valueOffset, valueOffset + numValues - 1)
        );
      }
      result[tag] = value;
    }
    return result;
  }
  __name(parseIFD, "parseIFD");
  const ifdData = parseIFD(ifdOffset);
  return ifdData;
}
__name(parseExifData$1, "parseExifData$1");
function getFromAvifFile(file) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = (event) => {
      const buffer = event.target?.result;
      if (!buffer) {
        resolve({});
        return;
      }
      try {
        const comfyMetadata = parseAvifMetadata(buffer);
        const result = {};
        if (comfyMetadata.prompt) {
          result.prompt = JSON.stringify(comfyMetadata.prompt);
        }
        if (comfyMetadata.workflow) {
          result.workflow = JSON.stringify(comfyMetadata.workflow);
        }
        resolve(result);
      } catch (e2) {
        console.error("Parser: Error parsing AVIF metadata:", e2);
        resolve({});
      }
    };
    reader.onerror = (err) => {
      console.error("FileReader: Error reading AVIF file:", err);
      resolve({});
    };
    reader.readAsArrayBuffer(file);
  });
}
__name(getFromAvifFile, "getFromAvifFile");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.avif = window.comfyAPI.avif || {};
window.comfyAPI.avif.getFromAvifFile = getFromAvifFile;
function getFromFlacBuffer(buffer) {
  const dataView = new DataView(buffer);
  const signature = String.fromCharCode(...new Uint8Array(buffer, 0, 4));
  if (signature !== "fLaC") {
    console.error("Not a valid FLAC file");
    return;
  }
  let offset = 4;
  let vorbisComment = null;
  while (offset < dataView.byteLength) {
    const isLastBlock = dataView.getUint8(offset) & 128;
    const blockType = dataView.getUint8(offset) & 127;
    const blockSize = dataView.getUint32(offset, false) & 16777215;
    offset += 4;
    if (blockType === 4) {
      vorbisComment = parseVorbisComment(
        new DataView(buffer, offset, blockSize)
      );
    }
    offset += blockSize;
    if (isLastBlock) break;
  }
  return vorbisComment;
}
__name(getFromFlacBuffer, "getFromFlacBuffer");
function getFromFlacFile(file) {
  return new Promise((r) => {
    const reader = new FileReader();
    reader.onload = function(event) {
      const arrayBuffer = event.target.result;
      r(getFromFlacBuffer(arrayBuffer));
    };
    reader.readAsArrayBuffer(file);
  });
}
__name(getFromFlacFile, "getFromFlacFile");
function parseVorbisComment(dataView) {
  let offset = 0;
  const vendorLength = dataView.getUint32(offset, true);
  offset += 4;
  getString(dataView, offset, vendorLength);
  offset += vendorLength;
  const userCommentListLength = dataView.getUint32(offset, true);
  offset += 4;
  const comments = {};
  for (let i = 0; i < userCommentListLength; i++) {
    const commentLength = dataView.getUint32(offset, true);
    offset += 4;
    const comment = getString(dataView, offset, commentLength);
    offset += commentLength;
    const ind = comment.indexOf("=");
    const key = comment.substring(0, ind);
    comments[key] = comment.substring(ind + 1);
  }
  return comments;
}
__name(parseVorbisComment, "parseVorbisComment");
function getString(dataView, offset, length) {
  let string = "";
  for (let i = 0; i < length; i++) {
    string += String.fromCharCode(dataView.getUint8(offset + i));
  }
  return string;
}
__name(getString, "getString");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.flac = window.comfyAPI.flac || {};
window.comfyAPI.flac.getFromFlacBuffer = getFromFlacBuffer;
window.comfyAPI.flac.getFromFlacFile = getFromFlacFile;
function getFromPngBuffer(buffer) {
  const pngData = new Uint8Array(buffer);
  const dataView = new DataView(pngData.buffer);
  if (dataView.getUint32(0) !== 2303741511) {
    console.error("Not a valid PNG file");
    return {};
  }
  let offset = 8;
  let txt_chunks = {};
  while (offset < pngData.length) {
    const length = dataView.getUint32(offset);
    const type = String.fromCharCode(...pngData.slice(offset + 4, offset + 8));
    if (type === "tEXt" || type == "comf" || type === "iTXt") {
      let keyword_end = offset + 8;
      while (pngData[keyword_end] !== 0) {
        keyword_end++;
      }
      const keyword = String.fromCharCode(
        ...pngData.slice(offset + 8, keyword_end)
      );
      const contentArraySegment = pngData.slice(
        keyword_end + 1,
        offset + 8 + length
      );
      const contentJson = new TextDecoder("utf-8").decode(contentArraySegment);
      txt_chunks[keyword] = contentJson;
    }
    offset += 12 + length;
  }
  return txt_chunks;
}
__name(getFromPngBuffer, "getFromPngBuffer");
function getFromPngFile(file) {
  return new Promise((r) => {
    const reader = new FileReader();
    reader.onload = (event) => {
      r(getFromPngBuffer(event.target.result));
    };
    reader.readAsArrayBuffer(file);
  });
}
__name(getFromPngFile, "getFromPngFile");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.png = window.comfyAPI.png || {};
window.comfyAPI.png.getFromPngBuffer = getFromPngBuffer;
window.comfyAPI.png.getFromPngFile = getFromPngFile;
function getPngMetadata(file) {
  return getFromPngFile(file);
}
__name(getPngMetadata, "getPngMetadata");
function getFlacMetadata(file) {
  return getFromFlacFile(file);
}
__name(getFlacMetadata, "getFlacMetadata");
function getAvifMetadata(file) {
  return getFromAvifFile(file);
}
__name(getAvifMetadata, "getAvifMetadata");
function parseExifData(exifData) {
  const isLittleEndian = String.fromCharCode(...exifData.slice(0, 2)) === "II";
  function readInt(offset, isLittleEndian2, length) {
    let arr = exifData.slice(offset, offset + length);
    if (length === 2) {
      return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint16(
        0,
        isLittleEndian2
      );
    } else if (length === 4) {
      return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint32(
        0,
        isLittleEndian2
      );
    }
  }
  __name(readInt, "readInt");
  const ifdOffset = readInt(4, isLittleEndian, 4);
  function parseIFD(offset) {
    const numEntries = readInt(offset, isLittleEndian, 2);
    const result = {};
    for (let i = 0; i < numEntries; i++) {
      const entryOffset = offset + 2 + i * 12;
      const tag = readInt(entryOffset, isLittleEndian, 2);
      const type = readInt(entryOffset + 2, isLittleEndian, 2);
      const numValues = readInt(entryOffset + 4, isLittleEndian, 4);
      const valueOffset = readInt(entryOffset + 8, isLittleEndian, 4);
      let value;
      if (type === 2) {
        value = new TextDecoder("utf-8").decode(
          // @ts-expect-error fixme ts strict error
          exifData.subarray(valueOffset, valueOffset + numValues - 1)
        );
      }
      result[tag] = value;
    }
    return result;
  }
  __name(parseIFD, "parseIFD");
  const ifdData = parseIFD(ifdOffset);
  return ifdData;
}
__name(parseExifData, "parseExifData");
function getWebpMetadata(file) {
  return new Promise((r) => {
    const reader = new FileReader();
    reader.onload = (event) => {
      const webp = new Uint8Array(event.target.result);
      const dataView = new DataView(webp.buffer);
      if (dataView.getUint32(0) !== 1380533830 || dataView.getUint32(8) !== 1464156752) {
        console.error("Not a valid WEBP file");
        r({});
        return;
      }
      let offset = 12;
      let txt_chunks = {};
      while (offset < webp.length) {
        const chunk_length = dataView.getUint32(offset + 4, true);
        const chunk_type = String.fromCharCode(
          ...webp.slice(offset, offset + 4)
        );
        if (chunk_type === "EXIF") {
          if (String.fromCharCode(...webp.slice(offset + 8, offset + 8 + 6)) == "Exif\0\0") {
            offset += 6;
          }
          let data = parseExifData(
            webp.slice(offset + 8, offset + 8 + chunk_length)
          );
          for (var key in data) {
            const value = data[key];
            if (typeof value === "string") {
              const index2 = value.indexOf(":");
              txt_chunks[value.slice(0, index2)] = value.slice(index2 + 1);
            }
          }
          break;
        }
        offset += 8 + chunk_length;
      }
      r(txt_chunks);
    };
    reader.readAsArrayBuffer(file);
  });
}
__name(getWebpMetadata, "getWebpMetadata");
function getLatentMetadata(file) {
  return new Promise((r) => {
    const reader = new FileReader();
    reader.onload = (event) => {
      const safetensorsData = new Uint8Array(
        event.target?.result
      );
      const dataView = new DataView(safetensorsData.buffer);
      let header_size = dataView.getUint32(0, true);
      let offset = 8;
      let header = JSON.parse(
        new TextDecoder().decode(
          safetensorsData.slice(offset, offset + header_size)
        )
      );
      r(header.__metadata__);
    };
    var slice = file.slice(0, 1024 * 1024 * 4);
    reader.readAsArrayBuffer(slice);
  });
}
__name(getLatentMetadata, "getLatentMetadata");
async function importA1111(graph, parameters) {
  const p = parameters.lastIndexOf("\nSteps:");
  if (p > -1) {
    const embeddings = await api.getEmbeddings();
    const opts = parameters.substr(p).split("\n")[1].match(
      new RegExp('\\s*([^:]+:\\s*([^"\\{].*?|".*?"|\\{.*?\\}))\\s*(,|$)', "g")
    ).reduce((p3, n) => {
      const s = n.split(":");
      if (s[1].endsWith(",")) {
        s[1] = s[1].substr(0, s[1].length - 1);
      }
      p3[s[0].trim().toLowerCase()] = s[1].trim();
      return p3;
    }, {});
    const p2 = parameters.lastIndexOf("\nNegative prompt:", p);
    if (p2 > -1) {
      let positive = parameters.substr(0, p2).trim();
      let negative = parameters.substring(p2 + 18, p).trim();
      const ckptNode = LiteGraph.createNode("CheckpointLoaderSimple");
      const clipSkipNode = LiteGraph.createNode("CLIPSetLastLayer");
      const positiveNode = LiteGraph.createNode("CLIPTextEncode");
      const negativeNode = LiteGraph.createNode("CLIPTextEncode");
      const samplerNode = LiteGraph.createNode("KSampler");
      const imageNode = LiteGraph.createNode("EmptyLatentImage");
      const vaeNode = LiteGraph.createNode("VAEDecode");
      const saveNode = LiteGraph.createNode("SaveImage");
      let hrSamplerNode = null;
      let hrSteps = null;
      const ceil64 = /* @__PURE__ */ __name((v2) => Math.ceil(v2 / 64) * 64, "ceil64");
      const getWidget = /* @__PURE__ */ __name((node2, name2) => {
        return node2.widgets.find((w) => w.name === name2);
      }, "getWidget");
      const setWidgetValue = /* @__PURE__ */ __name((node2, name2, value, isOptionPrefix) => {
        const w = getWidget(node2, name2);
        if (isOptionPrefix) {
          const o = w.options.values.find((w2) => w2.startsWith(value));
          if (o) {
            w.value = o;
          } else {
            console.warn(`Unknown value '${value}' for widget '${name2}'`, node2);
            w.value = value;
          }
        } else {
          w.value = value;
        }
      }, "setWidgetValue");
      const createLoraNodes = /* @__PURE__ */ __name((clipNode, text, prevClip, prevModel) => {
        const loras = [];
        text = text.replace(/<lora:([^:]+:[^>]+)>/g, function(m, c2) {
          const s = c2.split(":");
          const weight = parseFloat(s[1]);
          if (isNaN(weight)) {
            console.warn("Invalid LORA", m);
          } else {
            loras.push({ name: s[0], weight });
          }
          return "";
        });
        for (const l of loras) {
          const loraNode = LiteGraph.createNode("LoraLoader");
          graph.add(loraNode);
          setWidgetValue(loraNode, "lora_name", l.name, true);
          setWidgetValue(loraNode, "strength_model", l.weight);
          setWidgetValue(loraNode, "strength_clip", l.weight);
          prevModel.node.connect(prevModel.index, loraNode, 0);
          prevClip.node.connect(prevClip.index, loraNode, 1);
          prevModel = { node: loraNode, index: 0 };
          prevClip = { node: loraNode, index: 1 };
        }
        prevClip.node.connect(1, clipNode, 0);
        prevModel.node.connect(0, samplerNode, 0);
        if (hrSamplerNode) {
          prevModel.node.connect(0, hrSamplerNode, 0);
        }
        return { text, prevModel, prevClip };
      }, "createLoraNodes");
      const replaceEmbeddings = /* @__PURE__ */ __name((text) => {
        if (!embeddings.length) return text;
        return text.replaceAll(
          new RegExp(
            "\\b(" + embeddings.map((e2) => e2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\b|\\b") + ")\\b",
            "ig"
          ),
          "embedding:$1"
        );
      }, "replaceEmbeddings");
      const popOpt = /* @__PURE__ */ __name((name2) => {
        const v2 = opts[name2];
        delete opts[name2];
        return v2;
      }, "popOpt");
      graph.clear();
      graph.add(ckptNode);
      graph.add(clipSkipNode);
      graph.add(positiveNode);
      graph.add(negativeNode);
      graph.add(samplerNode);
      graph.add(imageNode);
      graph.add(vaeNode);
      graph.add(saveNode);
      ckptNode.connect(1, clipSkipNode, 0);
      clipSkipNode.connect(0, positiveNode, 0);
      clipSkipNode.connect(0, negativeNode, 0);
      ckptNode.connect(0, samplerNode, 0);
      positiveNode.connect(0, samplerNode, 1);
      negativeNode.connect(0, samplerNode, 2);
      imageNode.connect(0, samplerNode, 3);
      vaeNode.connect(0, saveNode, 0);
      samplerNode.connect(0, vaeNode, 0);
      ckptNode.connect(2, vaeNode, 1);
      const handlers = {
        // @ts-expect-error fixme ts strict error
        model(v2) {
          setWidgetValue(ckptNode, "ckpt_name", v2, true);
        },
        vae() {
        },
        // @ts-expect-error fixme ts strict error
        "cfg scale"(v2) {
          setWidgetValue(samplerNode, "cfg", +v2);
        },
        // @ts-expect-error fixme ts strict error
        "clip skip"(v2) {
          setWidgetValue(clipSkipNode, "stop_at_clip_layer", -v2);
        },
        // @ts-expect-error fixme ts strict error
        sampler(v2) {
          let name2 = v2.toLowerCase().replace("++", "pp").replaceAll(" ", "_");
          if (name2.includes("karras")) {
            name2 = name2.replace("karras", "").replace(/_+$/, "");
            setWidgetValue(samplerNode, "scheduler", "karras");
          } else {
            setWidgetValue(samplerNode, "scheduler", "normal");
          }
          const w = getWidget(samplerNode, "sampler_name");
          const o = w.options.values.find(
            // @ts-expect-error fixme ts strict error
            (w2) => w2 === name2 || w2 === "sample_" + name2
          );
          if (o) {
            setWidgetValue(samplerNode, "sampler_name", o);
          }
        },
        // @ts-expect-error fixme ts strict error
        size(v2) {
          const wxh = v2.split("x");
          const w = ceil64(+wxh[0]);
          const h2 = ceil64(+wxh[1]);
          const hrUp = popOpt("hires upscale");
          const hrSz = popOpt("hires resize");
          hrSteps = popOpt("hires steps");
          let hrMethod = popOpt("hires upscaler");
          setWidgetValue(imageNode, "width", w);
          setWidgetValue(imageNode, "height", h2);
          if (hrUp || hrSz) {
            let uw, uh;
            if (hrUp) {
              uw = w * hrUp;
              uh = h2 * hrUp;
            } else {
              const s = hrSz.split("x");
              uw = +s[0];
              uh = +s[1];
            }
            let upscaleNode;
            let latentNode;
            if (hrMethod.startsWith("Latent")) {
              latentNode = upscaleNode = LiteGraph.createNode("LatentUpscale");
              graph.add(upscaleNode);
              samplerNode.connect(0, upscaleNode, 0);
              switch (hrMethod) {
                case "Latent (nearest-exact)":
                  hrMethod = "nearest-exact";
                  break;
              }
              setWidgetValue(upscaleNode, "upscale_method", hrMethod, true);
            } else {
              const decode = LiteGraph.createNode("VAEDecodeTiled");
              graph.add(decode);
              samplerNode.connect(0, decode, 0);
              ckptNode.connect(2, decode, 1);
              const upscaleLoaderNode = LiteGraph.createNode("UpscaleModelLoader");
              graph.add(upscaleLoaderNode);
              setWidgetValue(upscaleLoaderNode, "model_name", hrMethod, true);
              const modelUpscaleNode = LiteGraph.createNode(
                "ImageUpscaleWithModel"
              );
              graph.add(modelUpscaleNode);
              decode.connect(0, modelUpscaleNode, 1);
              upscaleLoaderNode.connect(0, modelUpscaleNode, 0);
              upscaleNode = LiteGraph.createNode("ImageScale");
              graph.add(upscaleNode);
              modelUpscaleNode.connect(0, upscaleNode, 0);
              const vaeEncodeNode = latentNode = LiteGraph.createNode("VAEEncodeTiled");
              graph.add(vaeEncodeNode);
              upscaleNode.connect(0, vaeEncodeNode, 0);
              ckptNode.connect(2, vaeEncodeNode, 1);
            }
            setWidgetValue(upscaleNode, "width", ceil64(uw));
            setWidgetValue(upscaleNode, "height", ceil64(uh));
            hrSamplerNode = LiteGraph.createNode("KSampler");
            graph.add(hrSamplerNode);
            ckptNode.connect(0, hrSamplerNode, 0);
            positiveNode.connect(0, hrSamplerNode, 1);
            negativeNode.connect(0, hrSamplerNode, 2);
            latentNode.connect(0, hrSamplerNode, 3);
            hrSamplerNode.connect(0, vaeNode, 0);
          }
        },
        // @ts-expect-error fixme ts strict error
        steps(v2) {
          setWidgetValue(samplerNode, "steps", +v2);
        },
        // @ts-expect-error fixme ts strict error
        seed(v2) {
          setWidgetValue(samplerNode, "seed", +v2);
        }
      };
      for (const opt in opts) {
        if (opt in handlers) {
          handlers[opt](popOpt(opt));
        }
      }
      if (hrSamplerNode) {
        setWidgetValue(
          hrSamplerNode,
          "steps",
          hrSteps ? +hrSteps : getWidget(samplerNode, "steps").value
        );
        setWidgetValue(
          hrSamplerNode,
          "cfg",
          getWidget(samplerNode, "cfg").value
        );
        setWidgetValue(
          hrSamplerNode,
          "scheduler",
          getWidget(samplerNode, "scheduler").value
        );
        setWidgetValue(
          hrSamplerNode,
          "sampler_name",
          getWidget(samplerNode, "sampler_name").value
        );
        setWidgetValue(
          hrSamplerNode,
          "denoise",
          +(popOpt("denoising strength") || "1")
        );
      }
      let n = createLoraNodes(
        positiveNode,
        positive,
        { node: clipSkipNode, index: 0 },
        { node: ckptNode, index: 0 }
      );
      positive = n.text;
      n = createLoraNodes(negativeNode, negative, n.prevClip, n.prevModel);
      negative = n.text;
      setWidgetValue(positiveNode, "text", replaceEmbeddings(positive));
      setWidgetValue(negativeNode, "text", replaceEmbeddings(negative));
      graph.arrange();
      for (const opt of [
        "model hash",
        "ensd",
        "version",
        "vae hash",
        "ti hashes",
        "lora hashes",
        "hashes"
      ]) {
        delete opts[opt];
      }
      console.warn("Unhandled parameters:", opts);
    }
  }
}
__name(importA1111, "importA1111");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.pnginfo = window.comfyAPI.pnginfo || {};
window.comfyAPI.pnginfo.getPngMetadata = getPngMetadata;
window.comfyAPI.pnginfo.getFlacMetadata = getFlacMetadata;
window.comfyAPI.pnginfo.getAvifMetadata = getAvifMetadata;
window.comfyAPI.pnginfo.getWebpMetadata = getWebpMetadata;
window.comfyAPI.pnginfo.getLatentMetadata = getLatentMetadata;
window.comfyAPI.pnginfo.importA1111 = importA1111;
async function getWorkflowDataFromFile(file) {
  if (file.type === "image/png") {
    return await getPngMetadata(file);
  }
  if (file.type === "image/avif") {
    return await getAvifMetadata(file);
  }
  if (file.type === "image/webp") {
    const pngInfo = await getWebpMetadata(file);
    const workflow = pngInfo?.workflow || pngInfo?.Workflow;
    const prompt = pngInfo?.prompt || pngInfo?.Prompt;
    return { workflow, prompt };
  }
  if (file.type === "audio/mpeg") {
    return await getMp3Metadata(file);
  }
  if (file.type === "audio/ogg") {
    return await getOggMetadata(file);
  }
  if (file.type === "audio/flac" || file.type === "audio/x-flac") {
    const pngInfo = await getFlacMetadata(file);
    const workflow = pngInfo?.workflow || pngInfo?.Workflow;
    const prompt = pngInfo?.prompt || pngInfo?.Prompt;
    return { workflow, prompt };
  }
  if (file.type === "video/webm") {
    return await getFromWebmFile(file);
  }
  if (file.name?.endsWith(".mp4") || file.name?.endsWith(".mov") || file.name?.endsWith(".m4v") || file.type === "video/mp4" || file.type === "video/quicktime" || file.type === "video/x-m4v") {
    return await getFromIsobmffFile(file);
  }
  if (file.type === "image/svg+xml" || file.name?.endsWith(".svg")) {
    return await getSvgMetadata(file);
  }
  if (file.type === "model/gltf-binary" || file.name?.endsWith(".glb")) {
    return await getGltfBinaryMetadata(file);
  }
  if (file.name?.endsWith(".latent") || file.name?.endsWith(".safetensors")) {
    return await getLatentMetadata(file);
  }
  if (file.type === "application/json" || file.name?.endsWith(".json")) {
    return getDataFromJSON(file);
  }
  return;
}
__name(getWorkflowDataFromFile, "getWorkflowDataFromFile");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.parser = window.comfyAPI.parser || {};
window.comfyAPI.parser.getWorkflowDataFromFile = getWorkflowDataFromFile;
async function extractWorkflowFromAsset(asset) {
  const baseFilename = asset.name.replace(/\.[^/.]+$/, ".json");
  const metadata = getOutputAssetMetadata(asset.user_metadata);
  if (metadata?.workflow) {
    return {
      workflow: metadata.workflow,
      filename: baseFilename
    };
  }
  try {
    const fileUrl = getAssetUrl(asset);
    const response = await fetch(fileUrl);
    if (!response.ok) {
      return { workflow: null, filename: baseFilename };
    }
    const blob = await response.blob();
    const file = new File([blob], asset.name, { type: blob.type });
    const workflowData = await getWorkflowDataFromFile(file);
    if (workflowData?.workflow) {
      const workflow = typeof workflowData.workflow === "string" ? JSON.parse(workflowData.workflow) : workflowData.workflow;
      return {
        workflow,
        filename: baseFilename
      };
    }
  } catch (error) {
    console.error("Failed to extract workflow from asset:", error);
  }
  return {
    workflow: null,
    filename: baseFilename
  };
}
__name(extractWorkflowFromAsset, "extractWorkflowFromAsset");
function supportsWorkflowMetadata(filename) {
  const lower = filename.toLowerCase();
  return lower.endsWith(".png") || lower.endsWith(".webp") || lower.endsWith(".flac") || lower.endsWith(".json");
}
__name(supportsWorkflowMetadata, "supportsWorkflowMetadata");
function detectNodeTypeFromFilename(filename) {
  const mediaType = getMediaTypeFromFilename(filename);
  switch (mediaType) {
    case "image":
      return { nodeType: "LoadImage", widgetName: "image" };
    case "video":
      return { nodeType: "LoadVideo", widgetName: "file" };
    case "audio":
      return { nodeType: "LoadAudio", widgetName: "audio" };
    default:
      return { nodeType: null, widgetName: null };
  }
}
__name(detectNodeTypeFromFilename, "detectNodeTypeFromFilename");
const zMediaKindSchema = z.enum(["video", "audio", "image", "3D"]);
const zDimensionsSchema = z.object({
  width: z.number().positive(),
  height: z.number().positive()
});
assetItemSchema.extend({
  // New required fields
  kind: zMediaKindSchema,
  src: z.string().url(),
  // New optional fields
  duration: z.number().nonnegative().optional(),
  dimensions: zDimensionsSchema.optional()
});
z.object({
  type: z.enum(["input", "output"]),
  outputCount: z.number().positive().optional()
  // Only for output context
});
const MediaAssetKey = /* @__PURE__ */ Symbol("mediaAsset");
function useMediaAssetActions() {
  const toast = useToast();
  const dialogStore = useDialogStore();
  const mediaContext = inject(MediaAssetKey, null);
  const { copyToClipboard } = useCopyToClipboard();
  const workflowActions = useWorkflowActionsService();
  const litegraphService = useLitegraphService();
  const nodeDefStore = useNodeDefStore();
  const deleteAssetApi = /* @__PURE__ */ __name(async (asset, assetType) => {
    if (assetType === "output") {
      const promptId = asset.id || getOutputAssetMetadata(asset.user_metadata)?.promptId;
      if (!promptId) {
        throw new Error("Unable to extract prompt ID from asset");
      }
      await api.deleteItem("history", promptId);
    } else {
      {
        throw new Error(t("mediaAsset.deletingImportedFilesCloudOnly"));
      }
    }
  }, "deleteAssetApi");
  const downloadAsset = /* @__PURE__ */ __name((asset) => {
    const targetAsset = asset ?? mediaContext?.asset.value;
    if (!targetAsset) return;
    try {
      const filename = targetAsset.name;
      let downloadUrl;
      if (isCloud && targetAsset.preview_url) ;
      else {
        downloadUrl = getAssetUrl(targetAsset);
      }
      downloadFile(downloadUrl, filename);
      toast.add({
        severity: "success",
        summary: t("g.success"),
        detail: t("g.downloadStarted"),
        life: 2e3
      });
    } catch (error) {
      toast.add({
        severity: "error",
        summary: t("g.error"),
        detail: t("g.failedToDownloadImage"),
        life: 3e3
      });
    }
  }, "downloadAsset");
  const downloadMultipleAssets = /* @__PURE__ */ __name((assets) => {
    if (!assets || assets.length === 0) return;
    try {
      assets.forEach((asset) => {
        const filename = asset.name;
        let downloadUrl;
        if (isCloud && asset.preview_url) ;
        else {
          downloadUrl = getAssetUrl(asset);
        }
        downloadFile(downloadUrl, filename);
      });
      toast.add({
        severity: "success",
        summary: t("g.success"),
        detail: t("mediaAsset.selection.downloadsStarted", {
          count: assets.length
        }),
        life: 2e3
      });
    } catch (error) {
      console.error("Failed to download assets:", error);
      toast.add({
        severity: "error",
        summary: t("g.error"),
        detail: t("g.failedToDownloadImage"),
        life: 3e3
      });
    }
  }, "downloadMultipleAssets");
  const confirmDelete = /* @__PURE__ */ __name(async (asset) => {
    const assetType = getAssetType(asset);
    return new Promise((resolve) => {
      dialogStore.showDialog({
        key: "delete-asset-confirmation",
        title: t("mediaAsset.deleteAssetTitle"),
        component: ConfirmationDialogContent,
        props: {
          message: t("mediaAsset.deleteAssetDescription"),
          type: "delete",
          itemList: [asset.name],
          onConfirm: /* @__PURE__ */ __name(async () => {
            const success = await deleteAsset(asset, assetType);
            resolve(success);
          }, "onConfirm"),
          onCancel: /* @__PURE__ */ __name(() => {
            resolve(false);
          }, "onCancel")
        }
      });
    });
  }, "confirmDelete");
  const deleteAsset = /* @__PURE__ */ __name(async (asset, assetType) => {
    const assetsStore = useAssetsStore();
    try {
      await deleteAssetApi(asset, assetType);
      if (assetType === "output") {
        await assetsStore.updateHistory();
      } else {
        await assetsStore.updateInputs();
      }
      toast.add({
        severity: "success",
        summary: t("g.success"),
        detail: t("mediaAsset.assetDeletedSuccessfully"),
        life: 2e3
      });
      return true;
    } catch (error) {
      console.error("Failed to delete asset:", error);
      const errorMessage = error instanceof Error ? error.message : "";
      const isCloudWarning = errorMessage.includes("Cloud");
      toast.add({
        severity: isCloudWarning ? "warn" : "error",
        summary: isCloudWarning ? t("g.warning") : t("g.error"),
        detail: errorMessage || t("mediaAsset.failedToDeleteAsset"),
        life: 3e3
      });
      return false;
    }
  }, "deleteAsset");
  const copyJobId = /* @__PURE__ */ __name(async (asset) => {
    const targetAsset = asset ?? mediaContext?.asset.value;
    if (!targetAsset) return;
    const metadata = getOutputAssetMetadata(targetAsset.user_metadata);
    const promptId = targetAsset.id || metadata?.promptId;
    if (!promptId) {
      toast.add({
        severity: "warn",
        summary: t("g.warning"),
        detail: t("mediaAsset.noJobIdFound"),
        life: 2e3
      });
      return;
    }
    await copyToClipboard(promptId);
  }, "copyJobId");
  const addWorkflow = /* @__PURE__ */ __name(async (asset) => {
    const targetAsset = asset ?? mediaContext?.asset.value;
    if (!targetAsset) return;
    const { nodeType, widgetName } = detectNodeTypeFromFilename(
      targetAsset.name
    );
    if (!nodeType || !widgetName) {
      toast.add({
        severity: "warn",
        summary: t("g.warning"),
        detail: t("mediaAsset.unsupportedFileType"),
        life: 2e3
      });
      return;
    }
    const nodeDef = nodeDefStore.nodeDefsByName[nodeType];
    if (!nodeDef) {
      toast.add({
        severity: "error",
        summary: t("g.error"),
        detail: t("mediaAsset.nodeTypeNotFound", { nodeType }),
        life: 3e3
      });
      return;
    }
    const node2 = litegraphService.addNodeOnGraph(nodeDef, {
      pos: litegraphService.getCanvasCenter()
    });
    if (!node2) {
      toast.add({
        severity: "error",
        summary: t("g.error"),
        detail: t("mediaAsset.failedToCreateNode"),
        life: 3e3
      });
      return;
    }
    const metadata = getOutputAssetMetadata(targetAsset.user_metadata);
    const assetType = getAssetType(targetAsset, "input");
    const annotated = createAnnotatedPath(
      {
        filename: targetAsset.name,
        subfolder: metadata?.subfolder || "",
        type: isResultItemType(assetType) ? assetType : void 0
      },
      {
        rootFolder: isResultItemType(assetType) ? assetType : void 0
      }
    );
    const widget = node2.widgets?.find((w) => w.name === widgetName);
    if (widget) {
      widget.value = annotated;
      widget.callback?.(annotated);
    }
    node2.graph?.setDirtyCanvas(true, true);
    toast.add({
      severity: "success",
      summary: t("g.success"),
      detail: t("mediaAsset.nodeAddedToWorkflow", { nodeType }),
      life: 2e3
    });
  }, "addWorkflow");
  const openWorkflow = /* @__PURE__ */ __name(async (asset) => {
    const targetAsset = asset ?? mediaContext?.asset.value;
    if (!targetAsset) return;
    const { workflow, filename } = await extractWorkflowFromAsset(targetAsset);
    const result = await workflowActions.openWorkflowAction(workflow, filename);
    if (!result.success) {
      toast.add({
        severity: "warn",
        summary: t("g.warning"),
        detail: result.error || t("mediaAsset.noWorkflowDataFound"),
        life: 2e3
      });
    } else {
      toast.add({
        severity: "success",
        summary: t("g.success"),
        detail: t("mediaAsset.workflowOpenedInNewTab"),
        life: 2e3
      });
    }
  }, "openWorkflow");
  const exportWorkflow = /* @__PURE__ */ __name(async (asset) => {
    const targetAsset = asset ?? mediaContext?.asset.value;
    if (!targetAsset) return;
    const { workflow, filename } = await extractWorkflowFromAsset(targetAsset);
    const result = await workflowActions.exportWorkflowAction(
      workflow,
      filename
    );
    if (!result.success) {
      const isNoWorkflow = result.error?.includes("No workflow");
      toast.add({
        severity: isNoWorkflow ? "warn" : "error",
        summary: isNoWorkflow ? t("g.warning") : t("g.error"),
        detail: result.error || t("mediaAsset.failedToExportWorkflow"),
        life: 3e3
      });
    } else {
      toast.add({
        severity: "success",
        summary: t("g.success"),
        detail: t("mediaAsset.workflowExportedSuccessfully"),
        life: 2e3
      });
    }
  }, "exportWorkflow");
  const deleteMultipleAssets = /* @__PURE__ */ __name(async (assets) => {
    if (!assets || assets.length === 0) return;
    const assetsStore = useAssetsStore();
    return new Promise((resolve) => {
      dialogStore.showDialog({
        key: "delete-multiple-assets-confirmation",
        title: t("mediaAsset.deleteSelectedTitle"),
        component: ConfirmationDialogContent,
        props: {
          message: t("mediaAsset.deleteSelectedDescription", {
            count: assets.length
          }),
          type: "delete",
          itemList: assets.map((asset) => asset.name),
          onConfirm: /* @__PURE__ */ __name(async () => {
            try {
              const results = await Promise.allSettled(
                assets.map(
                  (asset) => deleteAssetApi(asset, getAssetType(asset))
                )
              );
              const succeeded = results.filter(
                (r) => r.status === "fulfilled"
              ).length;
              const failed = results.filter((r) => r.status === "rejected");
              failed.forEach((result, index2) => {
                console.warn(
                  `Failed to delete asset ${assets[index2].name}:`,
                  result.reason
                );
              });
              const hasOutputAssets = assets.some(
                (a) => getAssetType(a) === "output"
              );
              const hasInputAssets = assets.some(
                (a) => getAssetType(a) === "input"
              );
              if (hasOutputAssets) {
                await assetsStore.updateHistory();
              }
              if (hasInputAssets) {
                await assetsStore.updateInputs();
              }
              if (failed.length === 0) {
                toast.add({
                  severity: "success",
                  summary: t("g.success"),
                  detail: t("mediaAsset.selection.assetsDeletedSuccessfully", {
                    count: succeeded
                  }),
                  life: 2e3
                });
              } else if (succeeded === 0) {
                toast.add({
                  severity: "error",
                  summary: t("g.error"),
                  detail: t("mediaAsset.selection.failedToDeleteAssets"),
                  life: 3e3
                });
              } else {
                toast.add({
                  severity: "warn",
                  summary: t("g.warning"),
                  detail: t("mediaAsset.selection.partialDeleteSuccess", {
                    succeeded,
                    failed: failed.length
                  }),
                  life: 3e3
                });
              }
            } catch (error) {
              console.error("Failed to delete assets:", error);
              toast.add({
                severity: "error",
                summary: t("g.error"),
                detail: t("mediaAsset.selection.failedToDeleteAssets"),
                life: 3e3
              });
            }
            resolve();
          }, "onConfirm"),
          onCancel: /* @__PURE__ */ __name(() => {
            resolve();
          }, "onCancel")
        }
      });
    });
  }, "deleteMultipleAssets");
  return {
    downloadAsset,
    downloadMultipleAssets,
    confirmDelete,
    deleteAsset,
    deleteMultipleAssets,
    copyJobId,
    addWorkflow,
    openWorkflow,
    exportWorkflow
  };
}
__name(useMediaAssetActions, "useMediaAssetActions");
const _sfc_main$21 = /* @__PURE__ */ defineComponent({
  __name: "MediaAssetContextMenu",
  props: {
    asset: {},
    assetType: {},
    fileKind: {},
    showDeleteButton: { type: Boolean }
  },
  emits: ["zoom", "asset-deleted"],
  setup(__props, { expose: __expose, emit: __emit }) {
    const emit = __emit;
    const contextMenu2 = ref();
    const actions = useMediaAssetActions();
    const { t: t2 } = useI18n();
    onClickOutside(
      computed(() => contextMenu2.value?.$el),
      () => {
        hide();
      }
    );
    const showAddToWorkflow = computed(() => {
      if (__props.assetType === "output") return true;
      if (__props.assetType === "input" && __props.asset?.name) {
        const { nodeType } = detectNodeTypeFromFilename(__props.asset.name);
        return nodeType !== null;
      }
      return false;
    });
    const showWorkflowActions = computed(() => {
      if (__props.assetType === "output") return true;
      if (__props.assetType === "input" && __props.asset?.name) {
        return supportsWorkflowMetadata(__props.asset.name);
      }
      return false;
    });
    const showCopyJobId = computed(() => {
      return __props.assetType !== "input";
    });
    const shouldShowDeleteButton = computed(() => {
      const propAllows = __props.showDeleteButton ?? true;
      const typeAllows = __props.assetType === "output" || __props.assetType === "input" && isCloud;
      return propAllows && typeAllows;
    });
    const contextMenuItems = computed(() => {
      if (!__props.asset) return [];
      const items = [];
      if (__props.fileKind !== "3D") {
        items.push({
          label: t2("mediaAsset.actions.inspect"),
          icon: "icon-[lucide--zoom-in]",
          command: /* @__PURE__ */ __name(() => emit("zoom"), "command")
        });
      }
      if (showAddToWorkflow.value) {
        items.push({
          label: t2("mediaAsset.actions.addToWorkflow"),
          icon: "icon-[comfy--node]",
          command: /* @__PURE__ */ __name(() => actions.addWorkflow(__props.asset), "command")
        });
      }
      items.push({
        label: t2("mediaAsset.actions.download"),
        icon: "icon-[lucide--download]",
        command: /* @__PURE__ */ __name(() => actions.downloadAsset(__props.asset), "command")
      });
      if (showWorkflowActions.value) {
        items.push({ separator: true });
        items.push({
          label: t2("mediaAsset.actions.openWorkflow"),
          icon: "icon-[comfy--workflow]",
          command: /* @__PURE__ */ __name(() => actions.openWorkflow(__props.asset), "command")
        });
        items.push({
          label: t2("mediaAsset.actions.exportWorkflow"),
          icon: "icon-[lucide--file-output]",
          command: /* @__PURE__ */ __name(() => actions.exportWorkflow(__props.asset), "command")
        });
      }
      if (showCopyJobId.value) {
        items.push({ separator: true });
        items.push({
          label: t2("mediaAsset.actions.copyJobId"),
          icon: "icon-[lucide--copy]",
          command: /* @__PURE__ */ __name(async () => {
            await actions.copyJobId(__props.asset);
          }, "command")
        });
      }
      if (shouldShowDeleteButton.value) {
        items.push({ separator: true });
        items.push({
          label: t2("mediaAsset.actions.delete"),
          icon: "icon-[lucide--trash-2]",
          command: /* @__PURE__ */ __name(async () => {
            if (__props.asset) {
              const success = await actions.confirmDelete(__props.asset);
              if (success) {
                emit("asset-deleted");
              }
            }
          }, "command")
        });
      }
      return items;
    });
    const show = /* @__PURE__ */ __name((event) => {
      contextMenu2.value?.show(event);
    }, "show");
    const hide = /* @__PURE__ */ __name(() => {
      contextMenu2.value?.hide();
    }, "hide");
    __expose({ show, hide });
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$f), {
        ref_key: "contextMenu",
        ref: contextMenu2,
        model: contextMenuItems.value,
        pt: {
          root: {
            class: unref(cn)(
              "rounded-lg",
              "bg-secondary-background text-base-foreground",
              "shadow-lg"
            )
          }
        }
      }, {
        item: withCtx(({ item, props }) => [
          createVNode(_sfc_main$2A, mergeProps({
            variant: "secondary",
            size: "sm",
            class: "w-full justify-start"
          }, props.action), {
            default: withCtx(() => [
              createBaseVNode("i", {
                class: normalizeClass([item.icon, "size-4"])
              }, null, 2),
              createBaseVNode("span", null, toDisplayString(typeof item.label === "function" ? item.label() : item.label ?? ""), 1)
            ]),
            _: 2
          }, 1040)
        ]),
        _: 1
      }, 8, ["model", "pt"]);
    };
  }
});
const _hoisted_1$1J = {
  key: 0,
  class: "size-full animate-pulse rounded-lg bg-modal-card-placeholder-background"
};
const _hoisted_2$1m = {
  key: 0,
  class: "flex flex-wrap items-center gap-1"
};
const _hoisted_3$W = {
  key: 0,
  class: "flex flex-col items-center justify-between gap-1"
};
const _sfc_main$20 = /* @__PURE__ */ defineComponent({
  __name: "MediaAssetCard",
  props: {
    asset: {},
    loading: { type: Boolean },
    selected: { type: Boolean },
    showOutputCount: { type: Boolean },
    outputCount: {},
    showDeleteButton: { type: Boolean },
    openContextMenuId: {}
  },
  emits: ["click", "zoom", "output-count-click", "asset-deleted", "context-menu-opened"],
  setup(__props, { emit: __emit }) {
    const mediaComponents = {
      top: {
        video: defineAsyncComponent(() => __vitePreload(() => import("./MediaVideoTop-DrDqLo2M.js"), true ? __vite__mapDeps([14,1,2]) : void 0, import.meta.url)),
        audio: defineAsyncComponent(() => __vitePreload(() => import("./MediaAudioTop-CgldzfMV.js"), true ? __vite__mapDeps([15,1,2]) : void 0, import.meta.url)),
        image: defineAsyncComponent(() => __vitePreload(() => import("./MediaImageTop-BmT4ess7.js"), true ? __vite__mapDeps([16,1,2]) : void 0, import.meta.url)),
        "3D": defineAsyncComponent(() => __vitePreload(() => import("./Media3DTop-BUnGfk-N.js"), true ? __vite__mapDeps([17,1,2,4,8,9,10,11,12]) : void 0, import.meta.url))
      },
      bottom: {
        video: defineAsyncComponent(() => __vitePreload(() => import("./MediaVideoBottom-uKfTCjRu.js"), true ? __vite__mapDeps([18,19,1,2,4,8,9,10,11,12]) : void 0, import.meta.url)),
        audio: defineAsyncComponent(() => __vitePreload(() => import("./MediaAudioBottom-v4U8_pF9.js"), true ? __vite__mapDeps([20,19,1,2,4,8,9,10,11,12]) : void 0, import.meta.url)),
        image: defineAsyncComponent(() => __vitePreload(() => import("./MediaImageBottom-Bd4M6dHo.js"), true ? __vite__mapDeps([21,19,1,2,4,8,9,10,11,12]) : void 0, import.meta.url)),
        "3D": defineAsyncComponent(() => __vitePreload(() => import("./Media3DBottom-Dbp8kDWi.js"), true ? __vite__mapDeps([22,19,1,2,4,8,9,10,11,12]) : void 0, import.meta.url))
      }
    };
    function getTopComponent(kind) {
      return mediaComponents.top[kind] || mediaComponents.top.image;
    }
    __name(getTopComponent, "getTopComponent");
    function getBottomComponent(kind) {
      return mediaComponents.bottom[kind] || mediaComponents.bottom.image;
    }
    __name(getBottomComponent, "getBottomComponent");
    const emit = __emit;
    const cardContainerRef = ref();
    const contextMenu2 = ref();
    const isVideoPlaying = ref(false);
    const showVideoControls = ref(false);
    const imageDimensions = ref();
    const isHovered = useElementHover(cardContainerRef);
    const actions = useMediaAssetActions();
    const assetType = computed(() => {
      return getAssetType$1(__props.asset?.tags);
    });
    const fileKind = computed(() => {
      return getMediaTypeFromFilename(__props.asset?.name || "");
    });
    const adaptedAsset = computed(() => {
      if (!__props.asset) return void 0;
      return {
        id: __props.asset.id,
        name: __props.asset.name,
        kind: fileKind.value,
        src: __props.asset.preview_url || "",
        size: __props.asset.size,
        tags: __props.asset.tags || [],
        created_at: __props.asset.created_at,
        duration: __props.asset.user_metadata?.duration ? Number(__props.asset.user_metadata.duration) : void 0,
        dimensions: imageDimensions.value
      };
    });
    provide(MediaAssetKey, {
      asset: toRef$2(() => adaptedAsset.value),
      context: toRef$2(() => ({ type: assetType.value })),
      isVideoPlaying,
      showVideoControls
    });
    const containerClasses = computed(
      () => cn(
        "gap-1 select-none group",
        __props.selected ? "ring-3 ring-inset ring-modal-card-border-highlighted" : "hover:bg-modal-card-background-hovered"
      )
    );
    const formattedDuration = computed(() => {
      const executionTime = __props.asset?.user_metadata?.executionTimeInSeconds;
      if (executionTime !== void 0 && executionTime !== null) {
        return `${Number(executionTime).toFixed(2)}s`;
      }
      const duration = __props.asset?.user_metadata?.duration;
      if (!duration) return "";
      return formatDuration(Number(duration));
    });
    const durationChipClasses = computed(() => {
      if (fileKind.value === "audio") {
        return "-translate-y-11";
      }
      if (fileKind.value === "video" && showVideoControls.value) {
        return "-translate-y-16";
      }
      return "";
    });
    const showStaticChips = computed(
      () => !__props.loading && !!__props.asset && !isHovered.value && !isVideoPlaying.value && formattedDuration.value
    );
    const showActionsOverlay = computed(
      () => !__props.loading && !!__props.asset && (isHovered.value || isVideoPlaying.value)
    );
    const handleZoomClick = /* @__PURE__ */ __name(() => {
      if (__props.asset) {
        emit("zoom", __props.asset);
      }
    }, "handleZoomClick");
    const handleImageLoaded = /* @__PURE__ */ __name((width2, height) => {
      imageDimensions.value = { width: width2, height };
    }, "handleImageLoaded");
    const handleOutputCountClick = /* @__PURE__ */ __name(() => {
      emit("output-count-click");
    }, "handleOutputCountClick");
    const handleContextMenu = /* @__PURE__ */ __name((event) => {
      emit("context-menu-opened");
      contextMenu2.value?.show(event);
    }, "handleContextMenu");
    whenever(
      () => __props.openContextMenuId && __props.openContextMenuId !== __props.asset?.id,
      () => {
        contextMenu2.value?.hide();
      }
    );
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock(Fragment, null, [
        createVNode(_sfc_main$25, {
          ref_key: "cardContainerRef",
          ref: cardContainerRef,
          role: "button",
          "aria-label": _ctx.asset ? _ctx.$t("assetBrowser.ariaLabel.assetCard", {
            name: _ctx.asset.name,
            type: fileKind.value
          }) : _ctx.$t("assetBrowser.ariaLabel.loadingAsset"),
          tabindex: _ctx.loading ? -1 : 0,
          size: "mini",
          variant: "ghost",
          rounded: "lg",
          class: normalizeClass(containerClasses.value),
          "data-selected": _ctx.selected,
          onClick: _cache[3] || (_cache[3] = withModifiers(($event) => _ctx.$emit("click"), ["stop"])),
          onContextmenu: withModifiers(handleContextMenu, ["prevent"])
        }, {
          top: withCtx(() => [
            createVNode(_sfc_main$24, {
              ratio: "square",
              "bottom-left-class": durationChipClasses.value,
              "bottom-right-class": durationChipClasses.value
            }, createSlots({
              "top-left": withCtx(() => [
                showStaticChips.value ? (openBlock(), createElementBlock("div", _hoisted_2$1m, [
                  formattedDuration.value ? (openBlock(), createBlock(_sfc_main$23, {
                    key: 0,
                    variant: "gray",
                    label: formattedDuration.value
                  }, null, 8, ["label"])) : createCommentVNode("", true)
                ])) : showActionsOverlay.value ? (openBlock(), createBlock(_sfc_main$2t, { key: 1 }, {
                  default: withCtx(() => [
                    createVNode(_sfc_main$2A, {
                      size: "icon",
                      onClick: withModifiers(handleZoomClick, ["stop"])
                    }, {
                      default: withCtx(() => _cache[5] || (_cache[5] = [
                        createBaseVNode("i", { class: "icon-[lucide--zoom-in] size-4" }, null, -1)
                      ])),
                      _: 1
                    }),
                    createVNode(_sfc_main$2A, {
                      size: "icon",
                      onClick: withModifiers(handleContextMenu, ["stop"])
                    }, {
                      default: withCtx(() => _cache[6] || (_cache[6] = [
                        createBaseVNode("i", { class: "icon-[lucide--ellipsis] size-4" }, null, -1)
                      ])),
                      _: 1
                    })
                  ]),
                  _: 1
                })) : createCommentVNode("", true)
              ]),
              default: withCtx(() => [
                _ctx.loading ? (openBlock(), createElementBlock("div", _hoisted_1$1J)) : _ctx.asset && adaptedAsset.value ? (openBlock(), createBlock(resolveDynamicComponent(getTopComponent(fileKind.value)), {
                  key: 1,
                  asset: adaptedAsset.value,
                  context: { type: assetType.value },
                  onView: handleZoomClick,
                  onDownload: _cache[0] || (_cache[0] = ($event) => unref(actions).downloadAsset()),
                  onVideoPlayingStateChanged: _cache[1] || (_cache[1] = ($event) => isVideoPlaying.value = $event),
                  onVideoControlsChanged: _cache[2] || (_cache[2] = ($event) => showVideoControls.value = $event),
                  onImageLoaded: handleImageLoaded
                }, null, 40, ["asset", "context"])) : createCommentVNode("", true)
              ]),
              _: 2
            }, [
              _ctx.showOutputCount ? {
                name: "top-right",
                fn: withCtx(() => [
                  withDirectives((openBlock(), createBlock(_sfc_main$2A, {
                    variant: "secondary",
                    size: "sm",
                    onClick: withModifiers(handleOutputCountClick, ["stop"])
                  }, {
                    default: withCtx(() => [
                      _cache[7] || (_cache[7] = createBaseVNode("i", { class: "icon-[lucide--layers] size-4" }, null, -1)),
                      createBaseVNode("span", null, toDisplayString(_ctx.outputCount), 1)
                    ]),
                    _: 1
                  })), [
                    [
                      _directive_tooltip,
                      _ctx.$t("mediaAsset.actions.seeMoreOutputs"),
                      void 0,
                      {
                        top: true,
                        "pt:pointer-events-none": true
                      }
                    ]
                  ])
                ]),
                key: "0"
              } : void 0
            ]), 1032, ["bottom-left-class", "bottom-right-class"])
          ]),
          bottom: withCtx(() => [
            createVNode(_sfc_main$26, null, {
              default: withCtx(() => [
                _ctx.loading ? (openBlock(), createElementBlock("div", _hoisted_3$W, _cache[8] || (_cache[8] = [
                  createBaseVNode("div", { class: "h-4 w-2/3 animate-pulse rounded bg-modal-card-background" }, null, -1),
                  createBaseVNode("div", { class: "h-3 w-1/2 animate-pulse rounded bg-modal-card-background" }, null, -1)
                ]))) : _ctx.asset && adaptedAsset.value ? (openBlock(), createBlock(resolveDynamicComponent(getBottomComponent(fileKind.value)), {
                  key: 1,
                  asset: adaptedAsset.value,
                  context: { type: assetType.value }
                }, null, 8, ["asset", "context"])) : createCommentVNode("", true)
              ]),
              _: 1
            })
          ]),
          _: 1
        }, 8, ["aria-label", "tabindex", "class", "data-selected"]),
        _ctx.asset ? (openBlock(), createBlock(_sfc_main$21, {
          key: 0,
          ref_key: "contextMenu",
          ref: contextMenu2,
          asset: _ctx.asset,
          "asset-type": assetType.value,
          "file-kind": fileKind.value,
          "show-delete-button": _ctx.showDeleteButton,
          onZoom: handleZoomClick,
          onAssetDeleted: _cache[4] || (_cache[4] = ($event) => emit("asset-deleted"))
        }, null, 8, ["asset", "asset-type", "file-kind", "show-delete-button"])) : createCommentVNode("", true)
      ], 64);
    };
  }
});
const _hoisted_1$1I = { class: "relative inline-flex items-center" };
const _hoisted_2$1l = { class: "flex min-w-40 flex-col gap-2 p-2" };
const _sfc_main$1$ = /* @__PURE__ */ defineComponent({
  __name: "MediaAssetFilterButton",
  emits: ["menuOpened", "menuClosed"],
  setup(__props) {
    const popover = ref();
    const toggle = /* @__PURE__ */ __name((event) => {
      popover.value?.toggle(event);
    }, "toggle");
    const hide = /* @__PURE__ */ __name(() => {
      popover.value?.hide();
    }, "hide");
    const pt = computed(() => ({
      root: {
        class: cn("absolute z-50")
      },
      content: {
        class: cn(
          "mt-1 rounded-lg bg-base-background text-base-foreground border border-border-default shadow-lg"
        )
      }
    }));
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1I, [
        createVNode(_sfc_main$2A, {
          variant: "secondary",
          size: "icon",
          onClick: toggle
        }, {
          default: withCtx(() => _cache[2] || (_cache[2] = [
            createBaseVNode("i", { class: "icon-[lucide--list-filter]" }, null, -1)
          ])),
          _: 1
        }),
        createVNode(unref(script$g), {
          ref_key: "popover",
          ref: popover,
          "append-to": "#vue-app",
          "auto-z-index": "",
          "base-z-index": 1e3,
          dismissable: "",
          "close-on-escape": "",
          unstyled: "",
          pt: pt.value,
          onShow: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("menuOpened")),
          onHide: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("menuClosed"))
        }, {
          default: withCtx(() => [
            createBaseVNode("div", _hoisted_2$1l, [
              renderSlot(_ctx.$slots, "default", { close: hide })
            ])
          ]),
          _: 3
        }, 8, ["pt"])
      ]);
    };
  }
});
const _hoisted_1$1H = { class: "flex flex-col gap-0 p-0 m-0" };
const _hoisted_2$1k = ["aria-checked", "onClick", "onKeydown"];
const _hoisted_3$V = {
  key: 0,
  class: "icon-[lucide--check] text-xs font-bold text-white"
};
const _hoisted_4$I = { class: "text-sm" };
const _sfc_main$1_ = /* @__PURE__ */ defineComponent({
  __name: "MediaAssetFilterMenu",
  props: {
    mediaTypeFilters: {}
  },
  emits: ["update:mediaTypeFilters"],
  setup(__props, { emit: __emit }) {
    const emit = __emit;
    const filters = [
      { type: "image", label: "sideToolbar.mediaAssets.filterImage" },
      { type: "video", label: "sideToolbar.mediaAssets.filterVideo" },
      { type: "audio", label: "sideToolbar.mediaAssets.filterAudio" },
      { type: "3d", label: "sideToolbar.mediaAssets.filter3D" }
    ];
    const toggleMediaType = /* @__PURE__ */ __name((type) => {
      const isCurrentlySelected = __props.mediaTypeFilters.includes(type);
      if (isCurrentlySelected) {
        emit(
          "update:mediaTypeFilters",
          __props.mediaTypeFilters.filter((t2) => t2 !== type)
        );
      } else {
        emit("update:mediaTypeFilters", [...__props.mediaTypeFilters, type]);
      }
    }, "toggleMediaType");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1H, [
        (openBlock(), createElementBlock(Fragment, null, renderList(filters, (filter2) => {
          return createBaseVNode("div", {
            key: filter2.type,
            class: "flex h-10 cursor-pointer items-center gap-2 rounded-lg px-2 hover:bg-secondary-background-hover",
            tabindex: "0",
            role: "checkbox",
            "aria-checked": _ctx.mediaTypeFilters.includes(filter2.type),
            onClick: /* @__PURE__ */ __name(($event) => toggleMediaType(filter2.type), "onClick"),
            onKeydown: [
              withKeys(withModifiers(($event) => toggleMediaType(filter2.type), ["prevent"]), ["enter"]),
              withKeys(withModifiers(($event) => toggleMediaType(filter2.type), ["prevent"]), ["space"])
            ]
          }, [
            createBaseVNode("div", {
              class: normalizeClass([
                "flex h-4 w-4 shrink-0 items-center justify-center rounded p-0.5 transition-all duration-200",
                _ctx.mediaTypeFilters.includes(filter2.type) ? "bg-primary-background border-primary-background" : "bg-secondary-background"
              ])
            }, [
              _ctx.mediaTypeFilters.includes(filter2.type) ? (openBlock(), createElementBlock("i", _hoisted_3$V)) : createCommentVNode("", true)
            ], 2),
            createBaseVNode("span", _hoisted_4$I, toDisplayString(_ctx.$t(filter2.label)), 1)
          ], 40, _hoisted_2$1k);
        }), 64))
      ]);
    };
  }
});
const _hoisted_1$1G = { class: "relative inline-flex items-center" };
const _hoisted_2$1j = { class: "flex min-w-40 flex-col gap-2 p-2" };
const _sfc_main$1Z = /* @__PURE__ */ defineComponent({
  __name: "MediaAssetSortButton",
  emits: ["menuOpened", "menuClosed"],
  setup(__props) {
    const popover = ref();
    const toggle = /* @__PURE__ */ __name((event) => {
      popover.value?.toggle(event);
    }, "toggle");
    const hide = /* @__PURE__ */ __name(() => {
      popover.value?.hide();
    }, "hide");
    const pt = computed(() => ({
      root: {
        class: cn("absolute z-50")
      },
      content: {
        class: cn(
          "mt-1 rounded-lg",
          "bg-base-background text-base-foreground border border-border-default",
          "shadow-lg"
        )
      }
    }));
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1G, [
        createVNode(_sfc_main$2A, {
          variant: "secondary",
          size: "icon",
          onClick: toggle
        }, {
          default: withCtx(() => _cache[2] || (_cache[2] = [
            createBaseVNode("i", { class: "icon-[lucide--arrow-up-down]" }, null, -1)
          ])),
          _: 1
        }),
        createVNode(unref(script$g), {
          ref_key: "popover",
          ref: popover,
          "append-to": "body",
          "auto-z-index": true,
          "base-z-index": 1e3,
          dismissable: true,
          "close-on-escape": true,
          unstyled: "",
          pt: pt.value,
          onShow: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("menuOpened")),
          onHide: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("menuClosed"))
        }, {
          default: withCtx(() => [
            createBaseVNode("div", _hoisted_2$1j, [
              renderSlot(_ctx.$slots, "default", { close: hide })
            ])
          ]),
          _: 3
        }, 8, ["pt"])
      ]);
    };
  }
});
const _hoisted_1$1F = { class: "flex flex-col" };
const _hoisted_2$1i = {
  key: 0,
  class: "icon-[lucide--check] size-4"
};
const _hoisted_3$U = {
  key: 0,
  class: "icon-[lucide--check] size-4"
};
const _hoisted_4$H = {
  key: 0,
  class: "icon-[lucide--check] size-4"
};
const _hoisted_5$B = {
  key: 0,
  class: "icon-[lucide--check] size-4"
};
const _sfc_main$1Y = /* @__PURE__ */ defineComponent({
  __name: "MediaAssetSortMenu",
  props: /* @__PURE__ */ mergeModels({
    close: { type: Function },
    showGenerationTimeSort: { type: Boolean, default: false }
  }, {
    "sortBy": { required: true },
    "sortByModifiers": {}
  }),
  emits: ["update:sortBy"],
  setup(__props) {
    const sortBy2 = useModel(__props, "sortBy");
    const handleSortChange = /* @__PURE__ */ __name((value) => {
      sortBy2.value = value;
      __props.close();
    }, "handleSortChange");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1F, [
        createVNode(_sfc_main$2A, {
          variant: "textonly",
          class: "justify-start",
          onClick: _cache[0] || (_cache[0] = ($event) => handleSortChange("newest"))
        }, {
          default: withCtx(() => [
            createBaseVNode("span", null, toDisplayString(_ctx.$t("sideToolbar.mediaAssets.sortNewestFirst")), 1),
            sortBy2.value === "newest" ? (openBlock(), createElementBlock("i", _hoisted_2$1i)) : createCommentVNode("", true)
          ]),
          _: 1
        }),
        createVNode(_sfc_main$2A, {
          variant: "textonly",
          class: "justify-start",
          onClick: _cache[1] || (_cache[1] = ($event) => handleSortChange("oldest"))
        }, {
          default: withCtx(() => [
            createBaseVNode("span", null, toDisplayString(_ctx.$t("sideToolbar.mediaAssets.sortOldestFirst")), 1),
            sortBy2.value === "oldest" ? (openBlock(), createElementBlock("i", _hoisted_3$U)) : createCommentVNode("", true)
          ]),
          _: 1
        }),
        _ctx.showGenerationTimeSort ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
          createVNode(_sfc_main$2A, {
            variant: "textonly",
            class: "justify-start",
            onClick: _cache[2] || (_cache[2] = ($event) => handleSortChange("longest"))
          }, {
            default: withCtx(() => [
              createBaseVNode("span", null, toDisplayString(_ctx.$t("sideToolbar.mediaAssets.sortLongestFirst")), 1),
              sortBy2.value === "longest" ? (openBlock(), createElementBlock("i", _hoisted_4$H)) : createCommentVNode("", true)
            ]),
            _: 1
          }),
          createVNode(_sfc_main$2A, {
            variant: "textonly",
            class: "justify-start",
            onClick: _cache[3] || (_cache[3] = ($event) => handleSortChange("fastest"))
          }, {
            default: withCtx(() => [
              createBaseVNode("span", null, toDisplayString(_ctx.$t("sideToolbar.mediaAssets.sortFastestFirst")), 1),
              sortBy2.value === "fastest" ? (openBlock(), createElementBlock("i", _hoisted_5$B)) : createCommentVNode("", true)
            ]),
            _: 1
          })
        ], 64)) : createCommentVNode("", true)
      ]);
    };
  }
});
const _hoisted_1$1E = { class: "flex gap-3" };
const _sfc_main$1X = /* @__PURE__ */ defineComponent({
  __name: "MediaAssetFilterBar",
  props: /* @__PURE__ */ mergeModels({
    searchQuery: {},
    showGenerationTimeSort: { type: Boolean, default: false },
    mediaTypeFilters: {}
  }, {
    "sortBy": { required: true },
    "sortByModifiers": {}
  }),
  emits: /* @__PURE__ */ mergeModels(["update:searchQuery", "update:mediaTypeFilters"], ["update:sortBy"]),
  setup(__props, { emit: __emit }) {
    const emit = __emit;
    const sortBy2 = useModel(__props, "sortBy");
    const handleSearchChange = /* @__PURE__ */ __name((value) => {
      emit("update:searchQuery", value ?? "");
    }, "handleSearchChange");
    const handleMediaTypeFiltersChange = /* @__PURE__ */ __name((value) => {
      emit("update:mediaTypeFilters", value);
    }, "handleMediaTypeFiltersChange");
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", _hoisted_1$1E, [
        createVNode(SearchBox, {
          "model-value": _ctx.searchQuery,
          placeholder: _ctx.$t("sideToolbar.searchAssets") + "...",
          "onUpdate:modelValue": handleSearchChange
        }, null, 8, ["model-value", "placeholder"]),
        unref(isCloud) ? withDirectives((openBlock(), createBlock(_sfc_main$1$, {
          key: 0,
          size: "md"
        }, {
          default: withCtx(({ close }) => [
            createVNode(_sfc_main$1_, {
              "media-type-filters": _ctx.mediaTypeFilters,
              close,
              "onUpdate:mediaTypeFilters": handleMediaTypeFiltersChange
            }, null, 8, ["media-type-filters", "close"])
          ]),
          _: 1
        })), [
          [
            _directive_tooltip,
            { value: _ctx.$t("assetBrowser.filterBy") },
            void 0,
            { top: true }
          ]
        ]) : createCommentVNode("", true),
        unref(isCloud) ? withDirectives((openBlock(), createBlock(_sfc_main$1Z, {
          key: 1,
          size: "md"
        }, {
          default: withCtx(({ close }) => [
            createVNode(_sfc_main$1Y, {
              "sort-by": sortBy2.value,
              "onUpdate:sortBy": _cache[0] || (_cache[0] = ($event) => sortBy2.value = $event),
              "show-generation-time-sort": _ctx.showGenerationTimeSort,
              close
            }, null, 8, ["sort-by", "show-generation-time-sort", "close"])
          ]),
          _: 1
        })), [
          [
            _directive_tooltip,
            { value: _ctx.$t("assetBrowser.sortBy") },
            void 0,
            { top: true }
          ]
        ]) : createCommentVNode("", true)
      ]);
    };
  }
});
function useInternalFilesApi(directory) {
  const assetsStore = useAssetsStore();
  const media = computed(
    () => directory === "input" ? assetsStore.inputAssets : assetsStore.historyAssets
  );
  const loading = computed(
    () => directory === "input" ? assetsStore.inputLoading : assetsStore.historyLoading
  );
  const error = computed(
    () => directory === "input" ? assetsStore.inputError : assetsStore.historyError
  );
  const fetchMediaList = /* @__PURE__ */ __name(async () => {
    if (directory === "input") {
      await assetsStore.updateInputs();
      return assetsStore.inputAssets;
    } else {
      await assetsStore.updateHistory();
      return assetsStore.historyAssets;
    }
  }, "fetchMediaList");
  const refresh = /* @__PURE__ */ __name(() => fetchMediaList(), "refresh");
  const loadMore = /* @__PURE__ */ __name(async () => {
    if (directory === "output") {
      await assetsStore.loadMoreHistory();
    }
  }, "loadMore");
  const hasMore = computed(() => {
    return directory === "output" ? assetsStore.hasMoreHistory : false;
  });
  const isLoadingMore = computed(() => {
    return directory === "output" ? assetsStore.isLoadingMore : false;
  });
  return {
    media,
    loading,
    error,
    fetchMediaList,
    refresh,
    loadMore,
    hasMore,
    isLoadingMore
  };
}
__name(useInternalFilesApi, "useInternalFilesApi");
function useMediaAssets(directory) {
  return useInternalFilesApi(directory);
}
__name(useMediaAssets, "useMediaAssets");
const useAssetSelectionStore = defineStore("assetSelection", () => {
  const selectedAssetIds = ref(/* @__PURE__ */ new Set());
  const lastSelectedIndex = ref(-1);
  const selectedCount = computed(() => selectedAssetIds.value.size);
  const hasSelection = computed(() => selectedAssetIds.value.size > 0);
  const selectedIdsArray = computed(() => Array.from(selectedAssetIds.value));
  function addToSelection(assetId) {
    selectedAssetIds.value.add(assetId);
  }
  __name(addToSelection, "addToSelection");
  function removeFromSelection(assetId) {
    selectedAssetIds.value.delete(assetId);
  }
  __name(removeFromSelection, "removeFromSelection");
  function setSelection(assetIds) {
    const newSet = new Set(assetIds);
    if (newSet.size !== selectedAssetIds.value.size || !assetIds.every((id2) => selectedAssetIds.value.has(id2))) {
      selectedAssetIds.value = newSet;
    }
  }
  __name(setSelection, "setSelection");
  function clearSelection() {
    selectedAssetIds.value.clear();
    lastSelectedIndex.value = -1;
  }
  __name(clearSelection, "clearSelection");
  function toggleSelection(assetId) {
    if (isSelected(assetId)) {
      removeFromSelection(assetId);
    } else {
      addToSelection(assetId);
    }
  }
  __name(toggleSelection, "toggleSelection");
  function isSelected(assetId) {
    return selectedAssetIds.value.has(assetId);
  }
  __name(isSelected, "isSelected");
  function setLastSelectedIndex(index2) {
    lastSelectedIndex.value = index2;
  }
  __name(setLastSelectedIndex, "setLastSelectedIndex");
  function reset() {
    selectedAssetIds.value.clear();
    lastSelectedIndex.value = -1;
  }
  __name(reset, "reset");
  return {
    // State
    selectedAssetIds: computed(() => selectedAssetIds.value),
    lastSelectedIndex: computed(() => lastSelectedIndex.value),
    // Getters
    selectedCount,
    hasSelection,
    selectedIdsArray,
    // Actions
    addToSelection,
    removeFromSelection,
    setSelection,
    clearSelection,
    toggleSelection,
    isSelected,
    setLastSelectedIndex,
    reset
  };
});
function useAssetSelection() {
  const selectionStore = useAssetSelectionStore();
  const isActive = ref(true);
  const shiftKeyRaw = useKeyModifier("Shift");
  const ctrlKeyRaw = useKeyModifier("Control");
  const metaKeyRaw = useKeyModifier("Meta");
  const shiftKey = computed(() => isActive.value && shiftKeyRaw.value);
  const ctrlKey = computed(() => isActive.value && ctrlKeyRaw.value);
  const metaKey = computed(() => isActive.value && metaKeyRaw.value);
  const cmdOrCtrlKey = computed(() => ctrlKey.value || metaKey.value);
  function handleAssetClick(asset, index2, allAssets) {
    if (!asset?.id || index2 < 0 || index2 >= allAssets.length) {
      console.warn("Invalid asset selection parameters");
      return;
    }
    const assetId = asset.id;
    if (shiftKey.value && selectionStore.lastSelectedIndex >= 0) {
      const start = Math.min(selectionStore.lastSelectedIndex, index2);
      const end = Math.max(selectionStore.lastSelectedIndex, index2);
      const rangeIds = allAssets.slice(start, end + 1).map((a) => a.id);
      const existingIds = Array.from(selectionStore.selectedAssetIds);
      const combinedIds = [.../* @__PURE__ */ new Set([...existingIds, ...rangeIds])];
      selectionStore.setSelection(combinedIds);
      return;
    }
    if (cmdOrCtrlKey.value) {
      selectionStore.toggleSelection(assetId);
      selectionStore.setLastSelectedIndex(index2);
      return;
    }
    selectionStore.clearSelection();
    selectionStore.addToSelection(assetId);
    selectionStore.setLastSelectedIndex(index2);
  }
  __name(handleAssetClick, "handleAssetClick");
  function selectAll(allAssets) {
    const allIds = allAssets.map((a) => a.id);
    selectionStore.setSelection(allIds);
    if (allAssets.length > 0) {
      selectionStore.setLastSelectedIndex(allAssets.length - 1);
    }
  }
  __name(selectAll, "selectAll");
  function getSelectedAssets(allAssets) {
    return allAssets.filter((asset) => selectionStore.isSelected(asset.id));
  }
  __name(getSelectedAssets, "getSelectedAssets");
  function activate() {
    isActive.value = true;
  }
  __name(activate, "activate");
  function deactivate() {
    isActive.value = false;
    selectionStore.reset();
  }
  __name(deactivate, "deactivate");
  return {
    // Selection state
    selectedIds: computed(() => selectionStore.selectedAssetIds),
    selectedCount: computed(() => selectionStore.selectedCount),
    hasSelection: computed(() => selectionStore.hasSelection),
    isSelected: /* @__PURE__ */ __name((assetId) => selectionStore.isSelected(assetId), "isSelected"),
    // Selection actions
    handleAssetClick,
    selectAll,
    clearSelection: /* @__PURE__ */ __name(() => selectionStore.clearSelection(), "clearSelection"),
    getSelectedAssets,
    reset: /* @__PURE__ */ __name(() => selectionStore.reset(), "reset"),
    // Lifecycle management
    activate,
    deactivate,
    // Key states (for UI feedback)
    shiftKey,
    cmdOrCtrlKey
  };
}
__name(useAssetSelection, "useAssetSelection");
const getAssetTime = /* @__PURE__ */ __name((asset) => {
  return asset.user_metadata?.create_time ?? (asset.created_at ? new Date(asset.created_at).getTime() : 0);
}, "getAssetTime");
const getAssetExecutionTime = /* @__PURE__ */ __name((asset) => {
  return asset.user_metadata?.executionTimeInSeconds ?? 0;
}, "getAssetExecutionTime");
function useMediaAssetFiltering(assets) {
  const searchQuery = ref("");
  const debouncedSearchQuery = refDebounced(searchQuery, 50);
  const sortBy$1 = ref("newest");
  const mediaTypeFilters = ref([]);
  const fuseOptions = {
    keys: ["name"],
    threshold: 0.4,
    includeScore: true
  };
  const fuse = computed(() => new Fuse(assets.value, fuseOptions));
  const searchFiltered = computed(() => {
    if (!debouncedSearchQuery.value.trim()) {
      return assets.value;
    }
    const results = fuse.value.search(debouncedSearchQuery.value);
    return results.map((result) => result.item);
  });
  const typeFiltered = computed(() => {
    if (mediaTypeFilters.value.length === 0) {
      return searchFiltered.value;
    }
    return searchFiltered.value.filter((asset) => {
      const mediaType = getMediaTypeFromFilename(asset.name);
      const normalizedType = mediaType.toLowerCase();
      return mediaTypeFilters.value.includes(normalizedType);
    });
  });
  const filteredAssets = computed(() => {
    switch (sortBy$1.value) {
      case "oldest":
        return sortBy(typeFiltered.value, [getAssetTime]);
      case "longest":
        return sortBy(typeFiltered.value, [
          (asset) => -getAssetExecutionTime(asset)
        ]);
      case "fastest":
        return sortBy(typeFiltered.value, [getAssetExecutionTime]);
      case "newest":
      default:
        return sortBy(typeFiltered.value, [(asset) => -getAssetTime(asset)]);
    }
  });
  return {
    searchQuery,
    sortBy: sortBy$1,
    mediaTypeFilters,
    filteredAssets
  };
}
__name(useMediaAssetFiltering, "useMediaAssetFiltering");
const _hoisted_1$1D = {
  key: 0,
  class: "flex w-full items-center justify-between gap-2"
};
const _hoisted_2$1h = { class: "flex items-center gap-2" };
const _hoisted_3$T = { class: "font-bold" };
const _hoisted_4$G = { class: "text-sm" };
const _hoisted_5$A = {
  key: 0,
  class: "px-2 2xl:px-4"
};
const _hoisted_6$r = { key: 0 };
const _hoisted_7$o = { key: 1 };
const _hoisted_8$i = { class: "flex-1 pl-4" };
const _hoisted_9$e = { class: "flex shrink gap-2 pr-4 items-center-safe justify-end-safe" };
const COMPACT_MODE_THRESHOLD_PX = 430;
const _sfc_main$1W = /* @__PURE__ */ defineComponent({
  __name: "AssetsSidebarTab",
  setup(__props) {
    const { t: t2 } = useI18n();
    const activeTab = ref("output");
    const folderPromptId = ref(null);
    const folderExecutionTime = ref(void 0);
    const isInFolderView = computed(() => folderPromptId.value !== null);
    const openContextMenuId = ref(null);
    const shouldShowDeleteButton = computed(() => {
      if (activeTab.value === "input" && !isCloud) return false;
      return true;
    });
    const getOutputCount = /* @__PURE__ */ __name((item) => {
      const count = item.user_metadata?.outputCount;
      return typeof count === "number" && count > 0 ? count : 1;
    }, "getOutputCount");
    const shouldShowOutputCount = /* @__PURE__ */ __name((item) => {
      if (activeTab.value !== "output" || isInFolderView.value) {
        return false;
      }
      return getOutputCount(item) > 1;
    }, "shouldShowOutputCount");
    const formattedExecutionTime = computed(() => {
      if (!folderExecutionTime.value) return "";
      return formatDuration(folderExecutionTime.value * 1e3);
    });
    const toast = useToast();
    const inputAssets = useMediaAssets("input");
    const outputAssets = useMediaAssets("output");
    const {
      isSelected,
      handleAssetClick,
      hasSelection,
      clearSelection,
      getSelectedAssets,
      activate: activateSelection,
      deactivate: deactivateSelection
    } = useAssetSelection();
    const { downloadMultipleAssets, deleteMultipleAssets } = useMediaAssetActions();
    const footerRef = ref(null);
    const footerWidth = ref(0);
    useResizeObserver(footerRef, (entries) => {
      const entry = entries[0];
      footerWidth.value = entry.contentRect.width;
    });
    const isCompact = computed(
      () => footerWidth.value > 0 && footerWidth.value <= COMPACT_MODE_THRESHOLD_PX
    );
    const selectionCountButtonRef = ref(null);
    const isHoveringSelectionCount = useElementHover(selectionCountButtonRef);
    const totalOutputCount = computed(() => {
      const selectedAssets = getSelectedAssets(displayAssets.value);
      return selectedAssets.reduce((sum, asset) => sum + getOutputCount(asset), 0);
    });
    const currentAssets = computed(
      () => activeTab.value === "input" ? inputAssets : outputAssets
    );
    const loading = computed(() => currentAssets.value.loading.value);
    const error = computed(() => currentAssets.value.error.value);
    const mediaAssets = computed(() => currentAssets.value.media.value);
    const galleryActiveIndex = ref(-1);
    const currentGalleryAssetId = ref(null);
    const folderAssets = ref([]);
    const baseAssets = computed(() => {
      if (isInFolderView.value) {
        return folderAssets.value;
      }
      return mediaAssets.value;
    });
    const { searchQuery, sortBy: sortBy2, mediaTypeFilters, filteredAssets } = useMediaAssetFiltering(baseAssets);
    const displayAssets = computed(() => {
      return filteredAssets.value;
    });
    watch(displayAssets, (newAssets) => {
      if (currentGalleryAssetId.value && galleryActiveIndex.value !== -1) {
        const newIndex = newAssets.findIndex(
          (asset) => asset.id === currentGalleryAssetId.value
        );
        if (newIndex !== -1) {
          galleryActiveIndex.value = newIndex;
        }
      }
    });
    watch(galleryActiveIndex, (index2) => {
      if (index2 === -1) {
        currentGalleryAssetId.value = null;
      }
    });
    const galleryItems = computed(() => {
      return displayAssets.value.map((asset) => {
        const mediaType = getMediaTypeFromFilename(asset.name);
        const resultItem = new ResultItemImpl({
          filename: asset.name,
          subfolder: "",
          type: "output",
          nodeId: "0",
          mediaType: mediaType === "image" ? "images" : mediaType
        });
        Object.defineProperty(resultItem, "url", {
          get() {
            return asset.preview_url || "";
          },
          configurable: true
        });
        return resultItem;
      });
    });
    const mediaAssetsWithKey = computed(() => {
      return displayAssets.value.map((asset) => ({
        ...asset,
        key: asset.id
      }));
    });
    const refreshAssets = /* @__PURE__ */ __name(async () => {
      await currentAssets.value.fetchMediaList();
      if (error.value) {
        console.error("Failed to refresh assets:", error.value);
      }
    }, "refreshAssets");
    watch(
      activeTab,
      () => {
        clearSelection();
        searchQuery.value = "";
        void refreshAssets();
      },
      { immediate: true }
    );
    const handleAssetSelect = /* @__PURE__ */ __name((asset) => {
      const index2 = displayAssets.value.findIndex((a) => a.id === asset.id);
      handleAssetClick(asset, index2, displayAssets.value);
    }, "handleAssetSelect");
    const handleZoomClick = /* @__PURE__ */ __name((asset) => {
      const mediaType = getMediaTypeFromFilename(asset.name);
      if (mediaType === "3D") {
        const dialogStore = useDialogStore();
        dialogStore.showDialog({
          key: "asset-3d-viewer",
          title: asset.name,
          component: Load3DViewerContent,
          props: {
            modelUrl: asset.preview_url || ""
          },
          dialogComponentProps: {
            style: "width: 80vw; height: 80vh;",
            maximizable: true
          }
        });
        return;
      }
      currentGalleryAssetId.value = asset.id;
      const index2 = displayAssets.value.findIndex((a) => a.id === asset.id);
      if (index2 !== -1) {
        galleryActiveIndex.value = index2;
      }
    }, "handleZoomClick");
    const enterFolderView = /* @__PURE__ */ __name((asset) => {
      const metadata = getOutputAssetMetadata(asset.user_metadata);
      if (!metadata) {
        console.warn("Invalid output asset metadata");
        return;
      }
      const { promptId, allOutputs, executionTimeInSeconds } = metadata;
      if (!promptId || !Array.isArray(allOutputs) || allOutputs.length === 0) {
        console.warn("Missing required folder view data");
        return;
      }
      folderPromptId.value = promptId;
      folderExecutionTime.value = executionTimeInSeconds;
      folderAssets.value = allOutputs.map((output) => ({
        id: `${output.nodeId}-${output.filename}`,
        name: output.filename,
        size: 0,
        created_at: asset.created_at,
        tags: ["output"],
        preview_url: output.url,
        user_metadata: {
          promptId,
          nodeId: output.nodeId,
          subfolder: output.subfolder,
          executionTimeInSeconds,
          workflow: metadata.workflow
        }
      }));
    }, "enterFolderView");
    const exitFolderView = /* @__PURE__ */ __name(() => {
      folderPromptId.value = null;
      folderExecutionTime.value = void 0;
      folderAssets.value = [];
      searchQuery.value = "";
    }, "exitFolderView");
    onMounted(() => {
      activateSelection();
    });
    onUnmounted(() => {
      deactivateSelection();
    });
    const handleDeselectAll = /* @__PURE__ */ __name(() => {
      clearSelection();
    }, "handleDeselectAll");
    const handleEmptySpaceClick = /* @__PURE__ */ __name(() => {
      if (hasSelection) {
        clearSelection();
      }
    }, "handleEmptySpaceClick");
    const copyJobId = /* @__PURE__ */ __name(async () => {
      if (folderPromptId.value) {
        try {
          await navigator.clipboard.writeText(folderPromptId.value);
          toast.add({
            severity: "success",
            summary: t2("mediaAsset.jobIdToast.copied"),
            detail: t2("mediaAsset.jobIdToast.jobIdCopied"),
            life: 2e3
          });
        } catch (error2) {
          toast.add({
            severity: "error",
            summary: t2("mediaAsset.jobIdToast.error"),
            detail: t2("mediaAsset.jobIdToast.jobIdCopyFailed"),
            life: 3e3
          });
        }
      }
    }, "copyJobId");
    const handleDownloadSelected = /* @__PURE__ */ __name(() => {
      const selectedAssets = getSelectedAssets(displayAssets.value);
      downloadMultipleAssets(selectedAssets);
      clearSelection();
    }, "handleDownloadSelected");
    const handleDeleteSelected = /* @__PURE__ */ __name(async () => {
      const selectedAssets = getSelectedAssets(displayAssets.value);
      await deleteMultipleAssets(selectedAssets);
      clearSelection();
    }, "handleDeleteSelected");
    const handleApproachEnd = useDebounceFn(async () => {
      if (activeTab.value === "output" && !isInFolderView.value && outputAssets.hasMore.value && !outputAssets.isLoadingMore.value) {
        await outputAssets.loadMore();
      }
    }, 300);
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock(Fragment, null, [
        createVNode(SidebarTabTemplate, {
          title: isInFolderView.value ? "" : _ctx.$t("sideToolbar.mediaAssets.title")
        }, {
          "alt-title": withCtx(() => [
            isInFolderView.value ? (openBlock(), createElementBlock("div", _hoisted_1$1D, [
              createBaseVNode("div", _hoisted_2$1h, [
                createBaseVNode("span", _hoisted_3$T, toDisplayString(_ctx.$t("assetBrowser.jobId")) + ":", 1),
                createBaseVNode("span", _hoisted_4$G, toDisplayString(folderPromptId.value?.substring(0, 8)), 1),
                createBaseVNode("button", {
                  class: "m-0 cursor-pointer border-0 bg-transparent p-0 outline-0",
                  role: "button",
                  onClick: copyJobId
                }, _cache[5] || (_cache[5] = [
                  createBaseVNode("i", { class: "icon-[lucide--copy] text-sm" }, null, -1)
                ]))
              ]),
              createBaseVNode("div", null, [
                createBaseVNode("span", null, toDisplayString(formattedExecutionTime.value), 1)
              ])
            ])) : createCommentVNode("", true)
          ]),
          "tool-buttons": withCtx(() => [
            !isInFolderView.value ? (openBlock(), createBlock(_sfc_main$27, {
              key: 0,
              modelValue: activeTab.value,
              "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => activeTab.value = $event)
            }, {
              default: withCtx(() => [
                createVNode(_sfc_main$28, {
                  class: "font-inter",
                  value: "output"
                }, {
                  default: withCtx(() => [
                    createTextVNode(toDisplayString(_ctx.$t("sideToolbar.labels.generated")), 1)
                  ]),
                  _: 1
                }),
                createVNode(_sfc_main$28, {
                  class: "font-inter",
                  value: "input"
                }, {
                  default: withCtx(() => [
                    createTextVNode(toDisplayString(_ctx.$t("sideToolbar.labels.imported")), 1)
                  ]),
                  _: 1
                })
              ]),
              _: 1
            }, 8, ["modelValue"])) : createCommentVNode("", true)
          ]),
          header: withCtx(() => [
            isInFolderView.value ? (openBlock(), createElementBlock("div", _hoisted_5$A, [
              createVNode(_sfc_main$2A, {
                variant: "secondary",
                size: "lg",
                onClick: exitFolderView
              }, {
                default: withCtx(() => [
                  _cache[6] || (_cache[6] = createBaseVNode("i", { class: "icon-[lucide--arrow-left] size-4" }, null, -1)),
                  createBaseVNode("span", null, toDisplayString(_ctx.$t("sideToolbar.backToAssets")), 1)
                ]),
                _: 1
              })
            ])) : createCommentVNode("", true),
            createVNode(_sfc_main$1X, {
              "search-query": unref(searchQuery),
              "onUpdate:searchQuery": _cache[1] || (_cache[1] = ($event) => isRef(searchQuery) ? searchQuery.value = $event : null),
              "sort-by": unref(sortBy2),
              "onUpdate:sortBy": _cache[2] || (_cache[2] = ($event) => isRef(sortBy2) ? sortBy2.value = $event : null),
              "media-type-filters": unref(mediaTypeFilters),
              "onUpdate:mediaTypeFilters": _cache[3] || (_cache[3] = ($event) => isRef(mediaTypeFilters) ? mediaTypeFilters.value = $event : null),
              class: "pb-1 px-2 2xl:px-4",
              "show-generation-time-sort": activeTab.value === "output"
            }, null, 8, ["search-query", "sort-by", "media-type-filters", "show-generation-time-sort"]),
            createVNode(unref(script$h), {
              type: "dashed",
              class: "my-2"
            })
          ]),
          body: withCtx(() => [
            loading.value && !displayAssets.value.length ? (openBlock(), createElementBlock("div", _hoisted_6$r, [
              createVNode(unref(script$7), { class: "absolute left-1/2 w-[50px] -translate-x-1/2" })
            ])) : !loading.value && !displayAssets.value.length ? (openBlock(), createElementBlock("div", _hoisted_7$o, [
              createVNode(NoResultsPlaceholder, {
                icon: "pi pi-info-circle",
                title: _ctx.$t(
                  activeTab.value === "input" ? "sideToolbar.noImportedFiles" : "sideToolbar.noGeneratedFiles"
                ),
                message: _ctx.$t("sideToolbar.noFilesFoundMessage")
              }, null, 8, ["title", "message"])
            ])) : (openBlock(), createElementBlock("div", {
              key: 2,
              class: "relative size-full",
              onClick: handleEmptySpaceClick
            }, [
              createVNode(VirtualGrid, {
                items: mediaAssetsWithKey.value,
                "grid-style": {
                  display: "grid",
                  gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
                  padding: "0 0.5rem",
                  gap: "0.5rem"
                },
                onApproachEnd: unref(handleApproachEnd)
              }, {
                item: withCtx(({ item }) => [
                  createVNode(_sfc_main$20, {
                    asset: item,
                    selected: unref(isSelected)(item.id),
                    "show-output-count": shouldShowOutputCount(item),
                    "output-count": getOutputCount(item),
                    "show-delete-button": shouldShowDeleteButton.value,
                    "open-context-menu-id": openContextMenuId.value,
                    onClick: /* @__PURE__ */ __name(($event) => handleAssetSelect(item), "onClick"),
                    onZoom: /* @__PURE__ */ __name(($event) => handleZoomClick(item), "onZoom"),
                    onOutputCountClick: /* @__PURE__ */ __name(($event) => enterFolderView(item), "onOutputCountClick"),
                    onAssetDeleted: refreshAssets,
                    onContextMenuOpened: /* @__PURE__ */ __name(($event) => openContextMenuId.value = item.id, "onContextMenuOpened")
                  }, null, 8, ["asset", "selected", "show-output-count", "output-count", "show-delete-button", "open-context-menu-id", "onClick", "onZoom", "onOutputCountClick", "onContextMenuOpened"])
                ]),
                _: 1
              }, 8, ["items", "onApproachEnd"])
            ]))
          ]),
          footer: withCtx(() => [
            unref(hasSelection) ? (openBlock(), createElementBlock("div", {
              key: 0,
              ref_key: "footerRef",
              ref: footerRef,
              class: "flex gap-1 h-18 w-full items-center justify-between"
            }, [
              createBaseVNode("div", _hoisted_8$i, [
                createBaseVNode("div", {
                  ref_key: "selectionCountButtonRef",
                  ref: selectionCountButtonRef,
                  class: "inline-flex w-48"
                }, [
                  createVNode(_sfc_main$2A, {
                    variant: "secondary",
                    size: "lg",
                    class: normalizeClass(unref(cn)(isCompact.value && "text-left")),
                    onClick: handleDeselectAll
                  }, {
                    default: withCtx(() => [
                      createTextVNode(toDisplayString(unref(isHoveringSelectionCount) ? _ctx.$t("mediaAsset.selection.deselectAll") : _ctx.$t("mediaAsset.selection.selectedCount", {
                        count: totalOutputCount.value
                      })), 1)
                    ]),
                    _: 1
                  }, 8, ["class"])
                ], 512)
              ]),
              createBaseVNode("div", _hoisted_9$e, [
                isCompact.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
                  shouldShowDeleteButton.value ? (openBlock(), createBlock(_sfc_main$2A, {
                    key: 0,
                    size: "icon",
                    onClick: handleDeleteSelected
                  }, {
                    default: withCtx(() => _cache[7] || (_cache[7] = [
                      createBaseVNode("i", { class: "icon-[lucide--trash-2] size-4" }, null, -1)
                    ])),
                    _: 1
                  })) : createCommentVNode("", true),
                  createVNode(_sfc_main$2A, {
                    size: "icon",
                    onClick: handleDownloadSelected
                  }, {
                    default: withCtx(() => _cache[8] || (_cache[8] = [
                      createBaseVNode("i", { class: "icon-[lucide--download] size-4" }, null, -1)
                    ])),
                    _: 1
                  })
                ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
                  shouldShowDeleteButton.value ? (openBlock(), createBlock(_sfc_main$2A, {
                    key: 0,
                    variant: "secondary",
                    onClick: handleDeleteSelected
                  }, {
                    default: withCtx(() => [
                      createBaseVNode("span", null, toDisplayString(_ctx.$t("mediaAsset.selection.deleteSelected")), 1),
                      _cache[9] || (_cache[9] = createBaseVNode("i", { class: "icon-[lucide--trash-2] size-4" }, null, -1))
                    ]),
                    _: 1
                  })) : createCommentVNode("", true),
                  createVNode(_sfc_main$2A, {
                    variant: "secondary",
                    onClick: handleDownloadSelected
                  }, {
                    default: withCtx(() => [
                      createBaseVNode("span", null, toDisplayString(_ctx.$t("mediaAsset.selection.downloadSelected")), 1),
                      _cache[10] || (_cache[10] = createBaseVNode("i", { class: "icon-[lucide--download] size-4" }, null, -1))
                    ]),
                    _: 1
                  })
                ], 64))
              ])
            ], 512)) : createCommentVNode("", true)
          ]),
          _: 1
        }, 8, ["title"]),
        createVNode(_sfc_main$29, {
          "active-index": galleryActiveIndex.value,
          "onUpdate:activeIndex": _cache[4] || (_cache[4] = ($event) => galleryActiveIndex.value = $event),
          "all-gallery-items": galleryItems.value
        }, null, 8, ["active-index", "all-gallery-items"])
      ], 64);
    };
  }
});
const useAssetsSidebarTab = /* @__PURE__ */ __name(() => {
  return {
    id: "assets",
    icon: "icon-[comfy--image-ai-edit]",
    title: "sideToolbar.assets",
    tooltip: "sideToolbar.assets",
    label: "sideToolbar.labels.assets",
    component: markRaw(_sfc_main$1W),
    type: "vue"
  };
}, "useAssetsSidebarTab");
function usePragmaticDroppable(dropTargetElement, options) {
  let cleanup = /* @__PURE__ */ __name(() => {
  }, "cleanup");
  onMounted(() => {
    const element = toValue(dropTargetElement);
    if (!element) {
      return;
    }
    cleanup = dropTargetForElements({
      element,
      ...options
    });
  });
  onBeforeUnmount(() => {
    cleanup();
  });
}
__name(usePragmaticDroppable, "usePragmaticDroppable");
function usePragmaticDraggable(draggableElement, options) {
  let cleanup = /* @__PURE__ */ __name(() => {
  }, "cleanup");
  onMounted(() => {
    const element = toValue(draggableElement);
    if (!element) {
      return;
    }
    cleanup = draggable({
      element,
      ...options
    });
  });
  onBeforeUnmount(() => {
    cleanup();
  });
}
__name(usePragmaticDraggable, "usePragmaticDraggable");
const InjectKeyHandleEditLabelFunction = /* @__PURE__ */ Symbol();
const InjectKeyExpandedKeys = /* @__PURE__ */ Symbol();
const _hoisted_1$1C = { class: "node-content" };
const _hoisted_2$1g = { class: "node-label" };
const _hoisted_3$S = { class: "node-actions motion-safe:opacity-0 motion-safe:group-hover/tree-node:opacity-100" };
const _sfc_main$1V = /* @__PURE__ */ defineComponent({
  __name: "TreeExplorerTreeNode",
  props: {
    node: {}
  },
  emits: ["itemDropped", "dragStart", "dragEnd"],
  setup(__props, { emit: __emit }) {
    const props = __props;
    const emit = __emit;
    const nodeBadgeText = computed(() => {
      if (props.node.leaf) {
        return "";
      }
      if (props.node.badgeText !== void 0 && props.node.badgeText !== null) {
        return props.node.badgeText;
      }
      return props.node.totalLeaves.toString();
    });
    const showNodeBadgeText = computed(() => nodeBadgeText.value !== "");
    const isEditing = computed(() => props.node.isEditingLabel ?? false);
    const handleEditLabel = inject(InjectKeyHandleEditLabelFunction);
    const handleRename = /* @__PURE__ */ __name((newName) => {
      handleEditLabel?.(props.node, newName);
    }, "handleRename");
    const container = ref(null);
    const canDrop = ref(false);
    const treeNodeElementGetter = /* @__PURE__ */ __name(() => container.value?.closest(".p-tree-node-content"), "treeNodeElementGetter");
    if (props.node.draggable) {
      usePragmaticDraggable(treeNodeElementGetter, {
        getInitialData: /* @__PURE__ */ __name(() => {
          return {
            type: "tree-explorer-node",
            data: props.node
          };
        }, "getInitialData"),
        onDragStart: /* @__PURE__ */ __name(() => emit("dragStart", props.node), "onDragStart"),
        onDrop: /* @__PURE__ */ __name(() => emit("dragEnd", props.node), "onDrop"),
        onGenerateDragPreview: props.node.renderDragPreview ? ({ nativeSetDragImage }) => {
          setCustomNativeDragPreview({
            render: /* @__PURE__ */ __name(({ container: container2 }) => {
              return props.node.renderDragPreview?.(container2);
            }, "render"),
            nativeSetDragImage
          });
        } : void 0
      });
    }
    if (props.node.droppable) {
      usePragmaticDroppable(treeNodeElementGetter, {
        onDrop: /* @__PURE__ */ __name(async (event) => {
          const dndData = event.source.data;
          if (dndData.type === "tree-explorer-node") {
            await props.node.handleDrop?.(dndData);
            canDrop.value = false;
            emit("itemDropped", props.node, dndData.data);
          }
        }, "onDrop"),
        onDragEnter: /* @__PURE__ */ __name((event) => {
          const dndData = event.source.data;
          if (dndData.type === "tree-explorer-node") {
            canDrop.value = true;
          }
        }, "onDragEnter"),
        onDragLeave: /* @__PURE__ */ __name(() => {
          canDrop.value = false;
        }, "onDragLeave")
      });
    }
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        ref_key: "container",
        ref: container,
        class: normalizeClass([
          "tree-node",
          {
            "can-drop": canDrop.value,
            "tree-folder": !props.node.leaf,
            "tree-leaf": props.node.leaf
          }
        ])
      }, [
        createBaseVNode("div", _hoisted_1$1C, [
          createBaseVNode("span", _hoisted_2$1g, [
            renderSlot(_ctx.$slots, "before-label", {
              node: props.node
            }, void 0, true),
            createVNode(EditableText, {
              "model-value": _ctx.node.label,
              "is-editing": isEditing.value,
              onEdit: handleRename
            }, null, 8, ["model-value", "is-editing"]),
            renderSlot(_ctx.$slots, "after-label", {
              node: props.node
            }, void 0, true)
          ]),
          showNodeBadgeText.value ? (openBlock(), createBlock(unref(script$4), {
            key: 0,
            value: nodeBadgeText.value,
            severity: "secondary",
            class: "leaf-count-badge"
          }, null, 8, ["value"])) : createCommentVNode("", true)
        ]),
        createBaseVNode("div", _hoisted_3$S, [
          renderSlot(_ctx.$slots, "actions", {
            node: props.node
          }, void 0, true)
        ])
      ], 2);
    };
  }
});
const TreeExplorerTreeNode = /* @__PURE__ */ _export_sfc(_sfc_main$1V, [["__scopeId", "data-v-b10ac6ce"]]);
function useTreeFolderOperations(expandNode) {
  const { t: t2 } = useI18n();
  const newFolderNode = ref(null);
  const addFolderTargetNode = ref(null);
  const generateTempKey = /* @__PURE__ */ __name((parentKey) => {
    return `${parentKey}/new_folder_${Date.now()}`;
  }, "generateTempKey");
  const handleFolderCreation = /* @__PURE__ */ __name(async (newName) => {
    if (!newFolderNode.value || !addFolderTargetNode.value) return;
    try {
      await addFolderTargetNode.value?.handleAddFolder?.(newName);
    } finally {
      newFolderNode.value = null;
      addFolderTargetNode.value = null;
    }
  }, "handleFolderCreation");
  const addFolderCommand = /* @__PURE__ */ __name((targetNode) => {
    expandNode(targetNode);
    newFolderNode.value = {
      key: generateTempKey(targetNode.key),
      label: "",
      leaf: false,
      children: [],
      icon: "pi pi-folder",
      type: "folder",
      totalLeaves: 0,
      badgeText: "",
      isEditingLabel: true
    };
    addFolderTargetNode.value = targetNode;
  }, "addFolderCommand");
  const getAddFolderMenuItem = /* @__PURE__ */ __name((targetNode) => {
    return {
      label: t2("g.newFolder"),
      icon: "pi pi-folder-plus",
      command: /* @__PURE__ */ __name(() => {
        if (targetNode) addFolderCommand(targetNode);
      }, "command"),
      visible: !!targetNode && !targetNode.leaf && !!targetNode.handleAddFolder,
      isAsync: false
    };
  }, "getAddFolderMenuItem");
  return {
    newFolderNode,
    addFolderCommand,
    getAddFolderMenuItem,
    handleFolderCreation
  };
}
__name(useTreeFolderOperations, "useTreeFolderOperations");
function buildTree(items, key) {
  const root = {
    key: "root",
    label: "root",
    children: []
  };
  const map2 = {
    root
  };
  for (const item of items) {
    const keys = key(item);
    let parent = root;
    for (let i = 0; i < keys.length; i++) {
      const k = keys[i];
      if (k === "" && i === keys.length - 1) break;
      const id2 = parent.key + "/" + k;
      if (!map2[id2]) {
        const node2 = {
          key: id2,
          label: k,
          leaf: false,
          children: []
        };
        map2[id2] = node2;
        parent.children?.push(node2);
      }
      parent = map2[id2];
    }
    parent.leaf = keys[keys.length - 1] !== "";
    parent.data = item;
  }
  return root;
}
__name(buildTree, "buildTree");
function flattenTree(tree) {
  const result = [];
  const stack = [tree];
  while (stack.length) {
    const node2 = stack.pop();
    if (node2.leaf && node2.data) result.push(node2.data);
    stack.push(...node2.children ?? []);
  }
  return result;
}
__name(flattenTree, "flattenTree");
function sortedTree(node2, {
  groupLeaf = false
} = {}) {
  const newNode = {
    ...node2
  };
  if (node2.children) {
    if (groupLeaf) {
      const folders = node2.children.filter((child) => !child.leaf);
      const files = node2.children.filter((child) => child.leaf);
      const sortedFolders = folders.sort(
        (a, b) => (a.label ?? "").localeCompare(b.label ?? "")
      );
      const sortedFiles = files.sort(
        (a, b) => (a.label ?? "").localeCompare(b.label ?? "")
      );
      newNode.children = [
        ...sortedFolders.map(
          (folder) => sortedTree(folder, { groupLeaf: true })
        ),
        ...sortedFiles
      ];
    } else {
      const sortedChildren = [...node2.children].sort(
        (a, b) => (a.label ?? "").localeCompare(b.label ?? "")
      );
      newNode.children = [
        ...sortedChildren.map(
          (child) => sortedTree(child, { groupLeaf: false })
        )
      ];
    }
  }
  return newNode;
}
__name(sortedTree, "sortedTree");
const findNodeByKey = /* @__PURE__ */ __name((root, key) => {
  if (root.key === key) {
    return root;
  }
  if (!root.children) {
    return null;
  }
  for (const child of root.children) {
    const result = findNodeByKey(child, key);
    if (result) {
      return result;
    }
  }
  return null;
}, "findNodeByKey");
function cloneTree(node2) {
  const clone2 = { ...node2 };
  if (node2.children && node2.children.length > 0) {
    clone2.children = node2.children.map((child) => cloneTree(child));
  }
  return clone2;
}
__name(cloneTree, "cloneTree");
const combineTrees = /* @__PURE__ */ __name((root, subtree) => {
  const newRoot = cloneTree(root);
  const parentKey = subtree.key.slice(0, subtree.key.lastIndexOf("/"));
  const parent = findNodeByKey(newRoot, parentKey);
  if (parent) {
    parent.children ??= [];
    parent.children.push(cloneTree(subtree));
  }
  return newRoot;
}, "combineTrees");
const _sfc_main$1U = /* @__PURE__ */ defineComponent({
  __name: "TreeExplorer",
  props: /* @__PURE__ */ mergeModels({
    root: {},
    class: {}
  }, {
    "expandedKeys": {
      required: true
    },
    "expandedKeysModifiers": {},
    "selectionKeys": {},
    "selectionKeysModifiers": {}
  }),
  emits: /* @__PURE__ */ mergeModels(["nodeClick", "nodeDelete", "contextMenu"], ["update:expandedKeys", "update:selectionKeys"]),
  setup(__props, { expose: __expose, emit: __emit }) {
    const expandedKeys = useModel(__props, "expandedKeys");
    provide(InjectKeyExpandedKeys, expandedKeys);
    const selectionKeys = useModel(__props, "selectionKeys");
    const storeSelectionKeys = selectionKeys.value !== void 0;
    const props = __props;
    const emit = __emit;
    const {
      newFolderNode,
      getAddFolderMenuItem,
      handleFolderCreation,
      addFolderCommand
    } = useTreeFolderOperations(
      /* expandNode */
      (node2) => {
        expandedKeys.value[node2.key] = true;
      }
    );
    const renderedRoot = computed(() => {
      const renderedRoot2 = fillNodeInfo(props.root);
      return newFolderNode.value ? combineTrees(renderedRoot2, newFolderNode.value) : renderedRoot2;
    });
    const getTreeNodeIcon = /* @__PURE__ */ __name((node2) => {
      if (node2.getIcon) {
        const icon2 = node2.getIcon();
        if (icon2) {
          return icon2;
        }
      } else if (node2.icon) {
        return node2.icon;
      }
      if (node2.leaf) {
        return "pi pi-file";
      }
      const isExpanded = expandedKeys.value?.[node2.key] ?? false;
      return isExpanded ? "pi pi-folder-open" : "pi pi-folder";
    }, "getTreeNodeIcon");
    const fillNodeInfo = /* @__PURE__ */ __name((node2) => {
      const children = node2.children?.map(fillNodeInfo) ?? [];
      const totalLeaves = node2.leaf ? 1 : children.reduce((acc, child) => acc + child.totalLeaves, 0);
      return {
        ...node2,
        icon: getTreeNodeIcon(node2),
        children,
        type: node2.leaf ? "node" : "folder",
        totalLeaves,
        badgeText: node2.getBadgeText ? node2.getBadgeText() : void 0,
        isEditingLabel: node2.key === renameEditingNode.value?.key
      };
    }, "fillNodeInfo");
    const onNodeContentClick = /* @__PURE__ */ __name(async (e2, node2) => {
      if (!storeSelectionKeys) {
        selectionKeys.value = {};
      }
      if (node2.handleClick) {
        await node2.handleClick(e2);
      }
      emit("nodeClick", node2, e2);
    }, "onNodeContentClick");
    const menu2 = ref(null);
    const menuTargetNode = ref(null);
    const extraMenuItems = computed(() => {
      return menuTargetNode.value?.contextMenuItems ? typeof menuTargetNode.value.contextMenuItems === "function" ? menuTargetNode.value.contextMenuItems(menuTargetNode.value) : menuTargetNode.value.contextMenuItems : [];
    });
    const renameEditingNode = ref(null);
    const errorHandling = useErrorHandling();
    const handleNodeLabelEdit = /* @__PURE__ */ __name(async (node2, newName) => {
      await errorHandling.wrapWithErrorHandlingAsync(
        async () => {
          if (node2.key === newFolderNode.value?.key) {
            await handleFolderCreation(newName);
          } else {
            await node2.handleRename?.(newName);
          }
        },
        node2.handleError,
        () => {
          renameEditingNode.value = null;
        }
      )();
    }, "handleNodeLabelEdit");
    provide(InjectKeyHandleEditLabelFunction, handleNodeLabelEdit);
    const { t: t2 } = useI18n();
    const renameCommand = /* @__PURE__ */ __name((node2) => {
      renameEditingNode.value = node2;
    }, "renameCommand");
    const deleteCommand = /* @__PURE__ */ __name(async (node2) => {
      await node2.handleDelete?.();
      emit("nodeDelete", node2);
    }, "deleteCommand");
    const menuItems = computed(
      () => [
        getAddFolderMenuItem(menuTargetNode.value),
        {
          label: t2("g.rename"),
          icon: "pi pi-file-edit",
          command: /* @__PURE__ */ __name(() => {
            if (menuTargetNode.value) {
              renameCommand(menuTargetNode.value);
            }
          }, "command"),
          visible: menuTargetNode.value?.handleRename !== void 0
        },
        {
          label: t2("g.delete"),
          icon: "pi pi-trash",
          command: /* @__PURE__ */ __name(async () => {
            if (menuTargetNode.value) {
              await deleteCommand(menuTargetNode.value);
            }
          }, "command"),
          visible: menuTargetNode.value?.handleDelete !== void 0,
          isAsync: true
          // The delete command can be async
        },
        ...extraMenuItems.value
      ].map((menuItem) => ({
        ...menuItem,
        command: menuItem.command ? wrapCommandWithErrorHandler(menuItem.command, {
          isAsync: menuItem.isAsync ?? false
        }) : void 0
      }))
    );
    const handleContextMenu = /* @__PURE__ */ __name((e2, node2) => {
      menuTargetNode.value = node2;
      emit("contextMenu", node2, e2);
      if (menuItems.value.filter((item) => item.visible).length > 0) {
        menu2.value?.show(e2);
      }
    }, "handleContextMenu");
    const wrapCommandWithErrorHandler = /* @__PURE__ */ __name((command, { isAsync = false }) => {
      return isAsync ? errorHandling.wrapWithErrorHandlingAsync(
        command,
        menuTargetNode.value?.handleError
      ) : errorHandling.wrapWithErrorHandling(
        command,
        menuTargetNode.value?.handleError
      );
    }, "wrapCommandWithErrorHandler");
    __expose({
      /**
       * The command to add a folder to a node via the context menu
       * @param targetNodeKey - The key of the node where the folder will be added under
       */
      addFolderCommand: /* @__PURE__ */ __name((targetNodeKey) => {
        const targetNode = findNodeByKey(renderedRoot.value, targetNodeKey);
        if (targetNode) {
          addFolderCommand(targetNode);
        }
      }, "addFolderCommand")
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock(Fragment, null, [
        createVNode(unref(script$i), {
          "expanded-keys": expandedKeys.value,
          "onUpdate:expandedKeys": _cache[0] || (_cache[0] = ($event) => expandedKeys.value = $event),
          "selection-keys": selectionKeys.value,
          "onUpdate:selectionKeys": _cache[1] || (_cache[1] = ($event) => selectionKeys.value = $event),
          class: normalizeClass(["tree-explorer px-2 py-0 2xl:px-4 bg-transparent", props.class]),
          value: renderedRoot.value.children,
          "selection-mode": "single",
          pt: {
            nodeLabel: "tree-explorer-node-label",
            nodeContent: /* @__PURE__ */ __name(({ context }) => ({
              class: "group/tree-node",
              onClick: /* @__PURE__ */ __name((e2) => onNodeContentClick(e2, context.node), "onClick"),
              onContextmenu: /* @__PURE__ */ __name((e2) => handleContextMenu(e2, context.node), "onContextmenu")
            }), "nodeContent"),
            nodeToggleButton: /* @__PURE__ */ __name(() => ({
              onClick: /* @__PURE__ */ __name((e2) => {
                e2.stopImmediatePropagation();
              }, "onClick")
            }), "nodeToggleButton")
          }
        }, {
          folder: withCtx(({ node: node2 }) => [
            renderSlot(_ctx.$slots, "folder", { node: node2 }, () => [
              createVNode(TreeExplorerTreeNode, { node: node2 }, null, 8, ["node"])
            ], true)
          ]),
          node: withCtx(({ node: node2 }) => [
            renderSlot(_ctx.$slots, "node", { node: node2 }, () => [
              createVNode(TreeExplorerTreeNode, { node: node2 }, null, 8, ["node"])
            ], true)
          ]),
          _: 3
        }, 8, ["expanded-keys", "selection-keys", "class", "value", "pt"]),
        createVNode(unref(script$f), {
          ref_key: "menu",
          ref: menu2,
          model: menuItems.value
        }, null, 8, ["model"])
      ], 64);
    };
  }
});
const TreeExplorer = /* @__PURE__ */ _export_sfc(_sfc_main$1U, [["__scopeId", "data-v-d85e78c0"]]);
const useElectronDownloadStore = defineStore("downloads", () => {
  const downloads = ref([]);
  const { DownloadManager } = electronAPI();
  const findByUrl = /* @__PURE__ */ __name((url) => downloads.value.find((download) => url === download.url), "findByUrl");
  const initialize = /* @__PURE__ */ __name(async () => {
    if (isElectron()) {
      const allDownloads = await DownloadManager.getAllDownloads();
      for (const download of allDownloads) {
        downloads.value.push(download);
      }
      DownloadManager.onDownloadProgress((data) => {
        if (!findByUrl(data.url)) {
          downloads.value.push(data);
        }
        const download = findByUrl(data.url);
        if (download) {
          download.progress = data.progress;
          download.status = data.status;
          download.filename = data.filename;
          download.savePath = data.savePath;
        }
      });
    }
  }, "initialize");
  void initialize();
  const start = /* @__PURE__ */ __name(({
    url,
    savePath,
    filename
  }) => DownloadManager.startDownload(url, savePath, filename), "start");
  const pause = /* @__PURE__ */ __name((url) => DownloadManager.pauseDownload(url), "pause");
  const resume = /* @__PURE__ */ __name((url) => DownloadManager.resumeDownload(url), "resume");
  const cancel = /* @__PURE__ */ __name((url) => DownloadManager.cancelDownload(url), "cancel");
  return {
    downloads,
    start,
    pause,
    resume,
    cancel,
    findByUrl,
    initialize,
    inProgressDownloads: computed(
      () => downloads.value.filter(
        ({ status }) => status !== DownloadStatus.COMPLETED
      )
    )
  };
});
const _hoisted_1$1B = { class: "flex flex-col" };
const _hoisted_2$1f = { key: 0 };
const _hoisted_3$R = {
  key: 1,
  class: "mt-2 flex flex-row items-center gap-2"
};
const _sfc_main$1T = /* @__PURE__ */ defineComponent({
  __name: "DownloadItem",
  props: {
    download: {}
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const electronDownloadStore = useElectronDownloadStore();
    const props = __props;
    const getDownloadLabel = /* @__PURE__ */ __name((savePath) => {
      let parts2 = savePath.split("/");
      parts2 = parts2.length === 1 ? parts2[0].split("\\") : parts2;
      const name2 = parts2.pop();
      const dir = parts2.pop();
      return `${dir}/${name2}`;
    }, "getDownloadLabel");
    const triggerCancelDownload = /* @__PURE__ */ __name(() => electronDownloadStore.cancel(props.download.url), "triggerCancelDownload");
    const triggerPauseDownload = /* @__PURE__ */ __name(() => electronDownloadStore.pause(props.download.url), "triggerPauseDownload");
    const triggerResumeDownload = /* @__PURE__ */ __name(() => electronDownloadStore.resume(props.download.url), "triggerResumeDownload");
    const handleRemoveDownload = /* @__PURE__ */ __name(() => {
      electronDownloadStore.$patch((state2) => {
        state2.downloads = state2.downloads.filter(
          ({ url }) => url !== props.download.url
        );
      });
    }, "handleRemoveDownload");
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", _hoisted_1$1B, [
        createBaseVNode("div", null, toDisplayString(getDownloadLabel(_ctx.download.savePath ?? "")), 1),
        ["cancelled", "error"].includes(_ctx.download.status ?? "") ? (openBlock(), createElementBlock("div", _hoisted_2$1f, [
          createVNode(unref(script$3), {
            class: "mt-2 h-6 bg-red-700 text-sm font-light",
            removable: "",
            onRemove: handleRemoveDownload
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(unref(t2)("electronFileDownload.cancelled")), 1)
            ]),
            _: 1
          })
        ])) : createCommentVNode("", true),
        ["in_progress", "paused", "completed"].includes(_ctx.download.status ?? "") ? (openBlock(), createElementBlock("div", _hoisted_3$R, [
          createVNode(unref(script$j), {
            class: "flex-1",
            value: Number(((_ctx.download.progress ?? 0) * 100).toFixed(1)),
            "show-value": (_ctx.download.progress ?? 0) > 0.1
          }, null, 8, ["value", "show-value"]),
          _ctx.download.status === "in_progress" ? withDirectives((openBlock(), createBlock(_sfc_main$2A, {
            key: 0,
            class: "size-[22px] rounded-full",
            variant: "secondary",
            size: "icon-sm",
            "aria-label": unref(t2)("electronFileDownload.pause"),
            onClick: triggerPauseDownload
          }, {
            default: withCtx(() => _cache[0] || (_cache[0] = [
              createBaseVNode("i", { class: "icon-[lucide--pause] size-3" }, null, -1)
            ])),
            _: 1
          }, 8, ["aria-label"])), [
            [
              _directive_tooltip,
              unref(t2)("electronFileDownload.pause"),
              void 0,
              { top: true }
            ]
          ]) : createCommentVNode("", true),
          _ctx.download.status === "paused" ? withDirectives((openBlock(), createBlock(_sfc_main$2A, {
            key: 1,
            class: "size-[22px] rounded-full",
            variant: "secondary",
            size: "icon-sm",
            "aria-label": unref(t2)("electronFileDownload.resume"),
            onClick: triggerResumeDownload
          }, {
            default: withCtx(() => _cache[1] || (_cache[1] = [
              createBaseVNode("i", { class: "icon-[lucide--play] size-3" }, null, -1)
            ])),
            _: 1
          }, 8, ["aria-label"])), [
            [
              _directive_tooltip,
              unref(t2)("electronFileDownload.resume"),
              void 0,
              { top: true }
            ]
          ]) : createCommentVNode("", true),
          ["in_progress", "paused"].includes(_ctx.download.status ?? "") ? withDirectives((openBlock(), createBlock(_sfc_main$2A, {
            key: 2,
            class: "size-[22px] rounded-full",
            variant: "destructive",
            size: "icon-sm",
            "aria-label": unref(t2)("electronFileDownload.cancel"),
            onClick: triggerCancelDownload
          }, {
            default: withCtx(() => _cache[2] || (_cache[2] = [
              createBaseVNode("i", { class: "icon-[lucide--x-circle] size-3" }, null, -1)
            ])),
            _: 1
          }, 8, ["aria-label"])), [
            [
              _directive_tooltip,
              unref(t2)("electronFileDownload.cancel"),
              void 0,
              { top: true }
            ]
          ]) : createCommentVNode("", true)
        ])) : createCommentVNode("", true)
      ]);
    };
  }
});
const _hoisted_1$1A = {
  key: 0,
  class: "mx-6 mb-4"
};
const _hoisted_2$1e = { class: "my-4 text-lg" };
const _sfc_main$1S = /* @__PURE__ */ defineComponent({
  __name: "ElectronDownloadItems",
  setup(__props) {
    const electronDownloadStore = useElectronDownloadStore();
    const { inProgressDownloads } = storeToRefs(electronDownloadStore);
    return (_ctx, _cache) => {
      return unref(inProgressDownloads).length > 0 ? (openBlock(), createElementBlock("div", _hoisted_1$1A, [
        createBaseVNode("div", _hoisted_2$1e, toDisplayString(_ctx.$t("electronFileDownload.inProgress")), 1),
        (openBlock(true), createElementBlock(Fragment, null, renderList(unref(inProgressDownloads), (download) => {
          return openBlock(), createBlock(_sfc_main$1T, {
            key: download.url,
            download
          }, null, 8, ["download"]);
        }), 128))
      ])) : createCommentVNode("", true);
    };
  }
});
function _findInMetadata(metadata, ...keys) {
  for (const key of keys) {
    if (key in metadata) {
      return metadata[key];
    }
    for (const k in metadata) {
      if (k.endsWith(key)) {
        return metadata[k];
      }
    }
  }
  return null;
}
__name(_findInMetadata, "_findInMetadata");
class ComfyModelDef {
  static {
    __name(this, "ComfyModelDef");
  }
  /** Path to the model */
  path_index;
  /** Proper filename of the model */
  file_name;
  /** Normalized filename of the model, with all backslashes replaced with forward slashes */
  normalized_file_name;
  /** Directory containing the model, eg 'checkpoints' */
  directory;
  /** Simplified copy of name, used as a default title. Excludes the directory and the '.safetensors' file extension */
  simplified_file_name;
  /** Key for the model, used to uniquely identify the model. */
  key;
  /** Title / display name of the model, sometimes same as the name but not always */
  title;
  /** Metadata: architecture ID for the model, such as 'stable-diffusion-xl-v1-base' */
  architecture_id = "";
  /** Metadata: author of the model */
  author = "";
  /** Metadata: resolution of the model, eg '1024x1024' */
  resolution = "";
  /** Metadata: description of the model */
  description = "";
  /** Metadata: usage hint for the model */
  usage_hint = "";
  /** Metadata: trigger phrase for the model */
  trigger_phrase = "";
  /** Metadata: tags list for the model */
  tags = [];
  /** Metadata: image for the model */
  image = "";
  /** Whether the model metadata has been loaded from the server, used for `load()` */
  has_loaded_metadata = false;
  /** If true, a metadata load request has been triggered, but may or may not yet have finished loading */
  is_load_requested = false;
  /** A string full of auto-computed lowercase-only searchable text for this model */
  searchable = "";
  constructor(name2, directory, pathIndex) {
    this.path_index = pathIndex;
    this.file_name = name2;
    this.normalized_file_name = name2.replaceAll("\\", "/");
    this.simplified_file_name = this.normalized_file_name.split("/").pop() ?? "";
    if (this.simplified_file_name.endsWith(".safetensors")) {
      this.simplified_file_name = this.simplified_file_name.slice(
        0,
        -".safetensors".length
      );
    }
    this.title = this.simplified_file_name;
    this.directory = directory;
    this.key = `${directory}/${this.normalized_file_name}`;
    this.updateSearchable();
  }
  updateSearchable() {
    this.searchable = [
      this.file_name,
      this.title,
      this.author,
      this.description,
      this.trigger_phrase,
      this.tags.join(", ")
    ].join("\n").toLowerCase();
  }
  /** Loads the model metadata from the server, filling in this object if data is available */
  async load() {
    if (this.has_loaded_metadata || this.is_load_requested) {
      return;
    }
    this.is_load_requested = true;
    try {
      const metadata = await api.viewMetadata(this.directory, this.file_name);
      if (!metadata) {
        return;
      }
      this.title = _findInMetadata(
        metadata,
        "modelspec.title",
        "title",
        "display_name",
        "name"
      ) || this.title;
      this.architecture_id = _findInMetadata(metadata, "modelspec.architecture", "architecture") || "";
      this.author = _findInMetadata(metadata, "modelspec.author", "author") || "";
      this.description = _findInMetadata(metadata, "modelspec.description", "description") || "";
      this.resolution = _findInMetadata(metadata, "modelspec.resolution", "resolution") || "";
      this.usage_hint = _findInMetadata(metadata, "modelspec.usage_hint", "usage_hint") || "";
      this.trigger_phrase = _findInMetadata(
        metadata,
        "modelspec.trigger_phrase",
        "trigger_phrase"
      ) || "";
      this.image = _findInMetadata(
        metadata,
        "modelspec.thumbnail",
        "thumbnail",
        "image",
        "icon"
      ) || "";
      const tagsCommaSeparated = _findInMetadata(metadata, "modelspec.tags", "tags") || "";
      this.tags = tagsCommaSeparated.split(",").map((tag) => tag.trim());
      this.has_loaded_metadata = true;
      this.updateSearchable();
    } catch (error) {
      console.error("Error loading model metadata", this.file_name, this, error);
    }
  }
}
var ResourceState = /* @__PURE__ */ ((ResourceState2) => {
  ResourceState2[ResourceState2["Uninitialized"] = 0] = "Uninitialized";
  ResourceState2[ResourceState2["Loading"] = 1] = "Loading";
  ResourceState2[ResourceState2["Loaded"] = 2] = "Loaded";
  return ResourceState2;
})(ResourceState || {});
class ModelFolder {
  static {
    __name(this, "ModelFolder");
  }
  constructor(directory, getModelsFunc) {
    this.directory = directory;
    this.getModelsFunc = getModelsFunc;
  }
  /** Models in this folder */
  models = {};
  state = 0;
  get key() {
    return this.directory + "/";
  }
  /**
   * Loads the models in this folder from the server
   */
  async load() {
    if (this.state !== 0) {
      return this;
    }
    this.state = 1;
    const models = await this.getModelsFunc(this.directory);
    for (const model of models) {
      this.models[`${model.pathIndex}/${model.name}`] = new ComfyModelDef(
        model.name,
        this.directory,
        model.pathIndex
      );
    }
    this.state = 2;
    return this;
  }
}
const useModelStore = defineStore("models", () => {
  const settingStore = useSettingStore();
  const modelFolderNames = ref([]);
  const modelFolderByName = ref({});
  const modelFolders = computed(
    () => modelFolderNames.value.map(
      (folderName) => modelFolderByName.value[folderName]
    )
  );
  const models = computed(
    () => modelFolders.value.flatMap((folder) => Object.values(folder.models))
  );
  async function loadModelFolders() {
    const useAssetAPI = settingStore.get("Comfy.Assets.UseAssetAPI");
    const resData = useAssetAPI ? await assetService.getAssetModelFolders() : await api.getModelFolders();
    modelFolderNames.value = resData.map((folder) => folder.name);
    modelFolderByName.value = {};
    for (const folderName of modelFolderNames.value) {
      const getModelsFunc = useAssetAPI ? (folder) => assetService.getAssetModels(folder) : (folder) => api.getModels(folder);
      modelFolderByName.value[folderName] = new ModelFolder(
        folderName,
        getModelsFunc
      );
    }
  }
  __name(loadModelFolders, "loadModelFolders");
  async function getLoadedModelFolder(folderName) {
    const folder = modelFolderByName.value[folderName];
    return folder ? await folder.load() : null;
  }
  __name(getLoadedModelFolder, "getLoadedModelFolder");
  async function loadModels() {
    return Promise.all(modelFolders.value.map((folder) => folder.load()));
  }
  __name(loadModels, "loadModels");
  return {
    models,
    modelFolders,
    loadModelFolders,
    loadModels,
    getLoadedModelFolder
  };
});
const _hoisted_1$1z = { class: "model_preview" };
const _hoisted_2$1d = { class: "model_preview_title" };
const _hoisted_3$Q = { class: "model_preview_top_container" };
const _hoisted_4$F = { class: "model_preview_filename" };
const _hoisted_5$z = {
  key: 0,
  class: "model_preview_architecture"
};
const _hoisted_6$q = { class: "model_preview_prefix" };
const _hoisted_7$n = {
  key: 1,
  class: "model_preview_author"
};
const _hoisted_8$h = { class: "model_preview_prefix" };
const _hoisted_9$d = {
  key: 0,
  class: "model_preview_image"
};
const _hoisted_10$b = ["src"];
const _hoisted_11$7 = {
  key: 1,
  class: "model_preview_usage_hint"
};
const _hoisted_12$5 = { class: "model_preview_prefix" };
const _hoisted_13$5 = {
  key: 2,
  class: "model_preview_trigger_phrase"
};
const _hoisted_14$3 = { class: "model_preview_prefix" };
const _hoisted_15$3 = {
  key: 3,
  class: "model_preview_description"
};
const _hoisted_16$2 = { class: "model_preview_prefix" };
const _sfc_main$1R = /* @__PURE__ */ defineComponent({
  __name: "ModelPreview",
  props: {
    modelDef: {
      type: ComfyModelDef,
      required: true
    }
  },
  setup(__props) {
    const props = __props;
    const modelDef = props.modelDef;
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1z, [
        createBaseVNode("div", _hoisted_2$1d, toDisplayString(unref(modelDef).title), 1),
        createBaseVNode("div", _hoisted_3$Q, [
          createBaseVNode("div", _hoisted_4$F, toDisplayString(unref(modelDef).file_name), 1),
          unref(modelDef).architecture_id ? (openBlock(), createElementBlock("div", _hoisted_5$z, [
            createBaseVNode("span", _hoisted_6$q, toDisplayString(_ctx.$t("g.architecture")) + ": ", 1),
            createTextVNode(" " + toDisplayString(unref(modelDef).architecture_id), 1)
          ])) : createCommentVNode("", true),
          unref(modelDef).author ? (openBlock(), createElementBlock("div", _hoisted_7$n, [
            createBaseVNode("span", _hoisted_8$h, toDisplayString(_ctx.$t("g.author")) + ": ", 1),
            createTextVNode(" " + toDisplayString(unref(modelDef).author), 1)
          ])) : createCommentVNode("", true)
        ]),
        unref(modelDef).image ? (openBlock(), createElementBlock("div", _hoisted_9$d, [
          createBaseVNode("img", {
            src: unref(modelDef).image
          }, null, 8, _hoisted_10$b)
        ])) : createCommentVNode("", true),
        unref(modelDef).usage_hint ? (openBlock(), createElementBlock("div", _hoisted_11$7, [
          createBaseVNode("span", _hoisted_12$5, toDisplayString(_ctx.$t("g.usageHint")) + ": ", 1),
          createTextVNode(" " + toDisplayString(unref(modelDef).usage_hint), 1)
        ])) : createCommentVNode("", true),
        unref(modelDef).trigger_phrase ? (openBlock(), createElementBlock("div", _hoisted_13$5, [
          createBaseVNode("span", _hoisted_14$3, toDisplayString(_ctx.$t("g.triggerPhrase")) + ": ", 1),
          createTextVNode(" " + toDisplayString(unref(modelDef).trigger_phrase), 1)
        ])) : createCommentVNode("", true),
        unref(modelDef).description ? (openBlock(), createElementBlock("div", _hoisted_15$3, [
          createBaseVNode("span", _hoisted_16$2, toDisplayString(_ctx.$t("g.description")) + ": ", 1),
          createTextVNode(" " + toDisplayString(unref(modelDef).description), 1)
        ])) : createCommentVNode("", true)
      ]);
    };
  }
});
const ModelPreview = /* @__PURE__ */ _export_sfc(_sfc_main$1R, [["__scopeId", "data-v-a1867382"]]);
const _hoisted_1$1y = {
  key: 0,
  class: "model-lib-model-icon-container"
};
const _sfc_main$1Q = /* @__PURE__ */ defineComponent({
  __name: "ModelTreeLeaf",
  props: {
    node: {}
  },
  setup(__props) {
    const props = __props;
    const modelDef = computed(() => props.node.data);
    const modelPreviewUrl = computed(() => {
      if (modelDef.value.image) {
        return modelDef.value.image;
      }
      const folder = modelDef.value.directory;
      const path_index = modelDef.value.path_index;
      const extension = modelDef.value.file_name.split(".").pop();
      const filename = modelDef.value.file_name.replace(`.${extension}`, ".webp");
      const encodedFilename = encodeURIComponent(filename).replace(/%2F/g, "/");
      return `/api/experiment/models/preview/${folder}/${path_index}/${encodedFilename}`;
    });
    const previewRef = ref(null);
    const modelPreviewStyle = ref({
      position: "absolute",
      top: "0px",
      left: "0px"
    });
    const settingStore = useSettingStore();
    const sidebarLocation = computed(
      () => settingStore.get("Comfy.Sidebar.Location")
    );
    const handleModelHover = /* @__PURE__ */ __name(async () => {
      const hoverTarget = modelContentElement.value;
      if (!hoverTarget) return;
      const targetRect = hoverTarget.getBoundingClientRect();
      const previewHeight = previewRef.value?.$el.offsetHeight || 0;
      const availableSpaceBelow = window.innerHeight - targetRect.bottom;
      modelPreviewStyle.value.top = previewHeight > availableSpaceBelow ? `${Math.max(0, targetRect.top - (previewHeight - availableSpaceBelow) - 20)}px` : `${targetRect.top - 40}px`;
      if (sidebarLocation.value === "left") {
        modelPreviewStyle.value.left = `${targetRect.right}px`;
      } else {
        modelPreviewStyle.value.left = `${targetRect.left - 400}px`;
      }
      await modelDef.value.load();
    }, "handleModelHover");
    const container = ref();
    const modelContentElement = ref();
    const isHovered = ref(false);
    const showPreview = computed(() => {
      return isHovered.value && modelDef.value && modelDef.value.has_loaded_metadata && (modelDef.value.author || modelDef.value.simplified_file_name != modelDef.value.title || modelDef.value.description || modelDef.value.usage_hint || modelDef.value.trigger_phrase || modelDef.value.image);
    });
    const handleMouseEnter = /* @__PURE__ */ __name(async () => {
      isHovered.value = true;
      await nextTick();
      await handleModelHover();
    }, "handleMouseEnter");
    const handleMouseLeave = /* @__PURE__ */ __name(() => {
      isHovered.value = false;
    }, "handleMouseLeave");
    onMounted(async () => {
      modelContentElement.value = container.value?.closest(".p-tree-node-content") ?? void 0;
      modelContentElement.value?.addEventListener("mouseenter", handleMouseEnter);
      modelContentElement.value?.addEventListener("mouseleave", handleMouseLeave);
      await modelDef.value.load();
    });
    onUnmounted(() => {
      modelContentElement.value?.removeEventListener("mouseenter", handleMouseEnter);
      modelContentElement.value?.removeEventListener("mouseleave", handleMouseLeave);
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        ref_key: "container",
        ref: container,
        class: "model-lib-node-container h-full w-full"
      }, [
        createVNode(TreeExplorerTreeNode, { node: _ctx.node }, {
          "before-label": withCtx(() => [
            modelPreviewUrl.value ? (openBlock(), createElementBlock("span", _hoisted_1$1y, [
              createBaseVNode("span", {
                class: "model-lib-model-icon",
                style: normalizeStyle({ backgroundImage: `url(${modelPreviewUrl.value})` })
              }, null, 4)
            ])) : createCommentVNode("", true)
          ]),
          _: 1
        }, 8, ["node"]),
        showPreview.value ? (openBlock(), createBlock(Teleport, {
          key: 0,
          to: "#model-library-model-preview-container"
        }, [
          createBaseVNode("div", {
            class: "model-lib-model-preview",
            style: normalizeStyle(modelPreviewStyle.value)
          }, [
            createVNode(ModelPreview, {
              ref_key: "previewRef",
              ref: previewRef,
              "model-def": modelDef.value
            }, null, 8, ["model-def"])
          ], 4)
        ])) : createCommentVNode("", true)
      ], 512);
    };
  }
});
const ModelTreeLeaf = /* @__PURE__ */ _export_sfc(_sfc_main$1Q, [["__scopeId", "data-v-641705c5"]]);
function useTreeExpansion(expandedKeys) {
  const toggleNode = /* @__PURE__ */ __name((node2) => {
    if (node2.key && typeof node2.key === "string") {
      if (node2.key in expandedKeys.value) {
        delete expandedKeys.value[node2.key];
      } else {
        expandedKeys.value[node2.key] = true;
      }
    }
  }, "toggleNode");
  const toggleNodeRecursive = /* @__PURE__ */ __name((node2) => {
    if (node2.key && typeof node2.key === "string") {
      if (node2.key in expandedKeys.value) {
        collapseNode(node2);
      } else {
        expandNode(node2);
      }
    }
  }, "toggleNodeRecursive");
  const expandNode = /* @__PURE__ */ __name((node2) => {
    if (node2.key && typeof node2.key === "string" && !node2.leaf) {
      expandedKeys.value[node2.key] = true;
      for (const child of node2.children ?? []) {
        expandNode(child);
      }
    }
  }, "expandNode");
  const collapseNode = /* @__PURE__ */ __name((node2) => {
    if (node2.key && typeof node2.key === "string" && !node2.leaf) {
      delete expandedKeys.value[node2.key];
      for (const child of node2.children ?? []) {
        collapseNode(child);
      }
    }
  }, "collapseNode");
  const toggleNodeOnEvent = /* @__PURE__ */ __name((e2, node2) => {
    if (e2.ctrlKey) {
      toggleNodeRecursive(node2);
    } else {
      toggleNode(node2);
    }
  }, "toggleNodeOnEvent");
  return {
    toggleNode,
    toggleNodeRecursive,
    expandNode,
    collapseNode,
    toggleNodeOnEvent
  };
}
__name(useTreeExpansion, "useTreeExpansion");
const _hoisted_1$1x = { class: "px-2 2xl:px-4" };
const _sfc_main$1P = /* @__PURE__ */ defineComponent({
  __name: "ModelLibrarySidebarTab",
  setup(__props) {
    const modelStore = useModelStore();
    const modelToNodeStore = useModelToNodeStore();
    const settingStore = useSettingStore();
    const searchBoxRef = ref();
    const searchQuery = ref("");
    const expandedKeys = ref({});
    const { expandNode, toggleNodeOnEvent } = useTreeExpansion(expandedKeys);
    const filteredModels = ref([]);
    const handleSearch = /* @__PURE__ */ __name(async (query) => {
      if (!query) {
        filteredModels.value = [];
        expandedKeys.value = {};
        return;
      }
      await modelStore.loadModels();
      const search = query.toLocaleLowerCase();
      filteredModels.value = modelStore.models.filter((model) => {
        return model.searchable.includes(search);
      });
      await nextTick();
      expandNode(root.value);
    }, "handleSearch");
    const root = computed(() => {
      const allNodes = searchQuery.value ? filteredModels.value : [...modelStore.modelFolders, ...modelStore.models];
      return buildTree(
        allNodes,
        (modelOrFolder) => modelOrFolder.key.split("/")
      );
    });
    const renderedRoot = computed(() => {
      const nameFormat = settingStore.get("Comfy.ModelLibrary.NameFormat");
      const fillNodeInfo = /* @__PURE__ */ __name((node2) => {
        const children = node2.children?.map(fillNodeInfo);
        const model = node2.leaf && node2.data ? node2.data : null;
        const folder = !node2.leaf && node2.data ? node2.data : null;
        return {
          key: node2.key,
          label: model ? nameFormat === "title" ? model.title : model.simplified_file_name : node2.label,
          leaf: node2.leaf,
          data: node2.data,
          getIcon() {
            if (model) {
              return model.image ? "pi pi-image" : "pi pi-file";
            }
            if (folder) {
              return folder.state === ResourceState.Loading ? "pi pi-spin pi-spinner" : "pi pi-folder";
            }
            return "pi pi-folder";
          },
          getBadgeText() {
            if (!folder) {
              return;
            }
            return folder.state === ResourceState.Loaded ? void 0 : "";
          },
          children,
          draggable: node2.leaf,
          handleClick(e2) {
            if (this.leaf) {
              const provider = modelToNodeStore.getNodeProvider(model.directory);
              if (provider) {
                const node22 = useLitegraphService().addNodeOnGraph(provider.nodeDef);
                const widget = node22.widgets.find(
                  (widget2) => widget2.name === provider.key
                );
                if (widget) {
                  widget.value = model.file_name;
                }
              }
            } else {
              toggleNodeOnEvent(e2, node2);
            }
          }
        };
      }, "fillNodeInfo");
      return fillNodeInfo(root.value);
    });
    watch(
      toRef$2(expandedKeys, "value"),
      (newExpandedKeys) => {
        Object.entries(newExpandedKeys).forEach(([key, isExpanded]) => {
          if (isExpanded) {
            const folderPath = key.split("/").slice(1).join("/");
            if (folderPath && !folderPath.includes("/")) {
              void modelStore.getLoadedModelFolder(folderPath);
            }
          }
        });
      },
      { deep: true }
    );
    onMounted(async () => {
      searchBoxRef.value?.focus();
      if (settingStore.get("Comfy.ModelLibrary.AutoLoadAll")) {
        await modelStore.loadModels();
      }
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock(Fragment, null, [
        createVNode(SidebarTabTemplate, {
          title: _ctx.$t("sideToolbar.modelLibrary")
        }, {
          "tool-buttons": withCtx(() => [
            withDirectives((openBlock(), createBlock(_sfc_main$2A, {
              variant: "muted-textonly",
              size: "icon",
              "aria-label": _ctx.$t("g.refresh"),
              onClick: unref(modelStore).loadModelFolders
            }, {
              default: withCtx(() => _cache[2] || (_cache[2] = [
                createBaseVNode("i", { class: "icon-[lucide--refresh-cw] size-4" }, null, -1)
              ])),
              _: 1
            }, 8, ["aria-label", "onClick"])), [
              [
                _directive_tooltip,
                _ctx.$t("g.refresh"),
                void 0,
                { bottom: true }
              ]
            ]),
            withDirectives((openBlock(), createBlock(_sfc_main$2A, {
              variant: "muted-textonly",
              size: "icon",
              "aria-label": _ctx.$t("g.loadAllFolders"),
              onClick: unref(modelStore).loadModels
            }, {
              default: withCtx(() => _cache[3] || (_cache[3] = [
                createBaseVNode("i", { class: "icon-[lucide--cloud-download] size-4" }, null, -1)
              ])),
              _: 1
            }, 8, ["aria-label", "onClick"])), [
              [
                _directive_tooltip,
                _ctx.$t("g.loadAllFolders"),
                void 0,
                { bottom: true }
              ]
            ])
          ]),
          header: withCtx(() => [
            createBaseVNode("div", _hoisted_1$1x, [
              createVNode(SearchBox, {
                ref_key: "searchBoxRef",
                ref: searchBoxRef,
                "model-value": searchQuery.value,
                "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchQuery.value = $event),
                placeholder: _ctx.$t("g.searchModels") + "...",
                onSearch: handleSearch
              }, null, 8, ["model-value", "placeholder"])
            ])
          ]),
          body: withCtx(() => [
            unref(isElectron)() ? (openBlock(), createBlock(_sfc_main$1S, { key: 0 })) : createCommentVNode("", true),
            createVNode(unref(script$h), {
              type: "dashed",
              class: "m-2"
            }),
            createVNode(TreeExplorer, {
              "expanded-keys": expandedKeys.value,
              "onUpdate:expandedKeys": _cache[1] || (_cache[1] = ($event) => expandedKeys.value = $event),
              class: "model-lib-tree-explorer",
              root: renderedRoot.value
            }, {
              node: withCtx(({ node: node2 }) => [
                createVNode(ModelTreeLeaf, { node: node2 }, null, 8, ["node"])
              ]),
              _: 1
            }, 8, ["expanded-keys", "root"])
          ]),
          _: 1
        }, 8, ["title"]),
        _cache[4] || (_cache[4] = createBaseVNode("div", { id: "model-library-model-preview-container" }, null, -1))
      ], 64);
    };
  }
});
const ModelLibrarySidebarTab = /* @__PURE__ */ _export_sfc(_sfc_main$1P, [["__scopeId", "data-v-7d2ea885"]]);
const useModelLibrarySidebarTab = /* @__PURE__ */ __name(() => {
  return {
    id: "model-library",
    icon: "icon-[comfy--ai-model]",
    title: "sideToolbar.modelLibrary",
    tooltip: "sideToolbar.modelLibrary",
    label: "sideToolbar.labels.models",
    component: markRaw(ModelLibrarySidebarTab),
    type: "vue",
    iconBadge: /* @__PURE__ */ __name(() => {
      if (isElectron()) {
        const electronDownloadStore = useElectronDownloadStore();
        if (electronDownloadStore.inProgressDownloads.length > 0) {
          return electronDownloadStore.inProgressDownloads.length.toString();
        }
      }
      return null;
    }, "iconBadge")
  };
}, "useModelLibrarySidebarTab");
function useVueFeatureFlagsIndividual() {
  const settingStore = useSettingStore();
  const shouldRenderVueNodes = computed(() => {
    try {
      return settingStore.get("Comfy.VueNodes.Enabled") ?? false;
    } catch {
      return false;
    }
  });
  watch(
    shouldRenderVueNodes,
    () => {
      LiteGraph.vueNodesMode = shouldRenderVueNodes.value;
    },
    { immediate: true }
  );
  return {
    shouldRenderVueNodes
  };
}
__name(useVueFeatureFlagsIndividual, "useVueFeatureFlagsIndividual");
const useVueFeatureFlags = createSharedComposable(
  useVueFeatureFlagsIndividual
);
const _hoisted_1$1w = {
  width: "1.2em",
  height: "1.2em",
  viewBox: "0 0 16 16",
  fill: "none",
  xmlns: "http://www.w3.org/2000/svg"
};
function render$3(_ctx, _cache) {
  return openBlock(), createElementBlock("svg", _hoisted_1$1w, [..._cache[0] || (_cache[0] = [
    createBaseVNode("path", {
      d: "M7.13727 11.2201L6.27454 14.4398",
      stroke: "var(--pin-color, var(--color-smoke-800, #8a8a8a))",
      "stroke-width": "1.3",
      "stroke-linecap": "round",
      "stroke-linejoin": "round"
    }, null, -1),
    createBaseVNode("path", {
      d: "M6.28085 6.68385C6.21652 6.92342 6.08664 7.1403 5.9058 7.31009C5.72497 7.47989 5.50035 7.59587 5.25721 7.645L3.95569 7.91742C3.71254 7.96655 3.48793 8.08253 3.30709 8.25233C3.12626 8.42212 2.99637 8.639 2.93204 8.87857L2.80091 9.36797C2.75515 9.53876 2.7791 9.72073 2.86751 9.87385C2.95591 10.027 3.10153 10.1387 3.27231 10.1845L10.9997 12.255C11.1705 12.3008 11.3525 12.2768 11.5056 12.1884C11.6587 12.1 11.7705 11.9544 11.8162 11.7836L11.9474 11.2942C12.0114 11.0546 12.0074 10.8018 11.9357 10.5643C11.864 10.3269 11.7274 10.1141 11.5414 9.95001L10.5505 9.06333C10.3645 8.89921 10.2279 8.68646 10.1562 8.44899C10.0845 8.21153 10.0805 7.95877 10.1446 7.71913L10.7933 5.29787C10.8391 5.12709 10.9508 4.98148 11.1039 4.89307C11.2571 4.80466 11.439 4.78071 11.6098 4.82647C11.9514 4.91799 12.3153 4.87008 12.6216 4.69327C12.9278 4.51646 13.1513 4.22523 13.2428 3.88366C13.3343 3.54209 13.2864 3.17815 13.1096 2.8719C12.9328 2.56566 12.6416 2.34219 12.3 2.25067L7.1484 0.870299C6.80683 0.778775 6.44289 0.826689 6.13665 1.0035C5.8304 1.18031 5.60694 1.47154 5.51541 1.81311C5.42389 2.15468 5.4718 2.51862 5.64861 2.82487C5.82542 3.13111 6.11665 3.35458 6.45822 3.4461C6.62901 3.49186 6.77462 3.6036 6.86302 3.75672C6.95143 3.90984 6.97539 4.09181 6.92962 4.2626L6.28085 6.68385Z",
      fill: "var(--handle-color, var(--color-gold-600, #fd9903))"
    }, null, -1)
  ])]);
}
__name(render$3, "render$3");
const __unplugin_components_0$2 = markRaw({ name: "comfy-pin", render: render$3 });
const _sfc_main$1O = /* @__PURE__ */ defineComponent({
  __name: "NodeBadge",
  props: {
    text: {},
    fgColor: { default: "currentColor" },
    bgColor: { default: "" },
    cssIcon: {}
  },
  setup(__props) {
    return (_ctx, _cache) => {
      return _ctx.text ? (openBlock(), createElementBlock("div", {
        key: 0,
        class: "min-w-max rounded-sm bg-node-component-surface px-1 py-0.5 text-xs flex items-center gap-1",
        style: normalizeStyle({
          color: _ctx.fgColor,
          backgroundColor: _ctx.bgColor
        })
      }, [
        createTextVNode(toDisplayString(_ctx.text) + " ", 1),
        _ctx.cssIcon ? (openBlock(), createElementBlock("i", {
          key: 0,
          class: normalizeClass(unref(cn)(_ctx.cssIcon))
        }, null, 2)) : createCommentVNode("", true)
      ], 4)) : createCommentVNode("", true);
    };
  }
});
const tooltipsTemporarilyDisabled = ref(false);
const hideTooltipsGlobally = /* @__PURE__ */ __name(() => {
  const tooltips = document.querySelectorAll(".p-tooltip");
  if (tooltips.length === 0) return;
  tooltips.forEach((tooltipEl) => {
    const tooltipId = tooltipEl.id;
    if (!tooltipId) return;
    const targetElements = document.querySelectorAll('[data-pd-tooltip="true"]');
    for (const targetEl of targetElements) {
      if (targetEl.$_ptooltipId === tooltipId) {
        targetEl.dispatchEvent(
          new MouseEvent("mouseleave", { bubbles: true })
        );
        break;
      }
    }
  });
  tooltipsTemporarilyDisabled.value = true;
}, "hideTooltipsGlobally");
const handlePointerUp = /* @__PURE__ */ __name(() => {
  tooltipsTemporarilyDisabled.value = false;
}, "handlePointerUp");
const globalTooltipState = { listenersSetup: false };
function setupGlobalTooltipHiding() {
  if (globalTooltipState.listenersSetup) return;
  document.addEventListener("pointerdown", hideTooltipsGlobally);
  document.addEventListener("pointerup", handlePointerUp);
  window.addEventListener("wheel", hideTooltipsGlobally, {
    capture: true,
    //Need this to bypass the event layer from Litegraph
    passive: true
  });
  globalTooltipState.listenersSetup = true;
}
__name(setupGlobalTooltipHiding, "setupGlobalTooltipHiding");
function useNodeTooltips(nodeType) {
  const nodeDefStore = useNodeDefStore();
  const settingsStore = useSettingStore();
  setupGlobalTooltipHiding();
  const tooltipsEnabled = computed(
    () => settingsStore.get("Comfy.EnableTooltips")
  );
  const nodeDef = computed(() => nodeDefStore.nodeDefsByName[unref(nodeType)]);
  const getNodeDescription = computed(() => {
    if (!tooltipsEnabled.value || !nodeDef.value) return "";
    const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.description`;
    return st(key, nodeDef.value.description || "");
  });
  const getInputSlotTooltip = /* @__PURE__ */ __name((slotName) => {
    if (!tooltipsEnabled.value || !nodeDef.value) return "";
    const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.inputs.${normalizeI18nKey(slotName)}.tooltip`;
    const inputTooltip = nodeDef.value.inputs?.[slotName]?.tooltip ?? "";
    return st(key, inputTooltip);
  }, "getInputSlotTooltip");
  const getOutputSlotTooltip = /* @__PURE__ */ __name((slotIndex) => {
    if (!tooltipsEnabled.value || !nodeDef.value) return "";
    const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.outputs.${slotIndex}.tooltip`;
    const outputTooltip = nodeDef.value.outputs?.[slotIndex]?.tooltip ?? "";
    return st(key, outputTooltip);
  }, "getOutputSlotTooltip");
  const getWidgetTooltip = /* @__PURE__ */ __name((widget) => {
    if (!tooltipsEnabled.value || !nodeDef.value) return "";
    const widgetTooltip = widget.tooltip;
    if (widgetTooltip) return widgetTooltip;
    const key = `nodeDefs.${normalizeI18nKey(unref(nodeType))}.inputs.${normalizeI18nKey(widget.name)}.tooltip`;
    const inputTooltip = nodeDef.value.inputs?.[widget.name]?.tooltip ?? "";
    return st(key, inputTooltip);
  }, "getWidgetTooltip");
  const createTooltipConfig = /* @__PURE__ */ __name((text) => {
    const tooltipDelay = settingsStore.get("LiteGraph.Node.TooltipDelay");
    const tooltipText = text || "";
    return {
      value: tooltipText,
      showDelay: tooltipDelay,
      hideDelay: 0,
      // Immediate hiding
      disabled: !tooltipsEnabled.value || !tooltipText || tooltipsTemporarilyDisabled.value,
      // this reactive value works but only on next mount,
      // so if the tooltip is already visible changing this will not hide it
      pt: {
        text: {
          class: "border-node-component-tooltip-border bg-node-component-tooltip-surface border rounded-md px-4 py-2 text-node-component-tooltip text-sm font-normal leading-tight max-w-75 shadow-none"
        },
        arrow: /* @__PURE__ */ __name(({ context }) => ({
          class: cn(
            context.top && "border-t-node-component-tooltip-border",
            context.bottom && "border-b-node-component-tooltip-border",
            context.left && "border-l-node-component-tooltip-border ",
            context.right && "border-r-node-component-tooltip-border"
          )
        }), "arrow")
      }
    };
  }, "createTooltipConfig");
  return {
    tooltipsEnabled,
    getNodeDescription,
    getInputSlotTooltip,
    getOutputSlotTooltip,
    getWidgetTooltip,
    createTooltipConfig
  };
}
__name(useNodeTooltips, "useNodeTooltips");
function applyLightThemeColor(color2, isLightTheme) {
  if (!color2 || !isLightTheme) {
    return color2;
  }
  return adjustColor(color2, { lightness: 0.5 });
}
__name(applyLightThemeColor, "applyLightThemeColor");
const _hoisted_1$1v = {
  key: 0,
  class: "node-error p-4 text-red-500"
};
const _hoisted_2$1c = ["data-testid"];
const _hoisted_3$P = { class: "flex items-center justify-between gap-2.5 min-w-0" };
const _hoisted_4$E = { class: "relative grow-1 flex items-center gap-2.5 min-w-0 flex-1" };
const _hoisted_5$y = { class: "flex shrink-0 items-center px-0.5" };
const _hoisted_6$p = {
  key: 0,
  class: "icon-[comfy--workflow] size-4"
};
const _hoisted_7$m = {
  key: 1,
  class: "icon-[lucide--component] size-4"
};
const _hoisted_8$g = {
  class: "flex min-w-0 flex-1 items-center gap-2",
  "data-testid": "node-title"
};
const _hoisted_9$c = { class: "truncate min-w-0 flex-1" };
const _hoisted_10$a = { class: "flex shrink-0 items-center justify-between gap-2" };
const _sfc_main$1N = /* @__PURE__ */ defineComponent({
  __name: "NodeHeader",
  props: {
    nodeData: {},
    collapsed: { type: Boolean }
  },
  emits: ["collapse", "update:title", "enter-subgraph"],
  setup(__props, { emit: __emit }) {
    const emit = __emit;
    const renderError = ref(null);
    const { toastErrorHandler } = useErrorHandling();
    onErrorCaptured((error) => {
      renderError.value = error.message;
      toastErrorHandler(error);
      return false;
    });
    const isEditing = ref(false);
    const { getNodeDescription, createTooltipConfig } = useNodeTooltips(
      __props.nodeData?.type || ""
    );
    const tooltipConfig = computed(() => {
      if (isEditing.value) {
        return { value: "", disabled: true };
      }
      const description = getNodeDescription.value;
      return createTooltipConfig(description);
    });
    const enterSubgraphTooltipConfig = computed(() => {
      return createTooltipConfig(st("enterSubgraph", "Enter Subgraph"));
    });
    const headerStyle = computed(() => {
      const colorPaletteStore = useColorPaletteStore();
      const opacity = useSettingStore().get("Comfy.Node.Opacity") ?? 1;
      if (!__props.nodeData?.color) {
        return { backgroundColor: "", opacity };
      }
      const headerColor = applyLightThemeColor(
        __props.nodeData.color,
        Boolean(colorPaletteStore.completedActivePalette.light_theme)
      );
      return { backgroundColor: headerColor, opacity };
    });
    const resolveTitle = /* @__PURE__ */ __name((info) => {
      const title = (info?.title ?? "").trim();
      if (title.length > 0) return title;
      const nodeType = (info?.type ?? "").trim() || "Untitled";
      const key = `nodeDefs.${normalizeI18nKey(nodeType)}.display_name`;
      return st(key, nodeType);
    }, "resolveTitle");
    const displayTitle = ref(resolveTitle(__props.nodeData));
    const bypassed = computed(
      () => __props.nodeData?.mode === LGraphEventMode.BYPASS
    );
    const muted = computed(() => __props.nodeData?.mode === LGraphEventMode.NEVER);
    const statusBadge = computed(
      () => muted.value ? { text: "Muted", cssIcon: "icon-[lucide--ban]" } : bypassed.value ? { text: "Bypassed", cssIcon: "icon-[lucide--redo-dot]" } : void 0
    );
    const nodeBadges = computed(
      () => [...__props.nodeData?.badges ?? []].map(toValue)
    );
    const isPinned = computed(() => Boolean(__props.nodeData?.flags?.pinned));
    const isApiNode = computed(() => Boolean(__props.nodeData?.apiNode));
    const headerShapeClass = computed(() => {
      if (__props.collapsed) {
        switch (__props.nodeData?.shape) {
          case RenderShape.BOX:
            return "rounded-none";
          case RenderShape.CARD:
            return "rounded-tl-2xl rounded-br-2xl rounded-tr-none rounded-bl-none";
          default:
            return "rounded-2xl";
        }
      }
      switch (__props.nodeData?.shape) {
        case RenderShape.BOX:
          return "rounded-t-none";
        case RenderShape.CARD:
          return "rounded-tl-2xl rounded-tr-none";
        default:
          return "rounded-t-2xl";
      }
    });
    const isSubgraphNode = computed(() => {
      if (!__props.nodeData?.id) return false;
      const graph = app$1.rootGraph;
      if (!graph) return false;
      const locatorId = getLocatorIdFromNodeData(__props.nodeData);
      const litegraphNode = getNodeByLocatorId(graph, locatorId);
      return litegraphNode?.isSubgraphNode() ?? false;
    });
    watch(
      () => [__props.nodeData?.title, __props.nodeData?.type],
      () => {
        const next = resolveTitle(__props.nodeData);
        if (next !== displayTitle.value) {
          displayTitle.value = next;
        }
      }
    );
    const handleCollapse = /* @__PURE__ */ __name(() => {
      emit("collapse");
    }, "handleCollapse");
    const handleDoubleClick = /* @__PURE__ */ __name(() => {
      isEditing.value = true;
    }, "handleDoubleClick");
    const handleTitleEdit = /* @__PURE__ */ __name((newTitle) => {
      isEditing.value = false;
      const trimmedTitle = newTitle.trim();
      if (trimmedTitle && trimmedTitle !== displayTitle.value) {
        emit("update:title", trimmedTitle);
      }
    }, "handleTitleEdit");
    const handleTitleCancel = /* @__PURE__ */ __name(() => {
      isEditing.value = false;
    }, "handleTitleCancel");
    const handleEnterSubgraph = /* @__PURE__ */ __name(() => {
      emit("enter-subgraph");
    }, "handleEnterSubgraph");
    return (_ctx, _cache) => {
      const _component_i_comfy58pin = __unplugin_components_0$2;
      const _directive_tooltip = resolveDirective("tooltip");
      return renderError.value ? (openBlock(), createElementBlock("div", _hoisted_1$1v, toDisplayString(unref(st)("nodeErrors.header", "Node Header Error")), 1)) : (openBlock(), createElementBlock("div", {
        key: 1,
        class: normalizeClass(
          unref(cn)(
            "lg-node-header text-sm py-2 pl-2 pr-3 w-full min-w-0",
            "text-node-component-header bg-node-component-header-surface",
            headerShapeClass.value
          )
        ),
        style: normalizeStyle(headerStyle.value),
        "data-testid": `node-header-${_ctx.nodeData?.id || ""}`,
        onDblclick: handleDoubleClick
      }, [
        createBaseVNode("div", _hoisted_3$P, [
          createBaseVNode("div", _hoisted_4$E, [
            createBaseVNode("div", _hoisted_5$y, [
              createVNode(_sfc_main$2A, {
                size: "icon-sm",
                variant: "textonly",
                class: "hover:bg-transparent",
                "data-testid": "node-collapse-button",
                onClick: withModifiers(handleCollapse, ["stop"]),
                onDblclick: _cache[0] || (_cache[0] = withModifiers(() => {
                }, ["stop"]))
              }, {
                default: withCtx(() => [
                  createBaseVNode("i", {
                    class: normalizeClass([
                      unref(cn)(
                        "icon-[lucide--chevron-down] size-5 transition-transform",
                        _ctx.collapsed && "-rotate-90"
                      ),
                      "text-node-component-header-icon"
                    ])
                  }, null, 2)
                ]),
                _: 1
              })
            ]),
            isSubgraphNode.value ? (openBlock(), createElementBlock("div", _hoisted_6$p)) : createCommentVNode("", true),
            isApiNode.value ? (openBlock(), createElementBlock("div", _hoisted_7$m)) : createCommentVNode("", true),
            withDirectives((openBlock(), createElementBlock("div", _hoisted_8$g, [
              createBaseVNode("div", _hoisted_9$c, [
                createVNode(EditableText, {
                  "model-value": displayTitle.value,
                  "is-editing": isEditing.value,
                  "input-attrs": { "data-testid": "node-title-input" },
                  onEdit: handleTitleEdit,
                  onCancel: handleTitleCancel
                }, null, 8, ["model-value", "is-editing"])
              ])
            ])), [
              [
                _directive_tooltip,
                tooltipConfig.value,
                void 0,
                { top: true }
              ]
            ])
          ]),
          createBaseVNode("div", _hoisted_10$a, [
            (openBlock(true), createElementBlock(Fragment, null, renderList(nodeBadges.value, (badge) => {
              return openBlock(), createBlock(_sfc_main$1O, mergeProps({
                key: badge.text,
                ref_for: true
              }, badge), null, 16);
            }), 128)),
            statusBadge.value ? (openBlock(), createBlock(_sfc_main$1O, normalizeProps(mergeProps({ key: 0 }, statusBadge.value)), null, 16)) : createCommentVNode("", true),
            isPinned.value ? (openBlock(), createBlock(_component_i_comfy58pin, {
              key: 1,
              class: "size-5",
              "data-testid": "node-pin-indicator"
            })) : createCommentVNode("", true),
            isSubgraphNode.value ? withDirectives((openBlock(), createBlock(_sfc_main$2A, {
              key: 2,
              variant: "textonly",
              size: "sm",
              "data-testid": "subgraph-enter-button",
              class: "text-node-component-header h-5 px-0.5",
              onClick: withModifiers(handleEnterSubgraph, ["stop"]),
              onDblclick: _cache[1] || (_cache[1] = withModifiers(() => {
              }, ["stop"]))
            }, {
              default: withCtx(() => [
                createBaseVNode("span", null, toDisplayString(_ctx.$t("g.edit")), 1),
                _cache[2] || (_cache[2] = createBaseVNode("i", { class: "icon-[lucide--scaling] size-5" }, null, -1))
              ]),
              _: 1
            })), [
              [
                _directive_tooltip,
                enterSubgraphTooltipConfig.value,
                void 0,
                { top: true }
              ]
            ]) : createCommentVNode("", true)
          ])
        ])
      ], 46, _hoisted_2$1c));
    };
  }
});
function coerceINodeSlot(input) {
  return isSlotObject(input) ? input : {
    name: typeof input === "string" ? input : "",
    type: "any",
    boundingRect: [0, 0, 0, 0]
  };
}
__name(coerceINodeSlot, "coerceINodeSlot");
function inputHasWidget(input) {
  return isSlotObject(input) && "widget" in input && input.widget;
}
__name(inputHasWidget, "inputHasWidget");
function nonWidgetedInputs(nodeData) {
  if (!nodeData?.inputs) return [];
  return nodeData.inputs.filter((input) => !inputHasWidget(input)).map(coerceINodeSlot);
}
__name(nonWidgetedInputs, "nonWidgetedInputs");
function linkedWidgetedInputs(nodeData) {
  if (!nodeData?.inputs) return [];
  return nodeData.inputs.filter((input) => inputHasWidget(input) && !!input.link).map(coerceINodeSlot);
}
__name(linkedWidgetedInputs, "linkedWidgetedInputs");
const state = reactive({
  active: false,
  pointerId: null,
  source: null,
  pointer: {
    client: { x: 0, y: 0 },
    canvas: { x: 0, y: 0 }
  },
  candidate: null,
  compatible: /* @__PURE__ */ new Map()
});
function updatePointerPosition(clientX, clientY, canvasX, canvasY) {
  state.pointer.client.x = clientX;
  state.pointer.client.y = clientY;
  state.pointer.canvas.x = canvasX;
  state.pointer.canvas.y = canvasY;
}
__name(updatePointerPosition, "updatePointerPosition");
function setCandidate(candidate) {
  state.candidate = candidate;
}
__name(setCandidate, "setCandidate");
function beginDrag(source, pointerId) {
  state.active = true;
  state.source = source;
  state.pointerId = pointerId;
  state.candidate = null;
  state.compatible.clear();
}
__name(beginDrag, "beginDrag");
function endDrag() {
  state.active = false;
  state.pointerId = null;
  state.source = null;
  state.pointer.client.x = 0;
  state.pointer.client.y = 0;
  state.pointer.canvas.x = 0;
  state.pointer.canvas.y = 0;
  state.candidate = null;
  state.compatible.clear();
}
__name(endDrag, "endDrag");
function getSlotLayout(nodeId, slotIndex, isInput) {
  const slotKey = getSlotKey(nodeId, slotIndex, isInput);
  return layoutStore.getSlotLayout(slotKey);
}
__name(getSlotLayout, "getSlotLayout");
function useSlotLinkDragUIState() {
  return {
    state: readonly(state),
    beginDrag,
    endDrag,
    updatePointerPosition,
    setCandidate,
    getSlotLayout,
    setCompatibleMap: /* @__PURE__ */ __name((entries) => {
      state.compatible.clear();
      for (const [key, value] of entries) state.compatible.set(key, value);
    }, "setCompatibleMap"),
    setCompatibleForKey: /* @__PURE__ */ __name((key, value) => {
      state.compatible.set(key, value);
    }, "setCompatibleForKey"),
    clearCompatible: /* @__PURE__ */ __name(() => state.compatible.clear(), "clearCompatible")
  };
}
__name(useSlotLinkDragUIState, "useSlotLinkDragUIState");
const useNodeSlotRegistryStore = defineStore("nodeSlotRegistry", () => {
  const registry = markRaw(/* @__PURE__ */ new Map());
  function getNode(nodeId) {
    return registry.get(nodeId);
  }
  __name(getNode, "getNode");
  function ensureNode(nodeId) {
    let node2 = registry.get(nodeId);
    if (!node2) {
      node2 = {
        nodeId,
        slots: markRaw(/* @__PURE__ */ new Map())
      };
      registry.set(nodeId, node2);
    }
    return node2;
  }
  __name(ensureNode, "ensureNode");
  function deleteNode(nodeId) {
    registry.delete(nodeId);
  }
  __name(deleteNode, "deleteNode");
  function clear() {
    registry.clear();
  }
  __name(clear, "clear");
  return {
    getNode,
    ensureNode,
    deleteNode,
    clear
  };
});
function createRafBatch(run) {
  let rafId = null;
  const schedule = /* @__PURE__ */ __name(() => {
    if (rafId != null) return;
    rafId = requestAnimationFrame(() => {
      rafId = null;
      run();
    });
  }, "schedule");
  const cancel = /* @__PURE__ */ __name(() => {
    if (rafId != null) {
      cancelAnimationFrame(rafId);
      rafId = null;
    }
  }, "cancel");
  const flush = /* @__PURE__ */ __name(() => {
    if (rafId == null) return;
    cancelAnimationFrame(rafId);
    rafId = null;
    run();
  }, "flush");
  const isScheduled = /* @__PURE__ */ __name(() => rafId != null, "isScheduled");
  return { schedule, cancel, flush, isScheduled };
}
__name(createRafBatch, "createRafBatch");
const pendingNodes = /* @__PURE__ */ new Set();
const raf = createRafBatch(() => {
  flushScheduledSlotLayoutSync();
});
function scheduleSlotLayoutSync(nodeId) {
  pendingNodes.add(nodeId);
  raf.schedule();
}
__name(scheduleSlotLayoutSync, "scheduleSlotLayoutSync");
function flushScheduledSlotLayoutSync() {
  if (pendingNodes.size === 0) return;
  const conv = useSharedCanvasPositionConversion();
  for (const nodeId of Array.from(pendingNodes)) {
    pendingNodes.delete(nodeId);
    syncNodeSlotLayoutsFromDOM(nodeId, conv);
  }
}
__name(flushScheduledSlotLayoutSync, "flushScheduledSlotLayoutSync");
function syncNodeSlotLayoutsFromDOM(nodeId, conv) {
  const nodeSlotRegistryStore = useNodeSlotRegistryStore();
  const node2 = nodeSlotRegistryStore.getNode(nodeId);
  if (!node2) return;
  const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value;
  if (!nodeLayout) return;
  const batch = [];
  for (const [slotKey, entry] of node2.slots) {
    const rect = entry.el.getBoundingClientRect();
    const screenCenter = [
      rect.left + rect.width / 2,
      rect.top + rect.height / 2
    ];
    const [x2, y] = (conv ?? useSharedCanvasPositionConversion()).clientPosToCanvasPos(screenCenter);
    const centerCanvas = { x: x2, y };
    entry.cachedOffset = {
      x: centerCanvas.x - nodeLayout.position.x,
      y: centerCanvas.y - nodeLayout.position.y
    };
    const size = LiteGraph.NODE_SLOT_HEIGHT;
    const half = size / 2;
    batch.push({
      key: slotKey,
      layout: {
        nodeId,
        index: entry.index,
        type: entry.type,
        position: { x: centerCanvas.x, y: centerCanvas.y },
        bounds: {
          x: centerCanvas.x - half,
          y: centerCanvas.y - half,
          width: size,
          height: size
        }
      }
    });
  }
  if (batch.length) layoutStore.batchUpdateSlotLayouts(batch);
}
__name(syncNodeSlotLayoutsFromDOM, "syncNodeSlotLayoutsFromDOM");
function updateNodeSlotsFromCache(nodeId) {
  const nodeSlotRegistryStore = useNodeSlotRegistryStore();
  const node2 = nodeSlotRegistryStore.getNode(nodeId);
  if (!node2) return;
  const nodeLayout = layoutStore.getNodeLayoutRef(nodeId).value;
  if (!nodeLayout) return;
  const batch = [];
  for (const [slotKey, entry] of node2.slots) {
    if (!entry.cachedOffset) {
      scheduleSlotLayoutSync(nodeId);
      continue;
    }
    const centerCanvas = {
      x: nodeLayout.position.x + entry.cachedOffset.x,
      y: nodeLayout.position.y + entry.cachedOffset.y
    };
    const size = LiteGraph.NODE_SLOT_HEIGHT;
    const half = size / 2;
    batch.push({
      key: slotKey,
      layout: {
        nodeId,
        index: entry.index,
        type: entry.type,
        position: { x: centerCanvas.x, y: centerCanvas.y },
        bounds: {
          x: centerCanvas.x - half,
          y: centerCanvas.y - half,
          width: size,
          height: size
        }
      }
    });
  }
  if (batch.length) layoutStore.batchUpdateSlotLayouts(batch);
}
__name(updateNodeSlotsFromCache, "updateNodeSlotsFromCache");
function useSlotElementTracking(options) {
  const { nodeId, index: index2, type, element } = options;
  const nodeSlotRegistryStore = useNodeSlotRegistryStore();
  onMounted(() => {
    if (!nodeId) return;
    const stop = watch(
      element,
      (el) => {
        if (!el) return;
        const node2 = nodeSlotRegistryStore.ensureNode(nodeId);
        if (!node2.stopWatch) {
          const layoutRef = layoutStore.getNodeLayoutRef(nodeId);
          const stopPositionWatch = watch(
            () => layoutRef.value?.position,
            (newPosition, oldPosition) => {
              if (!newPosition) return;
              if (!oldPosition || !isPointEqual(newPosition, oldPosition)) {
                updateNodeSlotsFromCache(nodeId);
              }
            }
          );
          const stopSizeWatch = watch(
            () => layoutRef.value?.size,
            (newSize, oldSize) => {
              if (!newSize) return;
              if (!oldSize || !isSizeEqual(newSize, oldSize)) {
                scheduleSlotLayoutSync(nodeId);
              }
            }
          );
          node2.stopWatch = () => {
            stopPositionWatch();
            stopSizeWatch();
          };
        }
        const slotKey = getSlotKey(nodeId, index2, type === "input");
        el.dataset.slotKey = slotKey;
        node2.slots.set(slotKey, { el, index: index2, type });
        scheduleSlotLayoutSync(nodeId);
        stop();
      },
      { immediate: true, flush: "post" }
    );
  });
  onUnmounted(() => {
    if (!nodeId) return;
    const node2 = nodeSlotRegistryStore.getNode(nodeId);
    if (!node2) return;
    const slotKey = getSlotKey(nodeId, index2, type === "input");
    const entry = node2.slots.get(slotKey);
    if (entry) {
      delete entry.el.dataset.slotKey;
      node2.slots.delete(slotKey);
    }
    layoutStore.deleteSlotLayout(slotKey);
    if (node2.slots.size === 0) {
      if (node2.stopWatch) node2.stopWatch();
      nodeSlotRegistryStore.deleteNode(nodeId);
    }
  });
  return {
    requestSlotLayoutSync: /* @__PURE__ */ __name(() => scheduleSlotLayoutSync(nodeId), "requestSlotLayoutSync")
  };
}
__name(useSlotElementTracking, "useSlotElementTracking");
const pointerHistory = /* @__PURE__ */ new Map();
const defineEnhancements = /* @__PURE__ */ __name((event, enhancement) => {
  Object.defineProperties(event, {
    canvasX: { value: enhancement.canvasX, configurable: true, writable: true },
    canvasY: { value: enhancement.canvasY, configurable: true, writable: true },
    deltaX: { value: enhancement.deltaX, configurable: true, writable: true },
    deltaY: { value: enhancement.deltaY, configurable: true, writable: true },
    safeOffsetX: {
      value: enhancement.safeOffsetX,
      configurable: true,
      writable: true
    },
    safeOffsetY: {
      value: enhancement.safeOffsetY,
      configurable: true,
      writable: true
    }
  });
}, "defineEnhancements");
const createEnhancement = /* @__PURE__ */ __name((event) => {
  const conversion = useSharedCanvasPositionConversion();
  conversion.update();
  const [canvasX, canvasY] = conversion.clientPosToCanvasPos([
    event.clientX,
    event.clientY
  ]);
  const canvas2 = useCanvasStore().getCanvas();
  const { offset, scale } = canvas2.ds;
  const [originClientX, originClientY] = conversion.canvasPosToClientPos([0, 0]);
  const left = originClientX - offset[0] * scale;
  const top = originClientY - offset[1] * scale;
  const safeOffsetX = event.clientX - left;
  const safeOffsetY = event.clientY - top;
  const previous = pointerHistory.get(event.pointerId);
  const deltaX = previous ? safeOffsetX - previous.x : 0;
  const deltaY = previous ? safeOffsetY - previous.y : 0;
  pointerHistory.set(event.pointerId, { x: safeOffsetX, y: safeOffsetY });
  return { canvasX, canvasY, deltaX, deltaY, safeOffsetX, safeOffsetY };
}, "createEnhancement");
const toCanvasPointerEvent = /* @__PURE__ */ __name((event) => {
  const enhancement = createEnhancement(event);
  defineEnhancements(event, enhancement);
  return event;
}, "toCanvasPointerEvent");
const clearCanvasPointerHistory = /* @__PURE__ */ __name((pointerId) => {
  pointerHistory.delete(pointerId);
}, "clearCanvasPointerHistory");
const adapterByGraph = /* @__PURE__ */ new WeakMap();
class LinkConnectorAdapter {
  static {
    __name(this, "LinkConnectorAdapter");
  }
  constructor(network, linkConnector) {
    this.network = network;
    this.linkConnector = linkConnector;
  }
  /**
   * The currently rendered/dragged links, typed for consumer use.
   * Prefer this over accessing `linkConnector.renderLinks` directly.
   */
  get renderLinks() {
    return this.linkConnector.renderLinks;
  }
  // Drag helpers
  /**
   * Begin dragging from an output slot.
   * @param nodeId Output node id
   * @param outputIndex Output slot index
   * @param opts Optional: moveExisting (shift), fromRerouteId
   */
  beginFromOutput(nodeId, outputIndex, opts) {
    const node2 = this.network.getNodeById(nodeId);
    const output = node2?.outputs?.[outputIndex];
    if (!node2 || !output) return;
    const fromReroute = this.network.getReroute(opts?.fromRerouteId);
    if (opts?.moveExisting) {
      this.linkConnector.moveOutputLink(this.network, output);
    } else {
      this.linkConnector.dragNewFromOutput(
        this.network,
        node2,
        output,
        fromReroute
      );
    }
  }
  /**
   * Begin dragging from an input slot.
   * @param nodeId Input node id
   * @param inputIndex Input slot index
   * @param opts Optional: moveExisting (when a link/floating exists), fromRerouteId
   */
  beginFromInput(nodeId, inputIndex, opts) {
    const node2 = this.network.getNodeById(nodeId);
    const input = node2?.inputs?.[inputIndex];
    if (!node2 || !input) return;
    const fromReroute = this.network.getReroute(opts?.fromRerouteId);
    if (opts?.moveExisting) {
      this.linkConnector.moveInputLink(this.network, input);
    } else {
      this.linkConnector.dragNewFromInput(
        this.network,
        node2,
        input,
        fromReroute
      );
    }
  }
  // Validation helpers
  isNodeValidDrop(nodeId) {
    const node2 = this.network.getNodeById(nodeId);
    if (!node2) return false;
    return this.linkConnector.isNodeValidDrop(node2);
  }
  isInputValidDrop(nodeId, inputIndex) {
    const node2 = this.network.getNodeById(nodeId);
    const input = node2?.inputs?.[inputIndex];
    if (!node2 || !input) return false;
    return this.linkConnector.isInputValidDrop(node2, input);
  }
  isOutputValidDrop(nodeId, outputIndex) {
    const node2 = this.network.getNodeById(nodeId);
    const output = node2?.outputs?.[outputIndex];
    if (!node2 || !output) return false;
    return this.linkConnector.renderLinks.some(
      (link) => link.canConnectToOutput(node2, output)
    );
  }
  isRerouteValidDrop(rerouteId) {
    const reroute = this.network.getReroute(rerouteId);
    if (!reroute) return false;
    return this.linkConnector.isRerouteValidDrop(reroute);
  }
  // Drop/cancel helpers for future flows
  /** Disconnects moving links (drop on canvas/no target). */
  disconnectMovingLinks() {
    this.linkConnector.disconnectLinks();
  }
  /** Drops moving links onto the canvas (no target). */
  dropOnCanvas(event) {
    if (isSubgraph(this.network)) {
      const { canvasX, canvasY } = event;
      const ioNode = this.network.getIoNodeOnPos?.(canvasX, canvasY);
      if (ioNode) {
        this.linkConnector.dropOnIoNode(ioNode, event);
        return;
      }
    }
    this.linkConnector.dropOnNothing(event);
  }
  /** Resets connector state and clears any temporary flags. */
  reset() {
    this.linkConnector.reset();
  }
}
function createLinkConnectorAdapter() {
  const graph = app$1.canvas?.graph;
  const connector = app$1.canvas?.linkConnector;
  if (!graph || !connector) return null;
  const adapter = adapterByGraph.get(graph);
  if (adapter && adapter.linkConnector === connector) {
    return adapter;
  }
  const newAdapter = new LinkConnectorAdapter(graph, connector);
  adapterByGraph.set(graph, newAdapter);
  return newAdapter;
}
__name(createLinkConnectorAdapter, "createLinkConnectorAdapter");
const resolveSlotTargetCandidate = /* @__PURE__ */ __name((target, { adapter, graph }) => {
  const { state: dragState, setCompatibleForKey } = useSlotLinkDragUIState();
  if (!(target instanceof HTMLElement)) return null;
  const elWithKey = target.closest(".lg-slot, .lg-node-widget")?.querySelector("[data-slot-key]");
  const key = elWithKey?.dataset["slotKey"];
  if (!key) return null;
  const layout = layoutStore.getSlotLayout(key);
  if (!layout) return null;
  const candidate = { layout, compatible: false };
  if (adapter && graph) {
    const cached = dragState.compatible.get(key);
    if (cached != null) {
      candidate.compatible = cached;
    } else {
      const nodeId = layout.nodeId;
      const compatible = layout.type === "input" ? adapter.isInputValidDrop(nodeId, layout.index) : adapter.isOutputValidDrop(nodeId, layout.index);
      setCompatibleForKey(key, compatible);
      candidate.compatible = compatible;
    }
  }
  return candidate;
}, "resolveSlotTargetCandidate");
const resolveNodeSurfaceSlotCandidate = /* @__PURE__ */ __name((target, { adapter, graph, session }) => {
  const { setCompatibleForKey } = useSlotLinkDragUIState();
  if (!(target instanceof HTMLElement)) return null;
  const elWithNode = target.closest("[data-node-id]");
  const nodeIdAttr = elWithNode?.dataset["nodeId"];
  if (!nodeIdAttr) return null;
  if (!adapter || !graph) return null;
  const nodeId = nodeIdAttr;
  const cachedPreferredSlotForNode = session.preferredSlotForNode.get(nodeId);
  if (cachedPreferredSlotForNode !== void 0) {
    return cachedPreferredSlotForNode ? { layout: cachedPreferredSlotForNode.layout, compatible: true } : null;
  }
  const node2 = graph.getNodeById(nodeId);
  if (!node2) return null;
  const firstLink = adapter.renderLinks[0];
  if (!firstLink) return null;
  const connectingTo = adapter.linkConnector.state.connectingTo;
  if (connectingTo !== "input" && connectingTo !== "output") return null;
  const isInput = connectingTo === "input";
  const slotType = firstLink.fromSlot.type;
  const result = isInput ? node2.findInputByType(slotType) : node2.findOutputByType(slotType);
  const index2 = result?.index;
  if (index2 == null) {
    session.preferredSlotForNode.set(nodeId, null);
    return null;
  }
  const key = getSlotKey(String(nodeId), index2, isInput);
  const layout = layoutStore.getSlotLayout(key);
  if (!layout) {
    session.preferredSlotForNode.set(nodeId, null);
    return null;
  }
  const compatible = isInput ? adapter.isInputValidDrop(nodeId, index2) : adapter.isOutputValidDrop(nodeId, index2);
  setCompatibleForKey(key, compatible);
  if (!compatible) {
    session.preferredSlotForNode.set(nodeId, null);
    return null;
  }
  const preferred = { index: index2, key, layout };
  session.preferredSlotForNode.set(nodeId, preferred);
  return { layout, compatible: true };
}, "resolveNodeSurfaceSlotCandidate");
function createSlotLinkDragContext() {
  const state2 = {
    preferredSlotForNode: /* @__PURE__ */ new Map(),
    lastHoverSlotKey: null,
    lastHoverNodeId: null,
    lastCandidateKey: null,
    pendingPointerMove: null,
    lastPointerEventTarget: null,
    lastPointerTargetSlotKey: null,
    lastPointerTargetNodeId: null,
    reset: /* @__PURE__ */ __name(() => {
      state2.preferredSlotForNode = /* @__PURE__ */ new Map();
      state2.lastHoverSlotKey = null;
      state2.lastHoverNodeId = null;
      state2.lastCandidateKey = null;
      state2.pendingPointerMove = null;
      state2.lastPointerEventTarget = null;
      state2.lastPointerTargetSlotKey = null;
      state2.lastPointerTargetNodeId = null;
    }, "reset"),
    dispose: /* @__PURE__ */ __name(() => {
      state2.reset();
    }, "dispose")
  };
  return state2;
}
__name(createSlotLinkDragContext, "createSlotLinkDragContext");
function augmentToCanvasPointerEvent(e2, node2, canvas2) {
  canvas2.adjustMouseEvent(e2);
  canvas2.graph_mouse[0] = e2.offsetX + node2.pos[0];
  canvas2.graph_mouse[1] = e2.offsetY + node2.pos[1];
}
__name(augmentToCanvasPointerEvent, "augmentToCanvasPointerEvent");
function createPointerSession() {
  let pointerId = null;
  let stops = [];
  const begin = /* @__PURE__ */ __name((id2) => {
    pointerId = id2;
  }, "begin");
  const register = /* @__PURE__ */ __name((...newStops) => {
    for (const stop of newStops) {
      if (typeof stop === "function") {
        stops.push(stop);
      }
    }
  }, "register");
  const matches = /* @__PURE__ */ __name((event) => pointerId !== null && event.pointerId === pointerId, "matches");
  const isActive = /* @__PURE__ */ __name(() => pointerId !== null, "isActive");
  const clear = /* @__PURE__ */ __name(() => {
    for (const stop of stops) {
      stop();
    }
    stops = [];
    pointerId = null;
  }, "clear");
  return { begin, register, matches, isActive, clear };
}
__name(createPointerSession, "createPointerSession");
function resolvePointerTarget(clientX, clientY, fallback) {
  return document.elementFromPoint(clientX, clientY) ?? fallback;
}
__name(resolvePointerTarget, "resolvePointerTarget");
function useSlotLinkInteraction({
  nodeId,
  index: index2,
  type
}) {
  const {
    state: state2,
    beginDrag: beginDrag2,
    endDrag: endDrag2,
    updatePointerPosition: updatePointerPosition2,
    setCandidate: setCandidate2,
    setCompatibleForKey,
    clearCompatible
  } = useSlotLinkDragUIState();
  const conversion = useSharedCanvasPositionConversion();
  const pointerSession = createPointerSession();
  let activeAdapter = null;
  const dragContext = createSlotLinkDragContext();
  const resolveRenderLinkSource = /* @__PURE__ */ __name((link) => {
    if (link.fromReroute) {
      const rerouteLayout = layoutStore.getRerouteLayout(link.fromReroute.id);
      if (rerouteLayout) return rerouteLayout.position;
      const [x2, y] = link.fromReroute.pos;
      return toPoint(x2, y);
    }
    const nodeId2 = link.node.id;
    if (nodeId2 != null) {
      const isInputFrom = link.toType === "output";
      const key = getSlotKey(String(nodeId2), link.fromSlotIndex, isInputFrom);
      const layout = layoutStore.getSlotLayout(key);
      if (layout) return layout.position;
    }
    const pos = link.fromPos;
    return toPoint(pos[0], pos[1]);
  }, "resolveRenderLinkSource");
  const syncRenderLinkOrigins = /* @__PURE__ */ __name(() => {
    if (!activeAdapter) return;
    for (const link of activeAdapter.renderLinks) {
      const origin = resolveRenderLinkSource(link);
      if (!origin) continue;
      const x2 = origin.x;
      const y = origin.y;
      if (link.fromPos[0] !== x2 || link.fromPos[1] !== y) {
        link.fromPos[0] = x2;
        link.fromPos[1] = y;
      }
    }
  }, "syncRenderLinkOrigins");
  function hasCanConnectToReroute(link) {
    return "canConnectToReroute" in link;
  }
  __name(hasCanConnectToReroute, "hasCanConnectToReroute");
  const isToInputLink = /* @__PURE__ */ __name((link) => link.toType === "input", "isToInputLink");
  const isToOutputLink = /* @__PURE__ */ __name((link) => link.toType === "output", "isToOutputLink");
  function connectLinksToInput(links, node2, inputSlot) {
    const validCandidates = links.filter(isToInputLink).filter((link) => link.canConnectToInput(node2, inputSlot));
    for (const link of validCandidates) {
      link.connectToInput(node2, inputSlot, activeAdapter?.linkConnector.events);
    }
    return validCandidates.length > 0;
  }
  __name(connectLinksToInput, "connectLinksToInput");
  function connectLinksToOutput(links, node2, outputSlot) {
    const validCandidates = links.filter(isToOutputLink).filter((link) => link.canConnectToOutput(node2, outputSlot));
    for (const link of validCandidates) {
      link.connectToOutput(
        node2,
        outputSlot,
        activeAdapter?.linkConnector.events
      );
    }
    return validCandidates.length > 0;
  }
  __name(connectLinksToOutput, "connectLinksToOutput");
  const resolveLinkOrigin = /* @__PURE__ */ __name((link) => {
    if (!link) return null;
    const slotKey = getSlotKey(String(link.origin_id), link.origin_slot, false);
    const layout = layoutStore.getSlotLayout(slotKey);
    if (!layout) return null;
    return { position: { ...layout.position }, direction: LinkDirection.NONE };
  }, "resolveLinkOrigin");
  const resolveExistingInputLinkAnchor = /* @__PURE__ */ __name((graph, inputSlot) => {
    if (!inputSlot) return null;
    const directLink = graph.getLink(inputSlot.link);
    if (directLink) {
      const reroutes = LLink.getReroutes(graph, directLink);
      const lastReroute = reroutes.at(-1);
      if (lastReroute) {
        const rerouteLayout = layoutStore.getRerouteLayout(lastReroute.id);
        if (rerouteLayout) {
          return {
            position: { ...rerouteLayout.position },
            direction: LinkDirection.NONE
          };
        }
        const pos = lastReroute.pos;
        if (pos) {
          return {
            position: toPoint(pos[0], pos[1]),
            direction: LinkDirection.NONE
          };
        }
      }
      const directAnchor = resolveLinkOrigin(directLink);
      if (directAnchor) return directAnchor;
    }
    const floatingLinkIterator = inputSlot._floatingLinks?.values();
    const floatingLink = floatingLinkIterator ? floatingLinkIterator.next().value : void 0;
    if (!floatingLink) return null;
    if (floatingLink.parentId != null) {
      const rerouteLayout = layoutStore.getRerouteLayout(floatingLink.parentId);
      if (rerouteLayout) {
        return {
          position: { ...rerouteLayout.position },
          direction: LinkDirection.NONE
        };
      }
      const reroute = graph.getReroute(floatingLink.parentId);
      if (reroute) {
        return {
          position: toPoint(reroute.pos[0], reroute.pos[1]),
          direction: LinkDirection.NONE
        };
      }
    }
    return null;
  }, "resolveExistingInputLinkAnchor");
  const cleanupInteraction = /* @__PURE__ */ __name(() => {
    if (state2.pointerId != null) {
      clearCanvasPointerHistory(state2.pointerId);
    }
    activeAdapter?.reset();
    pointerSession.clear();
    endDrag2();
    activeAdapter = null;
    raf2.cancel();
    dragContext.dispose();
    clearCompatible();
  }, "cleanupInteraction");
  const updatePointerState = /* @__PURE__ */ __name((event) => {
    const clientX = event.clientX;
    const clientY = event.clientY;
    const [canvasX, canvasY] = conversion.clientPosToCanvasPos([
      clientX,
      clientY
    ]);
    updatePointerPosition2(clientX, clientY, canvasX, canvasY);
  }, "updatePointerState");
  const processPointerMoveFrame = /* @__PURE__ */ __name(() => {
    const data = dragContext.pendingPointerMove;
    if (!data) return;
    dragContext.pendingPointerMove = null;
    const [canvasX, canvasY] = conversion.clientPosToCanvasPos([
      data.clientX,
      data.clientY
    ]);
    updatePointerPosition2(data.clientX, data.clientY, canvasX, canvasY);
    syncRenderLinkOrigins();
    let hoveredSlotKey = null;
    let hoveredNodeId = null;
    const target = resolvePointerTarget(data.clientX, data.clientY, data.target);
    if (target === dragContext.lastPointerEventTarget) {
      hoveredSlotKey = dragContext.lastPointerTargetSlotKey;
      hoveredNodeId = dragContext.lastPointerTargetNodeId;
    } else if (target instanceof HTMLElement) {
      const elWithSlot = target.closest(".lg-slot, .lg-node-widget")?.querySelector("[data-slot-key]");
      const elWithNode = target.closest("[data-node-id]");
      hoveredSlotKey = elWithSlot?.dataset["slotKey"] ?? null;
      hoveredNodeId = elWithNode?.dataset["nodeId"] ?? null;
      dragContext.lastPointerEventTarget = target;
      dragContext.lastPointerTargetSlotKey = hoveredSlotKey;
      dragContext.lastPointerTargetNodeId = hoveredNodeId;
    }
    const hoverChanged = hoveredSlotKey !== dragContext.lastHoverSlotKey || hoveredNodeId !== dragContext.lastHoverNodeId;
    let candidate = state2.candidate;
    if (hoverChanged) {
      const adapter = activeAdapter;
      const graph = app$1.canvas?.graph ?? null;
      const context = { adapter, graph, session: dragContext };
      const slotCandidate = resolveSlotTargetCandidate(target, context);
      const nodeCandidate = resolveNodeSurfaceSlotCandidate(target, context);
      candidate = slotCandidate?.compatible ? slotCandidate : nodeCandidate;
      dragContext.lastHoverSlotKey = hoveredSlotKey;
      dragContext.lastHoverNodeId = hoveredNodeId;
      if (slotCandidate) {
        const key = getSlotKey(
          slotCandidate.layout.nodeId,
          slotCandidate.layout.index,
          slotCandidate.layout.type === "input"
        );
        setCompatibleForKey(key, !!slotCandidate.compatible);
      }
      if (nodeCandidate && !slotCandidate?.compatible) {
        const key = getSlotKey(
          nodeCandidate.layout.nodeId,
          nodeCandidate.layout.index,
          nodeCandidate.layout.type === "input"
        );
        setCompatibleForKey(key, !!nodeCandidate.compatible);
      }
    }
    const newCandidate = candidate?.compatible ? candidate : null;
    const newCandidateKey = newCandidate ? getSlotKey(
      newCandidate.layout.nodeId,
      newCandidate.layout.index,
      newCandidate.layout.type === "input"
    ) : null;
    const candidateChanged = newCandidateKey !== dragContext.lastCandidateKey;
    if (candidateChanged) {
      setCandidate2(newCandidate);
      dragContext.lastCandidateKey = newCandidateKey;
    }
    let snapPosChanged = false;
    if (activeAdapter) {
      const snapX = newCandidate ? newCandidate.layout.position.x : state2.pointer.canvas.x;
      const snapY = newCandidate ? newCandidate.layout.position.y : state2.pointer.canvas.y;
      const currentSnap = activeAdapter.linkConnector.state.snapLinksPos;
      snapPosChanged = !currentSnap || currentSnap[0] !== snapX || currentSnap[1] !== snapY;
      if (snapPosChanged) {
        activeAdapter.linkConnector.state.snapLinksPos = [snapX, snapY];
      }
    }
    const shouldRedraw = candidateChanged || snapPosChanged;
    if (shouldRedraw) app$1.canvas?.setDirty(true, true);
  }, "processPointerMoveFrame");
  const raf2 = createRafBatch(processPointerMoveFrame);
  const handlePointerMove = /* @__PURE__ */ __name((event) => {
    if (!pointerSession.matches(event)) return;
    event.stopPropagation();
    dragContext.pendingPointerMove = {
      clientX: event.clientX,
      clientY: event.clientY,
      target: event.target
    };
    raf2.schedule();
  }, "handlePointerMove");
  const tryConnectToCandidate = /* @__PURE__ */ __name((candidate) => {
    if (!candidate?.compatible) return false;
    const graph = app$1.canvas?.graph;
    const adapter = activeAdapter;
    if (!graph || !adapter) return false;
    const nodeId2 = candidate.layout.nodeId;
    const targetNode = graph.getNodeById(nodeId2);
    if (!targetNode) return false;
    if (candidate.layout.type === "input") {
      const inputSlot = targetNode.inputs?.[candidate.layout.index];
      return !!inputSlot && connectLinksToInput(adapter.renderLinks, targetNode, inputSlot);
    }
    if (candidate.layout.type === "output") {
      const outputSlot = targetNode.outputs?.[candidate.layout.index];
      return !!outputSlot && connectLinksToOutput(adapter.renderLinks, targetNode, outputSlot);
    }
    return false;
  }, "tryConnectToCandidate");
  const tryConnectViaRerouteAtPointer = /* @__PURE__ */ __name(() => {
    const rerouteLayout = layoutStore.queryRerouteAtPoint({
      x: state2.pointer.canvas.x,
      y: state2.pointer.canvas.y
    });
    const graph = app$1.canvas?.graph;
    const adapter = activeAdapter;
    if (!rerouteLayout || !graph || !adapter) return false;
    const reroute = graph.getReroute(rerouteLayout.id);
    if (!reroute || !adapter.isRerouteValidDrop(reroute.id)) return false;
    let didConnect = false;
    const results = reroute.findTargetInputs() ?? [];
    const maybeReroutes = reroute.getReroutes();
    if (results.length && maybeReroutes !== null) {
      const originalReroutes = maybeReroutes.slice(0, -1).reverse();
      for (const link of adapter.renderLinks) {
        if (!isToInputLink(link)) continue;
        for (const result of results) {
          link.connectToRerouteInput(
            reroute,
            result,
            adapter.linkConnector.events,
            originalReroutes
          );
          didConnect = true;
        }
      }
    }
    const sourceOutput = reroute.findSourceOutput();
    if (sourceOutput) {
      const { node: node2, output } = sourceOutput;
      for (const link of adapter.renderLinks) {
        if (!isToOutputLink(link)) continue;
        if (hasCanConnectToReroute(link) && !link.canConnectToReroute(reroute))
          continue;
        link.connectToRerouteOutput(
          reroute,
          node2,
          output,
          adapter.linkConnector.events
        );
        didConnect = true;
      }
    }
    return didConnect;
  }, "tryConnectViaRerouteAtPointer");
  const finishInteraction = /* @__PURE__ */ __name((event) => {
    if (!pointerSession.matches(event)) return;
    const canvasEvent = toCanvasPointerEvent(event);
    canvasEvent.preventDefault();
    raf2.flush();
    raf2.flush();
    if (!state2.source) {
      cleanupInteraction();
      app$1.canvas?.setDirty(true, true);
      return;
    }
    const snappedCandidate = state2.candidate?.compatible ? state2.candidate : null;
    const dropTarget = resolvePointerTarget(
      event.clientX,
      event.clientY,
      canvasEvent.target
    );
    const hasConnected = connectByPriority(dropTarget, snappedCandidate);
    if (!hasConnected && dropTarget === app$1.canvas?.canvas) {
      activeAdapter?.dropOnCanvas(canvasEvent);
    }
    cleanupInteraction();
    app$1.canvas?.setDirty(true, true);
  }, "finishInteraction");
  const handlePointerUp2 = /* @__PURE__ */ __name((event) => {
    event.stopPropagation();
    finishInteraction(event);
  }, "handlePointerUp");
  const handlePointerCancel = /* @__PURE__ */ __name((event) => {
    if (!pointerSession.matches(event)) return;
    raf2.flush();
    toCanvasPointerEvent(event);
    cleanupInteraction();
    app$1.canvas?.setDirty(true, true);
  }, "handlePointerCancel");
  function connectByPriority(target, snappedCandidate) {
    const adapter = activeAdapter;
    const graph = app$1.canvas?.graph ?? null;
    const context = { adapter, graph, session: dragContext };
    const attemptSnapped = /* @__PURE__ */ __name(() => tryConnectToCandidate(snappedCandidate), "attemptSnapped");
    const domSlotCandidate = resolveSlotTargetCandidate(target, context);
    const attemptDomSlot = /* @__PURE__ */ __name(() => tryConnectToCandidate(domSlotCandidate), "attemptDomSlot");
    const nodeSurfaceSlotCandidate = resolveNodeSurfaceSlotCandidate(
      target,
      context
    );
    const attemptNodeSurface = /* @__PURE__ */ __name(() => tryConnectToCandidate(nodeSurfaceSlotCandidate), "attemptNodeSurface");
    const attemptReroute = /* @__PURE__ */ __name(() => tryConnectViaRerouteAtPointer(), "attemptReroute");
    if (attemptSnapped()) return true;
    if (attemptDomSlot()) return true;
    if (attemptNodeSurface()) return true;
    if (attemptReroute()) return true;
    return false;
  }
  __name(connectByPriority, "connectByPriority");
  const onPointerDown = /* @__PURE__ */ __name((event) => {
    if (event.button !== 0) return;
    if (!nodeId) return;
    if (pointerSession.isActive()) return;
    event.preventDefault();
    event.stopPropagation();
    const canvas2 = app$1.canvas;
    const graph = canvas2?.graph;
    if (!canvas2 || !graph) return;
    activeAdapter = createLinkConnectorAdapter();
    if (!activeAdapter) return;
    raf2.cancel();
    dragContext.reset();
    const layout = layoutStore.getSlotLayout(
      getSlotKey(nodeId, index2, type === "input")
    );
    if (!layout) return;
    const localNodeId = nodeId;
    const isInputSlot = type === "input";
    const isOutputSlot = type === "output";
    const resolvedNode = graph.getNodeById(localNodeId);
    const inputSlot = isInputSlot ? resolvedNode?.inputs?.[index2] : void 0;
    const outputSlot = isOutputSlot ? resolvedNode?.outputs?.[index2] : void 0;
    const ctrlOrMeta = event.ctrlKey || event.metaKey;
    const inputLinkId = inputSlot?.link ?? null;
    const inputFloatingCount = inputSlot?._floatingLinks?.size ?? 0;
    const hasExistingInputLink = inputLinkId != null || inputFloatingCount > 0;
    const outputLinkCount = outputSlot?.links?.length ?? 0;
    const outputFloatingCount = outputSlot?._floatingLinks?.size ?? 0;
    const hasExistingOutputLink = outputLinkCount > 0 || outputFloatingCount > 0;
    const shouldBreakExistingInputLink = isInputSlot && hasExistingInputLink && ctrlOrMeta && event.altKey && !event.shiftKey;
    const shouldBatchDisconnectOutputLinks = isOutputSlot && hasExistingOutputLink && ctrlOrMeta && event.altKey && !event.shiftKey;
    const existingInputLink = isInputSlot && inputLinkId != null ? graph.getLink(inputLinkId) : void 0;
    if (shouldBreakExistingInputLink && resolvedNode) {
      resolvedNode.disconnectInput(index2, true);
    }
    if (shouldBatchDisconnectOutputLinks && resolvedNode) {
      resolvedNode.disconnectOutput(index2);
      canvas2.setDirty(true, true);
      event.preventDefault();
      event.stopPropagation();
      return;
    }
    const baseDirection = isInputSlot ? inputSlot?.dir ?? LinkDirection.LEFT : outputSlot?.dir ?? LinkDirection.RIGHT;
    const existingAnchor = isInputSlot && !shouldBreakExistingInputLink ? resolveExistingInputLinkAnchor(graph, inputSlot) : null;
    const shouldMoveExistingOutput = isOutputSlot && event.shiftKey && hasExistingOutputLink;
    const shouldMoveExistingInput = isInputSlot && !shouldBreakExistingInputLink && hasExistingInputLink;
    if (isOutputSlot) {
      activeAdapter.beginFromOutput(localNodeId, index2, {
        moveExisting: shouldMoveExistingOutput
      });
    } else {
      activeAdapter.beginFromInput(localNodeId, index2, {
        moveExisting: shouldMoveExistingInput
      });
    }
    if (shouldMoveExistingInput && existingInputLink) {
      existingInputLink._dragging = true;
    }
    syncRenderLinkOrigins();
    const direction = existingAnchor?.direction ?? baseDirection;
    const startPosition = existingAnchor?.position ?? {
      x: layout.position.x,
      y: layout.position.y
    };
    beginDrag2(
      {
        nodeId,
        slotIndex: index2,
        type,
        direction,
        position: startPosition,
        linkId: !shouldBreakExistingInputLink ? existingInputLink?.id : void 0,
        movingExistingOutput: shouldMoveExistingOutput
      },
      event.pointerId
    );
    pointerSession.begin(event.pointerId);
    toCanvasPointerEvent(event);
    updatePointerState(event);
    activeAdapter.linkConnector.state.snapLinksPos = [
      state2.pointer.canvas.x,
      state2.pointer.canvas.y
    ];
    pointerSession.register(
      useEventListener("pointermove", handlePointerMove, {
        capture: true
      }),
      useEventListener("pointerup", handlePointerUp2, {
        capture: true
      }),
      useEventListener("pointercancel", handlePointerCancel, {
        capture: true
      })
    );
    const targetType = type === "input" ? "output" : "input";
    const allKeys = layoutStore.getAllSlotKeys();
    clearCompatible();
    for (const key of allKeys) {
      const slotLayout = layoutStore.getSlotLayout(key);
      if (!slotLayout) continue;
      if (slotLayout.type !== targetType) continue;
      const idx = slotLayout.index;
      const ok = targetType === "input" ? activeAdapter.isInputValidDrop(slotLayout.nodeId, idx) : activeAdapter.isOutputValidDrop(slotLayout.nodeId, idx);
      setCompatibleForKey(key, ok);
    }
    canvas2.setDirty(true, true);
  }, "onPointerDown");
  tryOnScopeDispose(() => {
    if (pointerSession.isActive()) {
      cleanupInteraction();
    }
  });
  function onDoubleClick(e2) {
    const { graph } = app$1.canvas;
    if (!graph) return;
    const node2 = graph.getNodeById(nodeId);
    if (!node2) return;
    augmentToCanvasPointerEvent(e2, node2, app$1.canvas);
    node2.onInputDblClick?.(index2, e2);
  }
  __name(onDoubleClick, "onDoubleClick");
  function onClick(e2) {
    const { graph } = app$1.canvas;
    if (!graph) return;
    const node2 = graph.getNodeById(nodeId);
    if (!node2) return;
    augmentToCanvasPointerEvent(e2, node2, app$1.canvas);
    node2.onInputClick?.(index2, e2);
  }
  __name(onClick, "onClick");
  return {
    onClick,
    onDoubleClick,
    onPointerDown
  };
}
__name(useSlotLinkInteraction, "useSlotLinkInteraction");
const _hoisted_1$1u = {
  width: "1.2em",
  height: "1.2em",
  viewBox: "0 0 100 100",
  xmlns: "http://www.w3.org/2000/svg"
};
function render$2(_ctx, _cache) {
  return openBlock(), createElementBlock("svg", _hoisted_1$1u, [..._cache[0] || (_cache[0] = [
    createStaticVNode('<defs><clipPath id="hollow"><path d="M-50 50\n       A100 100 0 0 1 150 50\n       A100 100 0 0 1 -50 50\n       M30 50\n       A20 20 0 0 0 70 50\n       A20 20 0 0 0 30 50"></path></clipPath></defs><g clip-path="var(--shape)" stroke-width="4"><path d="M50 0A50 50 0 0 1 93 75L50 50" fill="var(--type1, red)"></path><path d="M93 75A50 50 0 0 1 7 75L50 50" fill="var(--type2, blue)"></path><path d="M7 75A50 50 0 0 1 50 0L50 50" fill="var(--type3, green)"></path><path d="M50 50L50 0M50 50L93 75M50 50L7 75" stroke="var(--inner-stroke, black)"></path><path d="M50 2A48 48 0 0 1 50 98A48 48 0 0 1 50 2" fill="transparent" stroke="var(--outer-stroke, transparent)"></path></g>', 2)
  ])]);
}
__name(render$2, "render$2");
const __unplugin_components_1 = markRaw({ name: "comfy-node-slot3", render: render$2 });
const _hoisted_1$1t = {
  width: "1.2em",
  height: "1.2em",
  viewBox: "0 0 100 100",
  xmlns: "http://www.w3.org/2000/svg"
};
function render$1(_ctx, _cache) {
  return openBlock(), createElementBlock("svg", _hoisted_1$1t, [..._cache[0] || (_cache[0] = [
    createStaticVNode('<defs><clipPath id="hollow"><path d="M -50 50\n       A 100 100, 0, 0, 1, 150 50\n       A 100 100, 0, 0, 1, -50 50\n       M 30 50\n       A 20 20, 0, 0, 0, 70 50\n       A 20 20, 0, 0, 0, 30 50"></path></clipPath></defs><g clip-path="var(--shape)" stroke-width="4"><path d="M 50 0 A 50 50, 0, 0, 1, 50 100" fill="var(--type1, red)"></path><path d="M 50 100 A 50 50, 0, 0, 1, 50 0" fill="var(--type2, blue)"></path><path d="M50 0L50 100" stroke="var(--inner-stroke, black)"></path><path d="M50 2A48 48 0 0 1 50 98A48 48 0 0 1 50 2" fill="transparent" stroke="var(--outer-stroke, transparent)"></path></g>', 2)
  ])]);
}
__name(render$1, "render$1");
const __unplugin_components_0$1 = markRaw({ name: "comfy-node-slot2", render: render$1 });
function getSlotColor(type) {
  if (!type) return "#AAA";
  const typeStr = String(type).toUpperCase();
  return `var(--color-datatype-${typeStr}, #AAA)`;
}
__name(getSlotColor, "getSlotColor");
const _sfc_main$1M = /* @__PURE__ */ defineComponent({
  __name: "SlotConnectionDot",
  props: {
    slotData: {},
    class: { type: [Array, Object, String, Number, null, Boolean] },
    hasError: { type: Boolean },
    multi: { type: Boolean }
  },
  setup(__props, { expose: __expose }) {
    const props = __props;
    const slotElRef = useTemplateRef("slot-el");
    function getTypes() {
      if (props.hasError) return ["var(--color-error)"];
      if (!props.slotData) return [getSlotColor()];
      const typesSet = new Set(
        `${props.slotData.type}`.split(",").map(getSlotColor)
      );
      return [...typesSet].slice(0, 3);
    }
    __name(getTypes, "getTypes");
    const types = getTypes();
    __expose({
      slotElRef
    });
    const slotClass = computed(
      () => cn(
        "bg-slate-300 rounded-full slot-dot",
        "transition-all duration-150",
        "border border-solid border-node-component-slot-dot-outline",
        props.multi ? "w-3 h-6" : "size-3 cursor-crosshair group-hover/slot:[--node-component-slot-dot-outline-opacity-mult:5] group-hover/slot:scale-125"
      )
    );
    return (_ctx, _cache) => {
      const _component_i_comfy58node_slot2 = __unplugin_components_0$1;
      const _component_i_comfy58node_slot3 = __unplugin_components_1;
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(
          unref(cn)(
            "after:absolute after:inset-y-0 after:w-5/2 relative size-6 flex items-center justify-center group/slot",
            props.class
          )
        )
      }, [
        unref(types).length === 1 ? (openBlock(), createElementBlock("div", {
          key: 0,
          ref: "slot-el",
          style: normalizeStyle({ backgroundColor: unref(types)[0] }),
          class: normalizeClass(slotClass.value)
        }, null, 6)) : (openBlock(), createElementBlock("div", {
          key: 1,
          ref: "slot-el",
          style: normalizeStyle({
            "--type1": unref(types)[0],
            "--type2": unref(types)[1],
            "--type3": unref(types)[2]
          }),
          class: normalizeClass(slotClass.value)
        }, [
          unref(types).length === 2 ? (openBlock(), createBlock(_component_i_comfy58node_slot2, {
            key: 0,
            class: "size-full -translate-y-1/2"
          })) : (openBlock(), createBlock(_component_i_comfy58node_slot3, {
            key: 1,
            class: "size-full -translate-y-1/2"
          }))
        ], 6))
      ], 2);
    };
  }
});
const _hoisted_1$1s = {
  key: 0,
  class: "node-error p-1 text-xs text-red-500"
};
const _hoisted_2$1b = { class: "h-full flex items-center min-w-0" };
const _sfc_main$1L = /* @__PURE__ */ defineComponent({
  __name: "InputSlot",
  props: {
    nodeType: {},
    nodeId: {},
    slotData: {},
    index: {},
    connected: { type: Boolean },
    compatible: { type: Boolean },
    dotOnly: { type: Boolean },
    socketless: { type: Boolean }
  },
  setup(__props) {
    const props = __props;
    const executionStore = useExecutionStore();
    const hasSlotError = computed(() => {
      const nodeErrors2 = executionStore.lastNodeErrors?.[props.nodeId ?? ""];
      if (!nodeErrors2) return false;
      const slotName = props.slotData.name;
      return nodeErrors2.errors.some(
        (error) => error.extra_info?.input_name === slotName
      );
    });
    const renderError = ref(null);
    const { toastErrorHandler } = useErrorHandling();
    const { getInputSlotTooltip, createTooltipConfig } = useNodeTooltips(
      props.nodeType || ""
    );
    const tooltipConfig = computed(() => {
      const slotName = props.slotData.localized_name || props.slotData.name || "";
      const tooltipText = getInputSlotTooltip(slotName);
      const fallbackText = tooltipText || `Input: ${slotName}`;
      return createTooltipConfig(fallbackText);
    });
    onErrorCaptured((error) => {
      renderError.value = error.message;
      toastErrorHandler(error);
      return false;
    });
    const { state: dragState } = useSlotLinkDragUIState();
    const slotKey = computed(
      () => getSlotKey(props.nodeId ?? "", props.index, true)
    );
    const shouldDim = computed(() => {
      if (!dragState.active) return false;
      return !dragState.compatible.get(slotKey.value);
    });
    const connectionDotRef = ref(null);
    const slotElRef = ref(null);
    watchEffect(() => {
      const el = connectionDotRef.value?.slotElRef;
      slotElRef.value = el || null;
    });
    useSlotElementTracking({
      nodeId: props.nodeId ?? "",
      index: props.index,
      type: "input",
      element: slotElRef
    });
    const { onClick, onDoubleClick, onPointerDown } = useSlotLinkInteraction({
      nodeId: props.nodeId ?? "",
      index: props.index,
      type: "input"
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return renderError.value ? (openBlock(), createElementBlock("div", _hoisted_1$1s, "⚠️")) : withDirectives((openBlock(), createElementBlock("div", {
        key: 1,
        class: normalizeClass(
          unref(cn)(
            "lg-slot lg-slot--input flex items-center group rounded-r-lg m-0",
            "cursor-crosshair",
            props.dotOnly ? "lg-slot--dot-only" : "pr-6",
            {
              "lg-slot--connected": props.connected,
              "lg-slot--compatible": props.compatible,
              "opacity-40": shouldDim.value
            },
            props.socketless && "pointer-events-none invisible"
          )
        )
      }, [
        createVNode(_sfc_main$1M, {
          ref_key: "connectionDotRef",
          ref: connectionDotRef,
          class: normalizeClass(
            unref(cn)(
              "-translate-x-1/2 w-3",
              hasSlotError.value && "ring-2 ring-error ring-offset-0 rounded-full"
            )
          ),
          "slot-data": _ctx.slotData,
          onClick: unref(onClick),
          onDblclick: unref(onDoubleClick),
          onPointerdown: unref(onPointerDown)
        }, null, 8, ["class", "slot-data", "onClick", "onDblclick", "onPointerdown"]),
        createBaseVNode("div", _hoisted_2$1b, [
          !_ctx.dotOnly ? (openBlock(), createElementBlock("span", {
            key: 0,
            class: normalizeClass(
              unref(cn)(
                "truncate text-node-component-slot-text",
                hasSlotError.value && "text-error font-medium"
              )
            )
          }, toDisplayString(_ctx.slotData.label || _ctx.slotData.localized_name || _ctx.slotData.name || `Input ${_ctx.index}`), 3)) : createCommentVNode("", true)
        ])
      ], 2)), [
        [
          _directive_tooltip,
          tooltipConfig.value,
          void 0,
          { left: true }
        ]
      ]);
    };
  }
});
const _hoisted_1$1r = {
  key: 0,
  class: "node-error p-1 text-xs text-red-500"
};
const _hoisted_2$1a = { class: "relative h-full flex items-center min-w-0" };
const _hoisted_3$O = {
  key: 0,
  class: "truncate text-node-component-slot-text"
};
const _sfc_main$1K = /* @__PURE__ */ defineComponent({
  __name: "OutputSlot",
  props: {
    nodeType: {},
    nodeId: {},
    slotData: {},
    index: {},
    connected: { type: Boolean },
    compatible: { type: Boolean },
    dotOnly: { type: Boolean }
  },
  setup(__props) {
    const props = __props;
    const renderError = ref(null);
    const { toastErrorHandler } = useErrorHandling();
    const { getOutputSlotTooltip, createTooltipConfig } = useNodeTooltips(
      props.nodeType || ""
    );
    const tooltipConfig = computed(() => {
      const slotName = props.slotData.name || "";
      const tooltipText = getOutputSlotTooltip(props.index);
      const fallbackText = tooltipText || `Output: ${slotName}`;
      return createTooltipConfig(fallbackText);
    });
    onErrorCaptured((error) => {
      renderError.value = error.message;
      toastErrorHandler(error);
      return false;
    });
    const { state: dragState } = useSlotLinkDragUIState();
    const slotKey = computed(
      () => getSlotKey(props.nodeId ?? "", props.index, false)
    );
    const shouldDim = computed(() => {
      if (!dragState.active) return false;
      return !dragState.compatible.get(slotKey.value);
    });
    const slotWrapperClass = computed(
      () => cn(
        "lg-slot lg-slot--output flex items-center justify-end group rounded-l-lg h-6",
        "cursor-crosshair",
        props.dotOnly ? "lg-slot--dot-only justify-center" : "pl-6",
        {
          "lg-slot--connected": props.connected,
          "lg-slot--compatible": props.compatible,
          "opacity-40": shouldDim.value
        }
      )
    );
    const connectionDotRef = ref(null);
    const slotElRef = ref(null);
    watchEffect(() => {
      const el = connectionDotRef.value?.slotElRef;
      slotElRef.value = el || null;
    });
    useSlotElementTracking({
      nodeId: props.nodeId ?? "",
      index: props.index,
      type: "output",
      element: slotElRef
    });
    const { onPointerDown } = useSlotLinkInteraction({
      nodeId: props.nodeId ?? "",
      index: props.index,
      type: "output"
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return renderError.value ? (openBlock(), createElementBlock("div", _hoisted_1$1r, "⚠️")) : withDirectives((openBlock(), createElementBlock("div", {
        key: 1,
        class: normalizeClass(slotWrapperClass.value)
      }, [
        createBaseVNode("div", _hoisted_2$1a, [
          !_ctx.dotOnly ? (openBlock(), createElementBlock("span", _hoisted_3$O, toDisplayString(_ctx.slotData.localized_name || _ctx.slotData.name || `Output ${_ctx.index}`), 1)) : createCommentVNode("", true)
        ]),
        createVNode(_sfc_main$1M, {
          ref_key: "connectionDotRef",
          ref: connectionDotRef,
          class: "w-3 translate-x-1/2",
          "slot-data": _ctx.slotData,
          onPointerdown: unref(onPointerDown)
        }, null, 8, ["slot-data", "onPointerdown"])
      ], 2)), [
        [
          _directive_tooltip,
          tooltipConfig.value,
          void 0,
          { right: true }
        ]
      ]);
    };
  }
});
const _hoisted_1$1q = {
  key: 0,
  class: "node-error p-2 text-sm text-red-500"
};
const _sfc_main$1J = /* @__PURE__ */ defineComponent({
  __name: "NodeSlots",
  props: {
    nodeData: {},
    unified: { type: Boolean, default: false }
  },
  setup(__props) {
    const linkedWidgetInputs = computed(
      () => __props.unified ? linkedWidgetedInputs(__props.nodeData) : []
    );
    const filteredInputs = computed(() => [
      ...nonWidgetedInputs(__props.nodeData),
      ...linkedWidgetInputs.value
    ]);
    const unifiedWrapperClass = computed(
      () => cn(
        __props.unified && "absolute inset-0 items-center pointer-events-none opacity-0 z-30"
      )
    );
    const unifiedDotsClass = computed(
      () => cn(
        __props.unified && "grid grid-cols-1 grid-rows-1 gap-0 [&>*]:row-span-full [&>*]:col-span-full place-items-center"
      )
    );
    const getActualInputIndex = /* @__PURE__ */ __name((input, filteredIndex) => {
      if (!__props.nodeData?.inputs) return filteredIndex;
      const actualIndex = __props.nodeData.inputs.findIndex((i) => i === input);
      return actualIndex !== -1 ? actualIndex : filteredIndex;
    }, "getActualInputIndex");
    const renderError = ref(null);
    const { toastErrorHandler } = useErrorHandling();
    onErrorCaptured((error) => {
      renderError.value = error.message;
      toastErrorHandler(error);
      return false;
    });
    return (_ctx, _cache) => {
      return renderError.value ? (openBlock(), createElementBlock("div", _hoisted_1$1q, toDisplayString(unref(st)("nodeErrors.slots", "Node Slots Error")), 1)) : (openBlock(), createElementBlock("div", {
        key: 1,
        class: normalizeClass(unref(cn)("flex justify-between min-w-0", unifiedWrapperClass.value))
      }, [
        filteredInputs.value.length ? (openBlock(), createElementBlock("div", {
          key: 0,
          class: normalizeClass(unref(cn)("flex flex-col min-w-0", unifiedDotsClass.value))
        }, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(filteredInputs.value, (input, index2) => {
            return openBlock(), createBlock(_sfc_main$1L, {
              key: `input-${index2}`,
              "slot-data": input,
              "node-type": _ctx.nodeData?.type || "",
              "node-id": _ctx.nodeData?.id != null ? String(_ctx.nodeData.id) : "",
              index: getActualInputIndex(input, index2)
            }, null, 8, ["slot-data", "node-type", "node-id", "index"]);
          }), 128))
        ], 2)) : createCommentVNode("", true),
        _ctx.nodeData?.outputs?.length ? (openBlock(), createElementBlock("div", {
          key: 1,
          class: normalizeClass(unref(cn)("ml-auto flex flex-col min-w-0", unifiedDotsClass.value))
        }, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.nodeData.outputs, (output, index2) => {
            return openBlock(), createBlock(_sfc_main$1K, {
              key: `output-${index2}`,
              "slot-data": output,
              "node-type": _ctx.nodeData?.type || "",
              "node-id": _ctx.nodeData?.id != null ? String(_ctx.nodeData.id) : "",
              index: index2
            }, null, 8, ["slot-data", "node-type", "node-id", "index"]);
          }), 128))
        ], 2)) : createCommentVNode("", true)
      ], 2));
    };
  }
});
function useNodeZIndex(options = {}) {
  const { layoutSource = LayoutSource.Vue } = options;
  const layoutMutations2 = useLayoutMutations();
  function bringNodeToFront(nodeId, source) {
    layoutMutations2.setSource(source ?? layoutSource);
    layoutMutations2.bringNodeToFront(nodeId);
  }
  __name(bringNodeToFront, "bringNodeToFront");
  return {
    bringNodeToFront
  };
}
__name(useNodeZIndex, "useNodeZIndex");
const _sfc_main$1I = /* @__PURE__ */ defineComponent({
  __name: "WidgetDOM",
  props: {
    widget: {},
    nodeId: {}
  },
  setup(__props) {
    const props = __props;
    const domEl = ref();
    const { canvas: canvas2 } = useCanvasStore();
    onMounted(() => {
      if (!domEl.value) return;
      const node2 = canvas2?.graph?.getNodeById(props.nodeId) ?? void 0;
      if (!node2) return;
      const widget = node2.widgets?.find((w) => w.name === props.widget.name);
      if (!widget || !isDOMWidget(widget)) return;
      domEl.value.replaceChildren(widget.element);
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        ref_key: "domEl",
        ref: domEl
      }, null, 512);
    };
  }
});
const scaleFactor = 2;
const _sfc_main$1H = /* @__PURE__ */ defineComponent({
  __name: "WidgetLegacy",
  props: {
    widget: {}
  },
  setup(__props) {
    const props = __props;
    const canvasEl = ref();
    const containerHeight = ref(20);
    const canvas2 = useCanvasStore().canvas;
    let node2;
    let widgetInstance;
    let pointer;
    onMounted(() => {
      node2 = canvas2?.graph?.getNodeById(
        canvasEl.value.parentElement.attributes["node-id"].value
      ) ?? void 0;
      if (!node2) return;
      widgetInstance = node2.widgets?.find((w) => w.name === props.widget.name);
      if (!widgetInstance) return;
      canvasEl.value.width *= scaleFactor;
      if (!widgetInstance.triggerDraw)
        widgetInstance.callback = useChainCallback(
          widgetInstance.callback,
          function() {
            this?.triggerDraw?.();
          }
        );
      widgetInstance.triggerDraw = draw;
      useResizeObserver(canvasEl.value.parentElement, draw);
      watch(() => useColorPaletteStore().activePaletteId, draw);
      pointer = new CanvasPointer(canvasEl.value);
    });
    onBeforeUnmount(() => {
      if (widgetInstance) widgetInstance.triggerDraw = () => {
      };
    });
    function draw() {
      if (!widgetInstance || !node2) return;
      const width2 = canvasEl.value.parentElement.clientWidth;
      let height = 20;
      if (widgetInstance.computedHeight) {
        height = widgetInstance.computedHeight;
      } else if (widgetInstance.computeLayoutSize) {
        height = widgetInstance.computeLayoutSize(node2).minHeight;
      } else if (widgetInstance.computeSize) {
        height = widgetInstance.computeSize(width2)[1];
      }
      containerHeight.value = height;
      node2.canvasHeight = height;
      widgetInstance.y = 0;
      canvasEl.value.height = (height + 2) * scaleFactor;
      canvasEl.value.width = width2 * scaleFactor;
      const ctx = canvasEl.value?.getContext("2d");
      if (!ctx) return;
      ctx.scale(scaleFactor, scaleFactor);
      widgetInstance.draw?.(ctx, node2, width2, 1, height);
    }
    __name(draw, "draw");
    function handleDown(e2) {
      if (!node2 || !widgetInstance || !pointer) return;
      augmentToCanvasPointerEvent(e2, node2, canvas2);
      pointer.down(e2);
      if (widgetInstance.mouse)
        pointer.onDrag = (e22) => widgetInstance.mouse?.(e22, [e22.offsetX, e22.offsetY], node2);
      canvas2.processWidgetClick(e2, node2, widgetInstance, pointer);
    }
    __name(handleDown, "handleDown");
    function handleUp(e2) {
      if (!pointer || !node2) return;
      augmentToCanvasPointerEvent(e2, node2, canvas2);
      e2.click_time = e2.timeStamp - (pointer?.eDown?.timeStamp ?? 0);
      pointer.up(e2);
    }
    __name(handleUp, "handleUp");
    function handleMove(e2) {
      if (!pointer || !node2) return;
      augmentToCanvasPointerEvent(e2, node2, canvas2);
      pointer.move(e2);
    }
    __name(handleMove, "handleMove");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: "relative mx-[-12px] min-w-0 basis-0",
        style: normalizeStyle({ minHeight: `${containerHeight.value}px` })
      }, [
        createBaseVNode("canvas", {
          ref_key: "canvasEl",
          ref: canvasEl,
          class: "absolute w-full cursor-crosshair",
          onPointerdown: handleDown,
          onPointerup: handleUp,
          onPointermove: handleMove
        }, null, 544)
      ], 4);
    };
  }
});
const WidgetButton = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetButton-dYpFIcYD.js"), true ? __vite__mapDeps([23,24,1,2,4,8,9,10,11,12]) : void 0, import.meta.url)
);
const WidgetInputText = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetInputText-CBfvHKGO.js"), true ? __vite__mapDeps([25,4,1,2,24,26,27,8,9,10,11,12]) : void 0, import.meta.url)
);
const WidgetInputNumber = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetInputNumber-cOgMBsme.js"), true ? __vite__mapDeps([28,29,4,1,2,24,26,27,30,31,8,9,10,11,12]) : void 0, import.meta.url)
);
const WidgetToggleSwitch = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetToggleSwitch-fPkC2AfT.js"), true ? __vite__mapDeps([32,4,1,2,24,27,8,9,10,11,12]) : void 0, import.meta.url)
);
const WidgetSelect = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetSelect-BbENZ2wo.js"), true ? __vite__mapDeps([33,34,1,2,4,9,10,24,26,27,35,8,30,11,12]) : void 0, import.meta.url)
);
const WidgetColorPicker = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetColorPicker-BoLiq7pp.js"), true ? __vite__mapDeps([36,4,1,2,24,26,27,8,9,10,11,12]) : void 0, import.meta.url)
);
const WidgetTextarea = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetTextarea-CWEGHPVp.js"), true ? __vite__mapDeps([37,1,2,4,24,26,8,9,10,11,12]) : void 0, import.meta.url)
);
const WidgetChart = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetChart-DE0SfMVO.js"), true ? __vite__mapDeps([38,4,1,2]) : void 0, import.meta.url)
);
const WidgetImageCompare = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetImageCompare-Bvg3Od2A.js"), true ? __vite__mapDeps([39,1,2]) : void 0, import.meta.url)
);
const WidgetGalleria = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetGalleria-Cv_jTk4g.js"), true ? __vite__mapDeps([40,4,1,2,8,24,9,10,11,12,41]) : void 0, import.meta.url)
);
const WidgetMarkdown = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetMarkdown-BIS1u9hf.js"), true ? __vite__mapDeps([42,1,2,4,8,9,10,11,12]) : void 0, import.meta.url)
);
const WidgetLegacy = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetLegacy-Pu5H3QVb.js"), true ? __vite__mapDeps([43,4,1,2,8,9,10,11,12]) : void 0, import.meta.url)
);
const WidgetRecordAudio = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetRecordAudio-vbi6hQm8.js"), true ? __vite__mapDeps([44,1,2,7,6,4,8,9,10,11,12]) : void 0, import.meta.url)
);
const AudioPreviewPlayer = defineAsyncComponent(
  () => __vitePreload(() => import("./AudioPreviewPlayer-CmLbwr_w.js"), true ? __vite__mapDeps([45,4,1,2,8,6,9,10,11,12,46]) : void 0, import.meta.url)
);
const WidgetAudioUI = defineAsyncComponent(
  () => __vitePreload(() => import("./WidgetAudioUI-Bq-GLU2i.js"), true ? __vite__mapDeps([47,6,34,1,2,4,9,10,24,26,27,35,8,30,45,11,12,46]) : void 0, import.meta.url)
);
const Load3D = defineAsyncComponent(
  () => __vitePreload(() => import("./Load3D-TOov0GRg.js"), true ? __vite__mapDeps([48,3,1,2,4,5,8,9,10,11,12]) : void 0, import.meta.url)
);
const coreWidgetDefinitions = [
  [
    "button",
    { component: WidgetButton, aliases: ["BUTTON"], essential: false }
  ],
  [
    "string",
    {
      component: WidgetInputText,
      aliases: ["STRING", "text"],
      essential: false
    }
  ],
  ["int", { component: WidgetInputNumber, aliases: ["INT"], essential: true }],
  [
    "float",
    {
      component: WidgetInputNumber,
      aliases: ["FLOAT", "number", "slider"],
      essential: true
    }
  ],
  [
    "boolean",
    {
      component: WidgetToggleSwitch,
      aliases: ["BOOLEAN", "toggle"],
      essential: true
    }
  ],
  [
    "combo",
    { component: WidgetSelect, aliases: ["COMBO", "asset"], essential: true }
  ],
  [
    "color",
    { component: WidgetColorPicker, aliases: ["COLOR"], essential: false }
  ],
  [
    "textarea",
    {
      component: WidgetTextarea,
      aliases: ["TEXTAREA", "multiline", "customtext"],
      essential: false
    }
  ],
  ["chart", { component: WidgetChart, aliases: ["CHART"], essential: false }],
  [
    "imagecompare",
    {
      component: WidgetImageCompare,
      aliases: ["IMAGECOMPARE"],
      essential: false
    }
  ],
  [
    "galleria",
    { component: WidgetGalleria, aliases: ["GALLERIA"], essential: false }
  ],
  [
    "markdown",
    {
      component: WidgetMarkdown,
      aliases: ["MARKDOWN", "progressText"],
      essential: false
    }
  ],
  ["legacy", { component: WidgetLegacy, aliases: [], essential: true }],
  [
    "audiorecord",
    {
      component: WidgetRecordAudio,
      aliases: ["AUDIO_RECORD", "AUDIORECORD"],
      essential: false
    }
  ],
  [
    "audioUI",
    {
      component: AudioPreviewPlayer,
      aliases: ["AUDIOUI", "AUDIO_UI"],
      essential: false
    }
  ],
  ["load3D", { component: Load3D, aliases: ["LOAD_3D"], essential: false }]
];
const getComboWidgetAdditions = /* @__PURE__ */ __name(() => {
  return /* @__PURE__ */ new Map([["audio", WidgetAudioUI]]);
}, "getComboWidgetAdditions");
const widgets = /* @__PURE__ */ new Map();
const aliasMap = /* @__PURE__ */ new Map();
for (const [type, def] of coreWidgetDefinitions) {
  widgets.set(type, def);
  for (const alias of def.aliases) {
    aliasMap.set(alias, type);
  }
}
const getCanonicalType = /* @__PURE__ */ __name((type) => aliasMap.get(type) || type, "getCanonicalType");
const getComponent = /* @__PURE__ */ __name((type, name2) => {
  if (type == "combo") {
    const comboAdditions = getComboWidgetAdditions();
    if (comboAdditions.has(name2)) {
      return comboAdditions.get(name2) || null;
    }
  }
  const canonicalType = getCanonicalType(type);
  return widgets.get(canonicalType)?.component || null;
}, "getComponent");
const shouldRenderAsVue = /* @__PURE__ */ __name((widget) => {
  return !widget.options?.canvasOnly && !!widget.type;
}, "shouldRenderAsVue");
const EXPANDING_TYPES = ["textarea", "markdown", "load3D"];
function shouldExpand(type) {
  const canonicalType = getCanonicalType(type);
  return EXPANDING_TYPES.includes(
    canonicalType
  );
}
__name(shouldExpand, "shouldExpand");
const _hoisted_1$1p = {
  key: 0,
  class: "node-error p-2 text-sm text-red-500"
};
const _hoisted_2$19 = {
  key: 0,
  class: "lg-node-widget group col-span-full grid grid-cols-subgrid items-stretch"
};
const _sfc_main$1G = /* @__PURE__ */ defineComponent({
  __name: "NodeWidgets",
  props: {
    nodeData: {}
  },
  setup(__props) {
    const { shouldHandleNodePointerEvents, forwardEventToCanvas } = useCanvasInteractions();
    const { bringNodeToFront } = useNodeZIndex();
    function handleWidgetPointerEvent(event) {
      if (shouldHandleNodePointerEvents.value) return;
      event.stopPropagation();
      forwardEventToCanvas(event);
    }
    __name(handleWidgetPointerEvent, "handleWidgetPointerEvent");
    function handleBringToFront() {
      if (__props.nodeData?.id != null) {
        bringNodeToFront(String(__props.nodeData.id));
      }
    }
    __name(handleBringToFront, "handleBringToFront");
    const renderError = ref(null);
    const { toastErrorHandler } = useErrorHandling();
    onErrorCaptured((error) => {
      renderError.value = error.message;
      toastErrorHandler(error);
      return false;
    });
    const nodeType = computed(() => __props.nodeData?.type || "");
    const { getWidgetTooltip, createTooltipConfig } = useNodeTooltips(
      nodeType.value
    );
    const processedWidgets = computed(() => {
      if (!__props.nodeData?.widgets) return [];
      const { widgets: widgets2 } = __props.nodeData;
      const result = [];
      for (const widget of widgets2) {
        let updateHandler = /* @__PURE__ */ __name(function(value) {
          widget.value = value;
          widget.callback?.(value);
        }, "updateHandler");
        if (!shouldRenderAsVue(widget)) continue;
        const vueComponent = getComponent(widget.type, widget.name) || (widget.isDOMWidget ? _sfc_main$1I : _sfc_main$1H);
        const { slotMetadata, options } = widget;
        const widgetOptions = slotMetadata?.linked ? { ...options, disabled: true } : options;
        const simplified = {
          name: widget.name,
          type: widget.type,
          value: widget.value,
          borderStyle: widget.borderStyle,
          callback: widget.callback,
          controlWidget: widget.controlWidget,
          label: widget.label,
          nodeType: widget.nodeType,
          options: widgetOptions,
          spec: widget.spec
        };
        const tooltipText = getWidgetTooltip(widget);
        const tooltipConfig = createTooltipConfig(tooltipText);
        result.push({
          name: widget.name,
          type: widget.type,
          vueComponent,
          simplified,
          value: widget.value,
          updateHandler,
          tooltipConfig,
          slotMetadata
        });
      }
      return result;
    });
    const gridTemplateRows = computed(() => {
      if (!__props.nodeData?.widgets) return "";
      const processedNames = new Set(toValue(processedWidgets).map((w) => w.name));
      return __props.nodeData.widgets.filter((w) => processedNames.has(w.name) && !w.options?.hidden).map(
        (w) => shouldExpand(w.type) || w.hasLayoutSize ? "auto" : "min-content"
      ).join(" ");
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return renderError.value ? (openBlock(), createElementBlock("div", _hoisted_1$1p, toDisplayString(unref(st)("nodeErrors.widgets", "Node Widgets Error")), 1)) : (openBlock(), createElementBlock("div", {
        key: 1,
        class: normalizeClass(
          unref(cn)(
            "lg-node-widgets grid grid-cols-[min-content_minmax(80px,max-content)_minmax(125px,auto)] flex-1 gap-y-1 pr-3",
            unref(shouldHandleNodePointerEvents) ? "pointer-events-auto" : "pointer-events-none"
          )
        ),
        style: normalizeStyle({
          "grid-template-rows": gridTemplateRows.value
        }),
        onPointerdownCapture: handleBringToFront,
        onPointerdown: handleWidgetPointerEvent,
        onPointermove: handleWidgetPointerEvent,
        onPointerup: handleWidgetPointerEvent
      }, [
        (openBlock(true), createElementBlock(Fragment, null, renderList(processedWidgets.value, (widget, index2) => {
          return openBlock(), createElementBlock(Fragment, {
            key: `widget-${index2}-${widget.name}`
          }, [
            !widget.simplified.options?.hidden ? (openBlock(), createElementBlock("div", _hoisted_2$19, [
              createBaseVNode("div", {
                class: normalizeClass(
                  unref(cn)(
                    "z-10 w-3 opacity-0 transition-opacity duration-150 group-hover:opacity-100 flex items-stretch",
                    widget.slotMetadata?.linked && "opacity-100"
                  )
                )
              }, [
                widget.slotMetadata ? (openBlock(), createBlock(_sfc_main$1L, {
                  key: 0,
                  "slot-data": {
                    name: widget.name,
                    type: widget.type,
                    boundingRect: [0, 0, 0, 0]
                  },
                  "node-id": _ctx.nodeData?.id != null ? String(_ctx.nodeData.id) : "",
                  index: widget.slotMetadata.index,
                  socketless: widget.simplified.spec?.socketless,
                  "dot-only": ""
                }, null, 8, ["slot-data", "node-id", "index", "socketless"])) : createCommentVNode("", true)
              ], 2),
              withDirectives((openBlock(), createBlock(resolveDynamicComponent(widget.vueComponent), {
                modelValue: widget.value,
                "onUpdate:modelValue": [($event) => widget.value = $event, widget.updateHandler],
                widget: widget.simplified,
                "node-id": _ctx.nodeData?.id != null ? String(_ctx.nodeData.id) : "",
                "node-type": nodeType.value,
                class: "col-span-2"
              }, null, 8, ["modelValue", "onUpdate:modelValue", "widget", "node-id", "node-type"])), [
                [
                  _directive_tooltip,
                  widget.tooltipConfig,
                  void 0,
                  { left: true }
                ]
              ])
            ])) : createCommentVNode("", true)
          ], 64);
        }), 128))
      ], 38));
    };
  }
});
const _hoisted_1$1o = ["data-node-id"];
const _hoisted_2$18 = { class: "flex flex-col justify-center items-center relative pointer-events-none" };
const _hoisted_3$N = ["data-testid"];
const _sfc_main$1F = /* @__PURE__ */ defineComponent({
  __name: "LGraphNodePreview",
  props: {
    nodeDef: {},
    position: { default: "absolute" }
  },
  setup(__props) {
    const widgetStore = useWidgetStore();
    const nodeData = computed(() => {
      const widgets2 = Object.entries(__props.nodeDef.inputs || {}).filter(([_, input]) => widgetStore.inputIsWidget(input)).map(([name2, input]) => ({
        name: name2,
        type: input.widgetType || input.type,
        value: input.default !== void 0 ? input.default : input.type === "COMBO" && Array.isArray(input.options) && input.options.length > 0 ? input.options[0] : "",
        options: {
          hidden: input.hidden,
          advanced: input.advanced,
          values: input.type === "COMBO" ? input.options : void 0
          // For combo widgets
        }
      }));
      const inputs = Object.entries(__props.nodeDef.inputs || {}).filter(([_, input]) => !widgetStore.inputIsWidget(input)).map(([name2, input]) => ({
        name: name2,
        type: input.type,
        shape: input.isOptional ? RenderShape.HollowCircle : void 0,
        boundingRect: [0, 0, 0, 0],
        link: null
      }));
      const outputs = (__props.nodeDef.outputs || []).map((output) => {
        if (typeof output === "string") {
          return {
            name: output,
            type: output,
            boundingRect: [0, 0, 0, 0],
            links: []
          };
        }
        return {
          ...output,
          boundingRect: [0, 0, 0, 0],
          links: []
        };
      });
      return {
        id: `preview-${__props.nodeDef.name}`,
        title: __props.nodeDef.display_name || __props.nodeDef.name,
        type: __props.nodeDef.name,
        mode: 0,
        // Normal mode
        selected: false,
        executing: false,
        widgets: widgets2,
        inputs,
        outputs,
        flags: {
          collapsed: false
        }
      };
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        "data-node-id": nodeData.value.id,
        class: normalizeClass(
          unref(cn)(
            "bg-component-node-background lg-node pb-1 contain-style contain-layout w-[350px] rounded-2xl touch-none flex flex-col border-1 border-solid outline-transparent outline-2 border-node-stroke",
            _ctx.position
          )
        )
      }, [
        createBaseVNode("div", _hoisted_2$18, [
          createVNode(_sfc_main$1N, { "node-data": nodeData.value }, null, 8, ["node-data"])
        ]),
        createBaseVNode("div", {
          class: "flex flex-1 flex-col gap-1 pb-2 pointer-events-none",
          "data-testid": `node-body-${nodeData.value.id}`
        }, [
          createVNode(_sfc_main$1J, { "node-data": nodeData.value }, null, 8, ["node-data"]),
          nodeData.value.widgets?.length ? (openBlock(), createBlock(_sfc_main$1G, {
            key: 0,
            "node-data": nodeData.value,
            class: "pointer-events-none"
          }, null, 8, ["node-data"])) : createCommentVNode("", true)
        ], 8, _hoisted_3$N)
      ], 10, _hoisted_1$1o);
    };
  }
});
const ALLOWED_TAGS = ["video", "source"];
const ALLOWED_ATTRS = [
  "controls",
  "autoplay",
  "loop",
  "muted",
  "preload",
  "poster"
];
const MEDIA_SRC_REGEX = /(<(?:img|source|video)[^>]*\ssrc=['"])(?!(?:\/|https?:\/\/))([^'"\s>]+)(['"])/gi;
function createMarkdownRenderer(baseUrl) {
  const normalizedBase = baseUrl ? baseUrl.replace(/\/+$/, "") : "";
  const renderer = new _Renderer();
  renderer.image = ({ href, title, text }) => {
    let src = href;
    if (normalizedBase && !/^(?:\/|https?:\/\/)/.test(href)) {
      src = `${normalizedBase}/${href}`;
    }
    const titleAttr = title ? ` title="${title}"` : "";
    return `<img src="${src}" alt="${text}"${titleAttr} />`;
  };
  renderer.link = ({ href, title, tokens, text }) => {
    const linkText = tokens ? renderer.parser.parseInline(tokens) : text;
    const titleAttr = title ? ` title="${title}"` : "";
    return `<a href="${href}" ${titleAttr} target="_blank" rel="noopener noreferrer">${linkText}</a>`;
  };
  return renderer;
}
__name(createMarkdownRenderer, "createMarkdownRenderer");
function renderMarkdownToHtml(markdown, baseUrl) {
  if (!markdown) return "";
  let html = marked.parse(markdown, {
    renderer: createMarkdownRenderer(baseUrl),
    gfm: true
    // Enable GitHub Flavored Markdown (including autolinks)
  });
  if (baseUrl) {
    html = html.replace(MEDIA_SRC_REGEX, `$1${baseUrl}$2$3`);
  }
  return purify.sanitize(html, {
    ADD_TAGS: ALLOWED_TAGS,
    ADD_ATTR: [...ALLOWED_ATTRS, "target", "rel"]
  });
}
__name(renderMarkdownToHtml, "renderMarkdownToHtml");
const _hoisted_1$1n = {
  key: 1,
  class: "_sb_node_preview bg-component-node-background"
};
const _hoisted_2$17 = { class: "_sb_table" };
const _hoisted_3$M = ["title"];
const _hoisted_4$D = { class: "_sb_preview_badge" };
const _hoisted_5$x = { class: "_sb_col" };
const _hoisted_6$o = { class: "_sb_col" };
const _hoisted_7$l = { class: "_sb_col" };
const _hoisted_8$f = ["innerHTML"];
const _sfc_main$1E = /* @__PURE__ */ defineComponent({
  __name: "NodePreview",
  props: {
    nodeDef: {},
    position: { default: "absolute" }
  },
  setup(__props) {
    const { shouldRenderVueNodes } = useVueFeatureFlags();
    const colorPaletteStore = useColorPaletteStore();
    const litegraphColors = computed(
      () => colorPaletteStore.completedActivePalette.colors.litegraph_base
    );
    const widgetStore = useWidgetStore();
    const { description } = __props.nodeDef;
    const renderedDescription = computed(() => {
      if (!description) return "";
      return renderMarkdownToHtml(description);
    });
    const allInputDefs = Object.values(__props.nodeDef.inputs);
    const allOutputDefs = __props.nodeDef.outputs;
    const slotInputDefs = allInputDefs.filter(
      (input) => !widgetStore.inputIsWidget(input)
    );
    const widgetInputDefs = allInputDefs.filter(
      (input) => widgetStore.inputIsWidget(input)
    );
    const truncateDefaultValue = /* @__PURE__ */ __name((value, charLimit = 32) => {
      let stringValue;
      if (typeof value === "object" && value !== null) {
        stringValue = JSON.stringify(value);
      } else if (Array.isArray(value)) {
        stringValue = JSON.stringify(value);
      } else if (typeof value === "string") {
        stringValue = value;
      } else {
        stringValue = String(value);
      }
      return toolkit.truncate(stringValue, { length: charLimit });
    }, "truncateDefaultValue");
    return (_ctx, _cache) => {
      return unref(shouldRenderVueNodes) ? (openBlock(), createBlock(_sfc_main$1F, {
        key: 0,
        "node-def": _ctx.nodeDef,
        position: _ctx.position
      }, null, 8, ["node-def", "position"])) : (openBlock(), createElementBlock("div", _hoisted_1$1n, [
        createBaseVNode("div", _hoisted_2$17, [
          createBaseVNode("div", {
            class: "node_header mr-4 text-ellipsis",
            title: _ctx.nodeDef.display_name,
            style: normalizeStyle({
              backgroundColor: litegraphColors.value.NODE_DEFAULT_COLOR,
              color: litegraphColors.value.NODE_TITLE_COLOR
            })
          }, [
            _cache[0] || (_cache[0] = createBaseVNode("div", { class: "_sb_dot headdot pr-3" }, null, -1)),
            createTextVNode(" " + toDisplayString(_ctx.nodeDef.display_name), 1)
          ], 12, _hoisted_3$M),
          createBaseVNode("div", _hoisted_4$D, toDisplayString(_ctx.$t("g.preview")), 1),
          (openBlock(true), createElementBlock(Fragment, null, renderList(unref(toolkit).zip(unref(slotInputDefs), unref(allOutputDefs)), ([slotInput, slotOutput]) => {
            return openBlock(), createElementBlock("div", {
              key: (slotInput?.name || "") + (slotOutput?.index.toString() || ""),
              class: "_sb_row slot_row"
            }, [
              createBaseVNode("div", _hoisted_5$x, [
                slotInput ? (openBlock(), createElementBlock("div", {
                  key: 0,
                  class: normalizeClass(["_sb_dot", slotInput.type])
                }, null, 2)) : createCommentVNode("", true)
              ]),
              createBaseVNode("div", _hoisted_6$o, toDisplayString(slotInput ? slotInput.name : ""), 1),
              _cache[1] || (_cache[1] = createBaseVNode("div", { class: "_sb_col middle-column" }, null, -1)),
              createBaseVNode("div", {
                class: "_sb_col _sb_inherit",
                style: normalizeStyle({
                  color: litegraphColors.value.NODE_TEXT_COLOR
                })
              }, toDisplayString(slotOutput ? slotOutput.name : ""), 5),
              createBaseVNode("div", _hoisted_7$l, [
                slotOutput ? (openBlock(), createElementBlock("div", {
                  key: 0,
                  class: normalizeClass(["_sb_dot", slotOutput.type])
                }, null, 2)) : createCommentVNode("", true)
              ])
            ]);
          }), 128)),
          (openBlock(true), createElementBlock(Fragment, null, renderList(unref(widgetInputDefs), (widgetInput) => {
            return openBlock(), createElementBlock("div", {
              key: widgetInput.name,
              class: "_sb_row _long_field"
            }, [
              _cache[2] || (_cache[2] = createBaseVNode("div", { class: "_sb_col _sb_arrow" }, "◀", -1)),
              createBaseVNode("div", {
                class: "_sb_col",
                style: normalizeStyle({
                  color: litegraphColors.value.WIDGET_SECONDARY_TEXT_COLOR
                })
              }, toDisplayString(widgetInput.name), 5),
              _cache[3] || (_cache[3] = createBaseVNode("div", { class: "_sb_col middle-column" }, null, -1)),
              createBaseVNode("div", {
                class: "_sb_col _sb_inherit",
                style: normalizeStyle({ color: litegraphColors.value.WIDGET_TEXT_COLOR })
              }, toDisplayString(truncateDefaultValue(widgetInput.default)), 5),
              _cache[4] || (_cache[4] = createBaseVNode("div", { class: "_sb_col _sb_arrow" }, "▶", -1))
            ]);
          }), 128))
        ]),
        renderedDescription.value ? (openBlock(), createElementBlock("div", {
          key: 0,
          class: "_sb_description",
          style: normalizeStyle({
            color: litegraphColors.value.WIDGET_SECONDARY_TEXT_COLOR,
            backgroundColor: litegraphColors.value.WIDGET_BGCOLOR
          }),
          innerHTML: renderedDescription.value
        }, null, 12, _hoisted_8$f)) : createCommentVNode("", true)
      ]));
    };
  }
});
const NodePreview = /* @__PURE__ */ _export_sfc(_sfc_main$1E, [["__scopeId", "data-v-e348d541"]]);
const _hoisted_1$1m = { class: "_content" };
const _hoisted_2$16 = { class: "_footer" };
const _sfc_main$1D = /* @__PURE__ */ defineComponent({
  __name: "NodeSearchFilter",
  emits: ["addFilter"],
  setup(__props, { emit: __emit }) {
    const filters = computed(() => nodeDefStore.nodeSearchService.nodeFilters);
    const selectedFilter = ref();
    const filterValues = computed(() => selectedFilter.value?.fuseSearch.data ?? []);
    const selectedFilterValue = ref("");
    const nodeDefStore = useNodeDefStore();
    onMounted(() => {
      selectedFilter.value = nodeDefStore.nodeSearchService.nodeFilters[0];
      updateSelectedFilterValue();
    });
    const emit = __emit;
    const updateSelectedFilterValue = /* @__PURE__ */ __name(() => {
      if (filterValues.value.includes(selectedFilterValue.value)) {
        return;
      }
      selectedFilterValue.value = filterValues.value[0];
    }, "updateSelectedFilterValue");
    const submit = /* @__PURE__ */ __name(() => {
      if (!selectedFilter.value) {
        return;
      }
      emit("addFilter", {
        filterDef: selectedFilter.value,
        value: selectedFilterValue.value
      });
    }, "submit");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock(Fragment, null, [
        createBaseVNode("div", _hoisted_1$1m, [
          createVNode(unref(script$k), {
            modelValue: selectedFilter.value,
            "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedFilter.value = $event),
            class: "filter-type-select",
            options: filters.value,
            "allow-empty": false,
            "option-label": "name",
            onChange: updateSelectedFilterValue
          }, null, 8, ["modelValue", "options"]),
          createVNode(unref(script$9), {
            modelValue: selectedFilterValue.value,
            "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => selectedFilterValue.value = $event),
            class: "filter-value-select",
            options: filterValues.value,
            filter: "",
            "auto-filter-focus": ""
          }, null, 8, ["modelValue", "options"])
        ]),
        createBaseVNode("div", _hoisted_2$16, [
          createVNode(_sfc_main$2A, {
            type: "button",
            onClick: submit
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(_ctx.$t("g.add")), 1)
            ]),
            _: 1
          })
        ])
      ], 64);
    };
  }
});
const NodeSearchFilter = /* @__PURE__ */ _export_sfc(_sfc_main$1D, [["__scopeId", "data-v-96ed3454"]]);
function normalizePackId(packId) {
  return packId.split("@")[0];
}
__name(normalizePackId, "normalizePackId");
function normalizePackKeys(packs) {
  return mapKeys(packs, (_value, key) => normalizePackId(key));
}
__name(normalizePackKeys, "normalizePackKeys");
function extractCustomNodeName(pythonModule) {
  const modules = pythonModule?.split(".") || [];
  if (modules.length >= 2 && modules[0] === "custom_nodes") {
    return normalizePackId(modules[1]);
  }
  return null;
}
__name(extractCustomNodeName, "extractCustomNodeName");
function getNodeHelpBaseUrl(node2) {
  const nodeSource = getNodeSource(node2.python_module);
  if (nodeSource.type === NodeSourceType.Blueprint) {
    return "";
  }
  if (nodeSource.type === NodeSourceType.CustomNodes) {
    const customNodeName = extractCustomNodeName(node2.python_module);
    if (customNodeName) {
      return `/extensions/${customNodeName}/docs/`;
    }
  }
  return `/docs/${node2.name}/`;
}
__name(getNodeHelpBaseUrl, "getNodeHelpBaseUrl");
class NodeHelpService {
  static {
    __name(this, "NodeHelpService");
  }
  async fetchNodeHelp(node2, locale) {
    const nodeSource = getNodeSource(node2.python_module);
    if (nodeSource.type === NodeSourceType.Blueprint) {
      return node2.description || "";
    }
    if (nodeSource.type === NodeSourceType.CustomNodes) {
      return this.fetchCustomNodeHelp(node2, locale);
    } else {
      return this.fetchCoreNodeHelp(node2, locale);
    }
  }
  async fetchCustomNodeHelp(node2, locale) {
    const customNodeName = extractCustomNodeName(node2.python_module);
    let lastError;
    if (!customNodeName) {
      throw new Error("Invalid custom node module");
    }
    const localePath = `/extensions/${customNodeName}/docs/${node2.name}/${locale}.md`;
    const localeDoc = await this.tryFetchMarkdown(localePath);
    if (localeDoc.text) return localeDoc.text;
    lastError = localeDoc.errorText;
    const fallbackPath = `/extensions/${customNodeName}/docs/${node2.name}.md`;
    const fallbackDoc = await this.tryFetchMarkdown(fallbackPath);
    if (fallbackDoc.text) return fallbackDoc.text;
    lastError = fallbackDoc.errorText ?? lastError;
    throw new Error(lastError ?? "Help not found");
  }
  async fetchCoreNodeHelp(node2, locale) {
    const mdUrl = `/docs/${node2.name}/${locale}.md`;
    const doc = await this.tryFetchMarkdown(mdUrl);
    if (!doc.text) {
      throw new Error(doc.errorText ?? "Help not found");
    }
    return doc.text;
  }
  /**
   * Fetch a markdown file and return its text, guarding against HTML/SPA fallbacks.
   * Returns null when not OK or when the content type indicates HTML.
   */
  async tryFetchMarkdown(path) {
    const res = await fetch(api.fileURL(path));
    if (!res.ok) {
      return { text: null, errorText: res.statusText };
    }
    const contentType = res.headers?.get?.("content-type") ?? "";
    const text = await res.text();
    const isHtmlContentType = contentType.includes("text/html");
    if (isHtmlContentType) return { text: null, errorText: res.statusText };
    return { text };
  }
}
const nodeHelpService = new NodeHelpService();
const useNodeHelpStore = defineStore("nodeHelp", () => {
  const currentHelpNode = ref(null);
  const isHelpOpen = computed(() => currentHelpNode.value !== null);
  const helpContent = ref("");
  const isLoading = ref(false);
  const errorMsg = ref(null);
  function openHelp(nodeDef) {
    currentHelpNode.value = nodeDef;
  }
  __name(openHelp, "openHelp");
  function closeHelp() {
    currentHelpNode.value = null;
  }
  __name(closeHelp, "closeHelp");
  const baseUrl = computed(() => {
    const node2 = currentHelpNode.value;
    if (!node2) return "";
    return getNodeHelpBaseUrl(node2);
  });
  watch(
    () => currentHelpNode.value,
    async (node2) => {
      helpContent.value = "";
      errorMsg.value = null;
      if (node2) {
        isLoading.value = true;
        try {
          const locale = i18n.global.locale.value || "en";
          helpContent.value = await nodeHelpService.fetchNodeHelp(node2, locale);
        } catch (e2) {
          errorMsg.value = e2.message;
          helpContent.value = node2.description || "";
        } finally {
          isLoading.value = false;
        }
      }
    },
    { immediate: true }
  );
  const renderedHelpHtml = computed(() => {
    return renderMarkdownToHtml(helpContent.value, baseUrl.value);
  });
  return {
    currentHelpNode,
    isHelpOpen,
    openHelp,
    closeHelp,
    baseUrl,
    renderedHelpHtml,
    isLoading,
    error: errorMsg
  };
});
const _hoisted_1$1l = { class: "node-help-content mx-auto w-full" };
const _hoisted_2$15 = ["innerHTML"];
const _hoisted_3$L = {
  key: 2,
  class: "fallback-content space-y-6 text-sm"
};
const _hoisted_4$C = { key: 0 };
const _hoisted_5$w = { key: 1 };
const _hoisted_6$n = { class: "overflow-x-auto" };
const _hoisted_7$k = { key: 2 };
const _hoisted_8$e = { class: "overflow-x-auto" };
const _sfc_main$1C = /* @__PURE__ */ defineComponent({
  __name: "NodeHelpContent",
  props: {
    node: {}
  },
  setup(__props) {
    const nodeHelpStore = useNodeHelpStore();
    const { renderedHelpHtml, isLoading, error } = storeToRefs(nodeHelpStore);
    const inputList = computed(
      () => Object.values(__props.node.inputs).map((spec) => ({
        name: spec.name,
        type: spec.type,
        tooltip: spec.tooltip || ""
      }))
    );
    const outputList = computed(
      () => __props.node.outputs.map((spec) => ({
        name: spec.name,
        type: spec.type,
        tooltip: spec.tooltip || ""
      }))
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1l, [
        unref(isLoading) ? (openBlock(), createBlock(unref(script$7), {
          key: 0,
          class: "m-auto",
          "aria-label": _ctx.$t("g.loading")
        }, null, 8, ["aria-label"])) : !unref(error) ? (openBlock(), createElementBlock("div", {
          key: 1,
          class: "markdown-content",
          innerHTML: unref(renderedHelpHtml)
        }, null, 8, _hoisted_2$15)) : (openBlock(), createElementBlock("div", _hoisted_3$L, [
          _ctx.node.description ? (openBlock(), createElementBlock("p", _hoisted_4$C, [
            createBaseVNode("strong", null, toDisplayString(_ctx.$t("g.description")) + ":", 1),
            createTextVNode(" " + toDisplayString(_ctx.node.description), 1)
          ])) : createCommentVNode("", true),
          inputList.value.length ? (openBlock(), createElementBlock("div", _hoisted_5$w, [
            createBaseVNode("p", null, [
              createBaseVNode("strong", null, toDisplayString(_ctx.$t("nodeHelpPage.inputs")) + ":", 1)
            ]),
            createBaseVNode("table", _hoisted_6$n, [
              createBaseVNode("thead", null, [
                createBaseVNode("tr", null, [
                  createBaseVNode("th", null, toDisplayString(_ctx.$t("g.name")), 1),
                  createBaseVNode("th", null, toDisplayString(_ctx.$t("nodeHelpPage.type")), 1),
                  createBaseVNode("th", null, toDisplayString(_ctx.$t("g.description")), 1)
                ])
              ]),
              createBaseVNode("tbody", null, [
                (openBlock(true), createElementBlock(Fragment, null, renderList(inputList.value, (input) => {
                  return openBlock(), createElementBlock("tr", {
                    key: input.name
                  }, [
                    createBaseVNode("td", null, [
                      createBaseVNode("code", null, toDisplayString(input.name), 1)
                    ]),
                    createBaseVNode("td", null, toDisplayString(input.type), 1),
                    createBaseVNode("td", null, toDisplayString(input.tooltip || "-"), 1)
                  ]);
                }), 128))
              ])
            ])
          ])) : createCommentVNode("", true),
          outputList.value.length ? (openBlock(), createElementBlock("div", _hoisted_7$k, [
            createBaseVNode("p", null, [
              createBaseVNode("strong", null, toDisplayString(_ctx.$t("nodeHelpPage.outputs")) + ":", 1)
            ]),
            createBaseVNode("table", _hoisted_8$e, [
              createBaseVNode("thead", null, [
                createBaseVNode("tr", null, [
                  createBaseVNode("th", null, toDisplayString(_ctx.$t("g.name")), 1),
                  createBaseVNode("th", null, toDisplayString(_ctx.$t("nodeHelpPage.type")), 1),
                  createBaseVNode("th", null, toDisplayString(_ctx.$t("g.description")), 1)
                ])
              ]),
              createBaseVNode("tbody", null, [
                (openBlock(true), createElementBlock(Fragment, null, renderList(outputList.value, (output) => {
                  return openBlock(), createElementBlock("tr", {
                    key: output.name
                  }, [
                    createBaseVNode("td", null, [
                      createBaseVNode("code", null, toDisplayString(output.name), 1)
                    ]),
                    createBaseVNode("td", null, toDisplayString(output.type), 1),
                    createBaseVNode("td", null, toDisplayString(output.tooltip || "-"), 1)
                  ]);
                }), 128))
              ])
            ])
          ])) : createCommentVNode("", true)
        ]))
      ]);
    };
  }
});
const NodeHelpContent = /* @__PURE__ */ _export_sfc(_sfc_main$1C, [["__scopeId", "data-v-61b77096"]]);
const isOutputNode = /* @__PURE__ */ __name((node2) => node2.constructor.nodeData?.output_node, "isOutputNode");
const filterOutputNodes = /* @__PURE__ */ __name((nodes) => nodes.filter(isOutputNode), "filterOutputNodes");
function useSelectionState() {
  const canvasStore = useCanvasStore();
  const nodeDefStore = useNodeDefStore();
  const sidebarTabStore = useSidebarTabStore();
  const nodeHelpStore = useNodeHelpStore();
  const { id: nodeLibraryTabId } = useNodeLibrarySidebarTab();
  const { selectedItems } = storeToRefs(canvasStore);
  const selectedNodes = computed(() => {
    return selectedItems.value.filter(
      (i) => isLGraphNode(i)
    );
  });
  const nodeDef = computed(() => {
    if (selectedNodes.value.length !== 1) return null;
    return nodeDefStore.fromLGraphNode(selectedNodes.value[0]);
  });
  const hasAnySelection = computed(() => selectedItems.value.length > 0);
  const hasSingleSelection = computed(() => selectedItems.value.length === 1);
  const hasMultipleSelection = computed(() => selectedItems.value.length > 1);
  const isSingleNode = computed(
    () => hasSingleSelection.value && isLGraphNode(selectedItems.value[0])
  );
  const isSingleSubgraph = computed(
    () => isSingleNode.value && selectedItems.value[0]?.isSubgraphNode?.()
  );
  const isSingleImageNode = computed(
    () => isSingleNode.value && isImageNode(selectedItems.value[0])
  );
  const hasSubgraphs = computed(
    () => selectedItems.value.some((i) => i instanceof SubgraphNode)
  );
  const hasAny3DNodeSelected = computed(() => {
    const enable3DViewer = useSettingStore().get("Comfy.Load3D.3DViewerEnable");
    return selectedNodes.value.length === 1 && selectedNodes.value.some(isLoad3dNode) && enable3DViewer;
  });
  const hasImageNode = computed(() => isSingleImageNode.value);
  const hasOutputNodesSelected = computed(
    () => filterOutputNodes(selectedNodes.value).length > 0
  );
  const computeSelectionStatesFromNodes = /* @__PURE__ */ __name((nodes) => {
    if (!nodes.length)
      return { collapsed: false, pinned: false, bypassed: false };
    return {
      collapsed: nodes.some((n) => n.flags?.collapsed),
      pinned: nodes.some((n) => n.pinned),
      bypassed: nodes.some((n) => n.mode === LGraphEventMode.BYPASS)
    };
  }, "computeSelectionStatesFromNodes");
  const selectedNodesStates = computed(
    () => computeSelectionStatesFromNodes(selectedNodes.value)
  );
  const computeSelectionFlags = /* @__PURE__ */ __name(() => computeSelectionStatesFromNodes(selectedNodes.value), "computeSelectionFlags");
  const showNodeHelp = /* @__PURE__ */ __name(() => {
    const def = nodeDef.value;
    if (!def) return;
    const isSidebarActive = sidebarTabStore.activeSidebarTabId === nodeLibraryTabId;
    const currentHelpNode = nodeHelpStore.currentHelpNode;
    const isSameNodeHelpOpen = isSidebarActive && nodeHelpStore.isHelpOpen && currentHelpNode?.nodePath === def.nodePath;
    if (isSameNodeHelpOpen) {
      nodeHelpStore.closeHelp();
      sidebarTabStore.toggleSidebarTab(nodeLibraryTabId);
      return;
    }
    if (!isSidebarActive) sidebarTabStore.toggleSidebarTab(nodeLibraryTabId);
    nodeHelpStore.openHelp(def);
  }, "showNodeHelp");
  return {
    selectedItems,
    selectedNodes,
    nodeDef,
    showNodeHelp,
    hasAny3DNodeSelected,
    hasAnySelection,
    hasSingleSelection,
    hasMultipleSelection,
    isSingleNode,
    isSingleSubgraph,
    isSingleImageNode,
    hasSubgraphs,
    hasImageNode,
    hasOutputNodesSelected,
    selectedNodesStates,
    computeSelectionFlags
  };
}
__name(useSelectionState, "useSelectionState");
const _hoisted_1$1k = { class: "flex h-full flex-col overflow-auto" };
const _hoisted_2$14 = { class: "flex items-center border-b border-(--p-divider-color) px-3 py-2" };
const _hoisted_3$K = { class: "ml-2 font-semibold" };
const _hoisted_4$B = { class: "grow p-4" };
const _sfc_main$1B = /* @__PURE__ */ defineComponent({
  __name: "NodeHelpPage",
  props: {
    node: {}
  },
  emits: ["close"],
  setup(__props) {
    const nodeHelpStore = useNodeHelpStore();
    const { nodeDef } = useSelectionState();
    const activeHelpDef = computed(
      () => nodeHelpStore.isHelpOpen ? nodeDef.value : null
    );
    whenever(activeHelpDef, (def) => {
      const currentHelpNode = nodeHelpStore.currentHelpNode;
      if (currentHelpNode?.nodePath === def.nodePath) return;
      nodeHelpStore.openHelp(def);
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", _hoisted_1$1k, [
        createBaseVNode("div", _hoisted_2$14, [
          withDirectives((openBlock(), createBlock(_sfc_main$2A, {
            variant: "muted-textonly",
            size: "icon",
            "aria-label": _ctx.$t("g.back"),
            onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("close"))
          }, {
            default: withCtx(() => _cache[1] || (_cache[1] = [
              createBaseVNode("i", { class: "icon-[lucide--arrow-left] size-4" }, null, -1)
            ])),
            _: 1
          }, 8, ["aria-label"])), [
            [
              _directive_tooltip,
              _ctx.$t("g.back"),
              void 0,
              { bottom: true }
            ]
          ]),
          createBaseVNode("span", _hoisted_3$K, toDisplayString(_ctx.node.display_name), 1)
        ]),
        createBaseVNode("div", _hoisted_4$B, [
          createVNode(NodeHelpContent, { node: _ctx.node }, null, 8, ["node"])
        ])
      ]);
    };
  }
});
const BOOKMARK_SETTING_ID = "Comfy.NodeLibrary.Bookmarks.V2";
const useNodeBookmarkStore = defineStore("nodeBookmark", () => {
  const settingStore = useSettingStore();
  const nodeDefStore = useNodeDefStore();
  const bookmarks = computed(
    () => settingStore.get(BOOKMARK_SETTING_ID)
  );
  const bookmarksSet = computed(() => new Set(bookmarks.value));
  const bookmarkedRoot = computed(
    () => buildBookmarkTree(bookmarks.value)
  );
  const isBookmarked = /* @__PURE__ */ __name((node2) => bookmarksSet.value.has(node2.nodePath) || bookmarksSet.value.has(node2.name), "isBookmarked");
  const toggleBookmark = /* @__PURE__ */ __name(async (node2) => {
    if (isBookmarked(node2)) {
      await deleteBookmark(node2.nodePath);
      await deleteBookmark(node2.name);
    } else {
      await addBookmark(node2.name);
    }
  }, "toggleBookmark");
  const buildBookmarkTree = /* @__PURE__ */ __name((bookmarks2) => {
    const bookmarkNodes = bookmarks2.map((bookmark) => {
      if (bookmark.endsWith("/")) return createDummyFolderNodeDef(bookmark);
      const parts2 = bookmark.split("/");
      const name2 = parts2.pop() ?? "";
      const category = parts2.join("/");
      const srcNodeDef = nodeDefStore.nodeDefsByName[name2];
      if (!srcNodeDef) {
        return null;
      }
      const nodeDef = toolkit.clone(srcNodeDef);
      nodeDef.category = category;
      return nodeDef;
    }).filter((nodeDef) => nodeDef !== null);
    return buildNodeDefTree(bookmarkNodes);
  }, "buildBookmarkTree");
  const addBookmark = /* @__PURE__ */ __name(async (nodePath) => {
    await settingStore.set(BOOKMARK_SETTING_ID, [...bookmarks.value, nodePath]);
  }, "addBookmark");
  const deleteBookmark = /* @__PURE__ */ __name(async (nodePath) => {
    await settingStore.set(
      BOOKMARK_SETTING_ID,
      bookmarks.value.filter((b) => b !== nodePath)
    );
  }, "deleteBookmark");
  const addNewBookmarkFolder = /* @__PURE__ */ __name(async (parent, folderName) => {
    const parentPath = parent ? parent.nodePath : "";
    const newFolderPath = parentPath + folderName + "/";
    await addBookmark(newFolderPath);
    return newFolderPath;
  }, "addNewBookmarkFolder");
  const renameBookmarkFolder = /* @__PURE__ */ __name(async (folderNode, newName) => {
    if (!folderNode.isDummyFolder) {
      throw new Error("Cannot rename non-folder node");
    }
    if (newName.includes("/")) {
      throw new Error('Folder name cannot contain "/"');
    }
    const newNodePath = folderNode.category.split("/").slice(0, -1).concat(newName).join("/") + "/";
    if (newNodePath === folderNode.nodePath) {
      return;
    }
    if (bookmarks.value.some((b) => b.startsWith(newNodePath))) {
      throw new Error(`Folder name "${newNodePath}" already exists`);
    }
    await settingStore.set(
      BOOKMARK_SETTING_ID,
      bookmarks.value.map(
        (b) => b.startsWith(folderNode.nodePath) ? b.replace(folderNode.nodePath, newNodePath) : b
      )
    );
    await renameBookmarkCustomization(folderNode.nodePath, newNodePath);
  }, "renameBookmarkFolder");
  const deleteBookmarkFolder = /* @__PURE__ */ __name(async (folderNode) => {
    if (!folderNode.isDummyFolder) {
      throw new Error("Cannot delete non-folder node");
    }
    await settingStore.set(
      BOOKMARK_SETTING_ID,
      bookmarks.value.filter(
        (b) => b !== folderNode.nodePath && !b.startsWith(folderNode.nodePath)
      )
    );
    await deleteBookmarkCustomization(folderNode.nodePath);
  }, "deleteBookmarkFolder");
  const bookmarksCustomization = computed(() => settingStore.get("Comfy.NodeLibrary.BookmarksCustomization"));
  const updateBookmarkCustomization = /* @__PURE__ */ __name(async (nodePath, customization) => {
    const currentCustomization = bookmarksCustomization.value[nodePath] || {};
    const newCustomization = { ...currentCustomization, ...customization };
    if (newCustomization.icon === defaultBookmarkIcon) {
      delete newCustomization.icon;
    }
    if (newCustomization.color === defaultBookmarkColor) {
      delete newCustomization.color;
    }
    if (Object.keys(newCustomization).length === 0) {
      await deleteBookmarkCustomization(nodePath);
    } else {
      await settingStore.set("Comfy.NodeLibrary.BookmarksCustomization", {
        ...bookmarksCustomization.value,
        [nodePath]: newCustomization
      });
    }
  }, "updateBookmarkCustomization");
  const deleteBookmarkCustomization = /* @__PURE__ */ __name(async (nodePath) => {
    await settingStore.set("Comfy.NodeLibrary.BookmarksCustomization", {
      ...bookmarksCustomization.value,
      [nodePath]: void 0
    });
  }, "deleteBookmarkCustomization");
  const renameBookmarkCustomization = /* @__PURE__ */ __name(async (oldNodePath, newNodePath) => {
    const updatedCustomization = { ...bookmarksCustomization.value };
    if (updatedCustomization[oldNodePath]) {
      updatedCustomization[newNodePath] = updatedCustomization[oldNodePath];
      delete updatedCustomization[oldNodePath];
    }
    await settingStore.set(
      "Comfy.NodeLibrary.BookmarksCustomization",
      updatedCustomization
    );
  }, "renameBookmarkCustomization");
  const defaultBookmarkIcon = "pi-bookmark-fill";
  const defaultBookmarkColor = "#a1a1aa";
  return {
    bookmarks,
    bookmarkedRoot,
    isBookmarked,
    toggleBookmark,
    addBookmark,
    addNewBookmarkFolder,
    renameBookmarkFolder,
    deleteBookmarkFolder,
    bookmarksCustomization,
    updateBookmarkCustomization,
    deleteBookmarkCustomization,
    renameBookmarkCustomization,
    defaultBookmarkIcon,
    defaultBookmarkColor
  };
});
const _sfc_main$1A = /* @__PURE__ */ defineComponent({
  __name: "NodeTreeLeaf",
  props: {
    node: {},
    openNodeHelp: { type: Function }
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const props = __props;
    const nodeDef = computed(() => props.node.data);
    const nodeBookmarkStore = useNodeBookmarkStore();
    const isBookmarked = computed(
      () => nodeBookmarkStore.isBookmarked(nodeDef.value)
    );
    const settingStore = useSettingStore();
    const sidebarLocation = computed(
      () => settingStore.get("Comfy.Sidebar.Location")
    );
    const toggleBookmark = /* @__PURE__ */ __name(async () => {
      await nodeBookmarkStore.toggleBookmark(nodeDef.value);
    }, "toggleBookmark");
    const editBlueprint = /* @__PURE__ */ __name(async () => {
      if (!props.node.data)
        throw new Error(
          "Failed to edit subgraph blueprint lacking backing node data"
        );
      await useSubgraphStore().editBlueprint(props.node.data.name);
    }, "editBlueprint");
    const menu2 = ref(null);
    const menuItems = computed(() => {
      const items = [
        {
          label: t2("g.delete"),
          icon: "pi pi-trash",
          severity: "error",
          command: deleteBlueprint
        }
      ];
      return items;
    });
    function handleContextMenu(event) {
      if (!nodeDef.value.name.startsWith(useSubgraphStore().typePrefix)) return;
      menu2.value?.show(event);
    }
    __name(handleContextMenu, "handleContextMenu");
    function deleteBlueprint() {
      if (!props.node.data) return;
      void useSubgraphStore().deleteBlueprint(props.node.data.name);
    }
    __name(deleteBlueprint, "deleteBlueprint");
    const nodePreviewStyle = ref({
      position: "fixed",
      top: "0px",
      left: "0px",
      pointerEvents: "none",
      zIndex: 1001
    });
    const handleNodeHover = /* @__PURE__ */ __name(async () => {
      const hoverTarget = nodeContentElement.value;
      if (!hoverTarget) return;
      const targetRect = hoverTarget.getBoundingClientRect();
      const margin = 40;
      nodePreviewStyle.value.top = `${targetRect.top}px`;
      nodePreviewStyle.value.left = sidebarLocation.value === "left" ? `${targetRect.right + margin}px` : `${targetRect.left - margin}px`;
      nodePreviewStyle.value.transform = sidebarLocation.value === "right" ? "translateX(-100%)" : void 0;
    }, "handleNodeHover");
    const container = ref(null);
    const nodeContentElement = ref(null);
    const isHovered = ref(false);
    const handleMouseEnter = /* @__PURE__ */ __name(async () => {
      isHovered.value = true;
      await nextTick();
      await handleNodeHover();
    }, "handleMouseEnter");
    const handleMouseLeave = /* @__PURE__ */ __name(() => {
      isHovered.value = false;
    }, "handleMouseLeave");
    onMounted(() => {
      nodeContentElement.value = container.value?.closest(".p-tree-node-content") ?? null;
      nodeContentElement.value?.addEventListener("mouseenter", handleMouseEnter);
      nodeContentElement.value?.addEventListener("mouseleave", handleMouseLeave);
    });
    onUnmounted(() => {
      nodeContentElement.value?.removeEventListener("mouseenter", handleMouseEnter);
      nodeContentElement.value?.removeEventListener("mouseleave", handleMouseLeave);
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock(Fragment, null, [
        createBaseVNode("div", {
          ref_key: "container",
          ref: container,
          class: "node-lib-node-container"
        }, [
          createVNode(TreeExplorerTreeNode, {
            node: _ctx.node,
            onContextmenu: handleContextMenu
          }, createSlots({
            "before-label": withCtx(() => [
              nodeDef.value.experimental ? (openBlock(), createBlock(unref(script$l), {
                key: 0,
                value: _ctx.$t("g.experimental"),
                severity: "primary"
              }, null, 8, ["value"])) : createCommentVNode("", true),
              nodeDef.value.deprecated ? (openBlock(), createBlock(unref(script$l), {
                key: 1,
                value: _ctx.$t("g.deprecated"),
                severity: "danger"
              }, null, 8, ["value"])) : createCommentVNode("", true)
            ]),
            _: 2
          }, [
            nodeDef.value.name.startsWith(unref(useSubgraphStore)().typePrefix) ? {
              name: "actions",
              fn: withCtx(() => [
                createVNode(_sfc_main$2A, {
                  variant: "destructive",
                  size: "icon-sm",
                  "aria-label": _ctx.$t("g.delete"),
                  onClick: withModifiers(deleteBlueprint, ["stop"])
                }, {
                  default: withCtx(() => _cache[1] || (_cache[1] = [
                    createBaseVNode("i", { class: "icon-[lucide--trash-2] size-3.5" }, null, -1)
                  ])),
                  _: 1
                }, 8, ["aria-label"]),
                createVNode(_sfc_main$2A, {
                  variant: "muted-textonly",
                  size: "icon-sm",
                  "aria-label": _ctx.$t("g.edit"),
                  onClick: withModifiers(editBlueprint, ["stop"])
                }, {
                  default: withCtx(() => _cache[2] || (_cache[2] = [
                    createBaseVNode("i", { class: "icon-[lucide--square-pen] size-3.5" }, null, -1)
                  ])),
                  _: 1
                }, 8, ["aria-label"])
              ]),
              key: "0"
            } : {
              name: "actions",
              fn: withCtx(() => [
                createVNode(_sfc_main$2A, {
                  class: "bookmark-button",
                  variant: "muted-textonly",
                  size: "icon-sm",
                  "aria-label": _ctx.$t("icon.bookmark"),
                  onClick: withModifiers(toggleBookmark, ["stop"])
                }, {
                  default: withCtx(() => [
                    createBaseVNode("i", {
                      class: normalizeClass(
                        unref(cn)(
                          isBookmarked.value ? "pi pi-bookmark-fill" : "pi pi-bookmark",
                          "size-3.5"
                        )
                      )
                    }, null, 2)
                  ]),
                  _: 1
                }, 8, ["aria-label"]),
                withDirectives((openBlock(), createBlock(_sfc_main$2A, {
                  class: "help-button",
                  variant: "muted-textonly",
                  size: "icon-sm",
                  "aria-label": _ctx.$t("g.learnMore"),
                  onClick: _cache[0] || (_cache[0] = withModifiers(($event) => props.openNodeHelp(nodeDef.value), ["stop"]))
                }, {
                  default: withCtx(() => _cache[3] || (_cache[3] = [
                    createBaseVNode("i", { class: "pi pi-question size-3.5" }, null, -1)
                  ])),
                  _: 1
                }, 8, ["aria-label"])), [
                  [
                    _directive_tooltip,
                    _ctx.$t("g.learnMore"),
                    void 0,
                    { bottom: true }
                  ]
                ])
              ]),
              key: "1"
            }
          ]), 1032, ["node"]),
          isHovered.value ? (openBlock(), createBlock(Teleport, {
            key: 0,
            to: "#node-library-node-preview-container"
          }, [
            createBaseVNode("div", {
              class: "node-lib-node-preview",
              style: normalizeStyle(nodePreviewStyle.value)
            }, [
              createVNode(NodePreview, { "node-def": nodeDef.value }, null, 8, ["node-def"])
            ], 4)
          ])) : createCommentVNode("", true)
        ], 512),
        createVNode(unref(script$f), {
          ref_key: "menu",
          ref: menu2,
          model: menuItems.value
        }, null, 8, ["model"])
      ], 64);
    };
  }
});
const NodeTreeLeaf = /* @__PURE__ */ _export_sfc(_sfc_main$1A, [["__scopeId", "data-v-3dea4187"]]);
const DEFAULT_ICON = "pi pi-sort";
const DEFAULT_GROUPING_ID = "category";
const DEFAULT_SORTING_ID = "original";
class NodeOrganizationService {
  static {
    __name(this, "NodeOrganizationService");
  }
  groupingStrategies = [
    {
      id: "category",
      label: "sideToolbar.nodeLibraryTab.groupStrategies.category",
      icon: "pi pi-folder",
      description: "sideToolbar.nodeLibraryTab.groupStrategies.categoryDesc",
      getNodePath: /* @__PURE__ */ __name((nodeDef) => {
        const category = nodeDef.category || "";
        const categoryParts = category ? category.split("/") : [];
        return [...categoryParts, nodeDef.name];
      }, "getNodePath")
    },
    {
      id: "module",
      label: "sideToolbar.nodeLibraryTab.groupStrategies.module",
      icon: "pi pi-box",
      description: "sideToolbar.nodeLibraryTab.groupStrategies.moduleDesc",
      getNodePath: /* @__PURE__ */ __name((nodeDef) => {
        const pythonModule = nodeDef.python_module || "";
        if (!pythonModule) {
          return ["unknown_module", nodeDef.name];
        }
        const parts2 = pythonModule.split(".");
        if (parts2[0] === "nodes") {
          return ["core", nodeDef.name];
        } else if (parts2[0] === "custom_nodes") {
          if (parts2.length > 1) {
            return [parts2[1], nodeDef.name];
          }
          return ["custom_nodes", nodeDef.name];
        }
        return [...parts2, nodeDef.name];
      }, "getNodePath")
    },
    {
      id: "source",
      label: "sideToolbar.nodeLibraryTab.groupStrategies.source",
      icon: "pi pi-server",
      description: "sideToolbar.nodeLibraryTab.groupStrategies.sourceDesc",
      getNodePath: /* @__PURE__ */ __name((nodeDef) => {
        if (nodeDef.api_node) {
          return ["API nodes", nodeDef.name];
        } else if (nodeDef.nodeSource.type === NodeSourceType.Core) {
          return ["Core", nodeDef.name];
        } else if (nodeDef.nodeSource.type === NodeSourceType.CustomNodes) {
          return ["Custom nodes", nodeDef.name];
        } else {
          return ["Unknown", nodeDef.name];
        }
      }, "getNodePath")
    }
  ];
  sortingStrategies = [
    {
      id: "original",
      label: "sideToolbar.nodeLibraryTab.sortBy.original",
      icon: "pi pi-sort-alt",
      description: "sideToolbar.nodeLibraryTab.sortBy.originalDesc",
      compare: /* @__PURE__ */ __name(() => 0, "compare")
    },
    {
      id: "alphabetical",
      label: "sideToolbar.nodeLibraryTab.sortBy.alphabetical",
      icon: "pi pi-sort-alpha-down",
      description: "sideToolbar.nodeLibraryTab.sortBy.alphabeticalDesc",
      compare: /* @__PURE__ */ __name((a, b) => (a.display_name ?? "").localeCompare(b.display_name ?? ""), "compare")
    }
  ];
  getGroupingStrategies() {
    return [...this.groupingStrategies];
  }
  getGroupingStrategy(id2) {
    return this.groupingStrategies.find((strategy) => strategy.id === id2);
  }
  getSortingStrategies() {
    return [...this.sortingStrategies];
  }
  getSortingStrategy(id2) {
    return this.sortingStrategies.find((strategy) => strategy.id === id2);
  }
  organizeNodes(nodes, options = {}) {
    const { groupBy: groupBy2 = DEFAULT_GROUPING_ID, sortBy: sortBy2 = DEFAULT_SORTING_ID } = options;
    const groupingStrategy = this.getGroupingStrategy(groupBy2);
    const sortingStrategy = this.getSortingStrategy(sortBy2);
    if (!groupingStrategy) {
      throw new Error(`Unknown grouping strategy: ${groupBy2}`);
    }
    if (!sortingStrategy) {
      throw new Error(`Unknown sorting strategy: ${sortBy2}`);
    }
    const sortedNodes = sortingStrategy.id !== "original" ? [...nodes].sort(sortingStrategy.compare) : nodes;
    const tree = buildNodeDefTree(sortedNodes, {
      pathExtractor: groupingStrategy.getNodePath
    });
    if (sortBy2 === "alphabetical") {
      return sortedTree(tree, { groupLeaf: true });
    }
    return tree;
  }
  getGroupingIcon(groupingId) {
    const strategy = this.getGroupingStrategy(groupingId);
    return strategy?.icon || DEFAULT_ICON;
  }
  getSortingIcon(sortingId) {
    const strategy = this.getSortingStrategy(sortingId);
    return strategy?.icon || DEFAULT_ICON;
  }
}
const nodeOrganizationService = new NodeOrganizationService();
const _hoisted_1$1j = { class: "color-customization-selector-container flex flex-row items-center gap-2" };
const _hoisted_2$13 = {
  key: 1,
  class: "pi pi-palette text-lg"
};
const _sfc_main$1z = /* @__PURE__ */ defineComponent({
  __name: "ColorCustomizationSelector",
  props: {
    modelValue: {},
    colorOptions: {},
    allowCustom: { type: Boolean, default: true }
  },
  emits: ["update:modelValue"],
  setup(__props, { emit: __emit }) {
    const customColorOption = { name: "_custom", value: "" };
    const colorOptionsWithCustom = computed(() => [
      ...__props.colorOptions,
      ...__props.allowCustom ? [customColorOption] : []
    ]);
    const emit = __emit;
    const selectedColorOption = ref(customColorOption);
    const customColorValue = ref("");
    onMounted(() => {
      if (__props.modelValue) {
        const predefinedColor = __props.colorOptions.find((opt) => opt.value === __props.modelValue);
        if (predefinedColor) {
          selectedColorOption.value = predefinedColor;
        } else {
          selectedColorOption.value = customColorOption;
          customColorValue.value = __props.modelValue.replace("#", "");
        }
      }
    });
    watch(selectedColorOption, (newOption, oldOption) => {
      if (newOption.name === "_custom") {
        customColorValue.value = oldOption.value.replace("#", "");
      } else {
        emit("update:modelValue", newOption.value);
      }
    });
    watch(customColorValue, (newValue2) => {
      if (selectedColorOption.value.name === "_custom") {
        emit("update:modelValue", newValue2 ? `#${newValue2}` : null);
      }
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1j, [
        createVNode(unref(script$k), {
          modelValue: selectedColorOption.value,
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedColorOption.value = $event),
          options: colorOptionsWithCustom.value,
          "option-label": "name",
          "data-key": "value",
          "allow-empty": false
        }, {
          option: withCtx((slotProps) => [
            slotProps.option.name !== "_custom" ? (openBlock(), createElementBlock("div", {
              key: 0,
              style: normalizeStyle({
                width: "20px",
                height: "20px",
                backgroundColor: slotProps.option.value,
                borderRadius: "50%"
              })
            }, null, 4)) : (openBlock(), createElementBlock("i", _hoisted_2$13))
          ]),
          _: 1
        }, 8, ["modelValue", "options"]),
        selectedColorOption.value.name === "_custom" ? (openBlock(), createBlock(unref(script$m), {
          key: 0,
          modelValue: customColorValue.value,
          "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => customColorValue.value = $event)
        }, null, 8, ["modelValue"])) : createCommentVNode("", true)
      ]);
    };
  }
});
const _hoisted_1$1i = { class: "p-fluid" };
const _hoisted_2$12 = { class: "field icon-field" };
const _hoisted_3$J = { for: "icon" };
const _hoisted_4$A = { class: "field color-field" };
const _hoisted_5$v = { for: "color" };
const _sfc_main$1y = /* @__PURE__ */ defineComponent({
  __name: "CustomizationDialog",
  props: {
    modelValue: { type: Boolean },
    initialIcon: {},
    initialColor: {}
  },
  emits: ["update:modelValue", "confirm"],
  setup(__props, { emit: __emit }) {
    const { t: t2 } = useI18n();
    const props = __props;
    const emit = __emit;
    const visible = computed({
      get: /* @__PURE__ */ __name(() => props.modelValue, "get"),
      set: /* @__PURE__ */ __name((value) => emit("update:modelValue", value), "set")
    });
    const nodeBookmarkStore = useNodeBookmarkStore();
    const iconOptions = [
      { name: t2("icon.bookmark"), value: nodeBookmarkStore.defaultBookmarkIcon },
      { name: t2("icon.folder"), value: "pi-folder" },
      { name: t2("icon.star"), value: "pi-star" },
      { name: t2("icon.heart"), value: "pi-heart" },
      { name: t2("icon.file"), value: "pi-file" },
      { name: t2("icon.inbox"), value: "pi-inbox" },
      { name: t2("icon.box"), value: "pi-box" },
      { name: t2("icon.briefcase"), value: "pi-briefcase" }
    ];
    const colorOptions = [
      { name: t2("color.default"), value: nodeBookmarkStore.defaultBookmarkColor },
      { name: t2("color.blue"), value: "#007bff" },
      { name: t2("color.green"), value: "#28a745" },
      { name: t2("color.red"), value: "#dc3545" },
      { name: t2("color.pink"), value: "#e83e8c" },
      { name: t2("color.yellow"), value: "#ffc107" }
    ];
    const defaultIcon = iconOptions.find(
      (option) => option.value === nodeBookmarkStore.defaultBookmarkIcon
    );
    const selectedIcon = ref(defaultIcon);
    const finalColor = ref(
      props.initialColor || nodeBookmarkStore.defaultBookmarkColor
    );
    const resetCustomization = /* @__PURE__ */ __name(() => {
      selectedIcon.value = iconOptions.find((option) => option.value === props.initialIcon) || defaultIcon;
      finalColor.value = props.initialColor || nodeBookmarkStore.defaultBookmarkColor;
    }, "resetCustomization");
    const confirmCustomization = /* @__PURE__ */ __name(() => {
      emit("confirm", selectedIcon.value.value, finalColor.value);
      closeDialog();
    }, "confirmCustomization");
    const closeDialog = /* @__PURE__ */ __name(() => {
      visible.value = false;
    }, "closeDialog");
    watch(
      () => props.modelValue,
      (newValue2) => {
        if (newValue2) {
          resetCustomization();
        }
      },
      { immediate: true }
    );
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$n), {
        visible: visible.value,
        "onUpdate:visible": _cache[2] || (_cache[2] = ($event) => visible.value = $event),
        header: _ctx.$t("g.customizeFolder")
      }, {
        footer: withCtx(() => [
          createVNode(_sfc_main$2A, {
            variant: "textonly",
            onClick: resetCustomization
          }, {
            default: withCtx(() => [
              _cache[3] || (_cache[3] = createBaseVNode("i", { class: "pi pi-refresh" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("g.reset")), 1)
            ]),
            _: 1
          }),
          createVNode(_sfc_main$2A, {
            autofocus: "",
            onClick: confirmCustomization
          }, {
            default: withCtx(() => [
              _cache[4] || (_cache[4] = createBaseVNode("i", { class: "pi pi-check" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("g.confirm")), 1)
            ]),
            _: 1
          })
        ]),
        default: withCtx(() => [
          createBaseVNode("div", _hoisted_1$1i, [
            createBaseVNode("div", _hoisted_2$12, [
              createBaseVNode("label", _hoisted_3$J, toDisplayString(_ctx.$t("g.icon")), 1),
              createVNode(unref(script$k), {
                modelValue: selectedIcon.value,
                "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedIcon.value = $event),
                options: iconOptions,
                "option-label": "name",
                "data-key": "value"
              }, {
                option: withCtx((slotProps) => [
                  createBaseVNode("i", {
                    class: normalizeClass(["pi", slotProps.option.value, "mr-2"]),
                    style: normalizeStyle({ color: finalColor.value })
                  }, null, 6)
                ]),
                _: 1
              }, 8, ["modelValue"])
            ]),
            createVNode(unref(script$h)),
            createBaseVNode("div", _hoisted_4$A, [
              createBaseVNode("label", _hoisted_5$v, toDisplayString(_ctx.$t("g.color")), 1),
              createVNode(_sfc_main$1z, {
                modelValue: finalColor.value,
                "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => finalColor.value = $event),
                "color-options": colorOptions
              }, null, 8, ["modelValue"])
            ])
          ])
        ]),
        _: 1
      }, 8, ["visible", "header"]);
    };
  }
});
const FolderCustomizationDialog = /* @__PURE__ */ _export_sfc(_sfc_main$1y, [["__scopeId", "data-v-254ef6c3"]]);
const _sfc_main$1x = /* @__PURE__ */ defineComponent({
  __name: "NodeTreeFolder",
  props: {
    node: {}
  },
  setup(__props) {
    const nodeBookmarkStore = useNodeBookmarkStore();
    const customization = computed(() => {
      const nodeDef = __props.node.data;
      return nodeDef ? nodeBookmarkStore.bookmarksCustomization[nodeDef.nodePath] : void 0;
    });
    const treeNodeElement = ref(null);
    const iconElement = ref(null);
    let stopWatchCustomization = null;
    const container = ref(null);
    onMounted(() => {
      treeNodeElement.value = container.value?.closest(
        ".p-tree-node-content"
      );
      iconElement.value = treeNodeElement.value.querySelector(
        ":scope > .p-tree-node-icon"
      );
      updateIconColor();
      stopWatchCustomization = watch(customization, updateIconColor, { deep: true });
    });
    const updateIconColor = /* @__PURE__ */ __name(() => {
      if (iconElement.value && customization.value) {
        iconElement.value.style.color = customization.value.color ?? "";
      }
    }, "updateIconColor");
    onUnmounted(() => {
      if (stopWatchCustomization) {
        stopWatchCustomization();
      }
    });
    const expandedKeys = inject(InjectKeyExpandedKeys);
    const handleItemDrop = /* @__PURE__ */ __name((node2) => {
      if (!expandedKeys) return;
      expandedKeys.value[node2.key] = true;
    }, "handleItemDrop");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        ref_key: "container",
        ref: container,
        class: "node-lib-node-container"
      }, [
        createVNode(TreeExplorerTreeNode, {
          node: _ctx.node,
          onItemDropped: handleItemDrop
        }, null, 8, ["node"])
      ], 512);
    };
  }
});
const _sfc_main$1w = /* @__PURE__ */ defineComponent({
  __name: "NodeBookmarkTreeExplorer",
  props: {
    filteredNodeDefs: {},
    openNodeHelp: { type: Function }
  },
  setup(__props, { expose: __expose }) {
    const props = __props;
    const expandedKeys = ref({});
    const { expandNode, toggleNodeOnEvent } = useTreeExpansion(expandedKeys);
    const nodeBookmarkStore = useNodeBookmarkStore();
    const bookmarkedRoot = computed(() => {
      const filterTree = /* @__PURE__ */ __name((node2) => {
        if (node2.leaf) {
          return props.filteredNodeDefs.some((def) => def.name === node2.data.name) ? node2 : null;
        }
        const filteredChildren = node2.children?.map(filterTree).filter((child) => child !== null);
        if (filteredChildren && filteredChildren.length > 0) {
          return {
            ...node2,
            children: filteredChildren
          };
        }
        return null;
      }, "filterTree");
      return props.filteredNodeDefs.length ? filterTree(nodeBookmarkStore.bookmarkedRoot) || {
        key: "root",
        label: "Root",
        children: []
      } : nodeBookmarkStore.bookmarkedRoot;
    });
    watch(
      () => props.filteredNodeDefs,
      async (newValue2) => {
        if (newValue2.length) {
          await nextTick();
          expandNode(bookmarkedRoot.value);
        }
      }
    );
    const { t: t2 } = useI18n();
    const extraMenuItems = /* @__PURE__ */ __name((menuTargetNode) => [
      {
        label: t2("g.customize"),
        icon: "pi pi-palette",
        command: /* @__PURE__ */ __name(() => {
          if (!menuTargetNode.data) return;
          const customization = nodeBookmarkStore.bookmarksCustomization[menuTargetNode.data.nodePath];
          initialIcon.value = customization?.icon || nodeBookmarkStore.defaultBookmarkIcon;
          initialColor.value = customization?.color || nodeBookmarkStore.defaultBookmarkColor;
          showCustomizationDialog.value = true;
          customizationTargetNodePath.value = menuTargetNode.data.nodePath;
        }, "command"),
        visible: !menuTargetNode?.leaf
      }
    ], "extraMenuItems");
    const renderedBookmarkedRoot = computed(
      () => {
        const fillNodeInfo = /* @__PURE__ */ __name((node2) => {
          const children = node2.children?.map(fillNodeInfo);
          const sortedChildren = children?.sort((a, b) => {
            if (a.leaf === b.leaf) {
              return a.label.localeCompare(b.label);
            }
            return a.leaf ? 1 : -1;
          });
          return {
            key: node2.key,
            label: node2.leaf ? node2.data.display_name : node2.label,
            leaf: node2.leaf,
            data: node2.data,
            getIcon() {
              if (this.leaf) {
                return "pi pi-circle-fill";
              }
              const customization = nodeBookmarkStore.bookmarksCustomization[node2.data?.nodePath];
              return customization?.icon ? "pi " + customization.icon : "pi pi-bookmark-fill";
            },
            children: sortedChildren,
            draggable: node2.leaf,
            async handleAddFolder(newName) {
              if (newName !== "") {
                await nodeBookmarkStore.addNewBookmarkFolder(this.data, newName);
              }
            },
            renderDragPreview(container) {
              const vnode = h(NodePreview, { nodeDef: node2.data });
              render$4(vnode, container);
              return () => {
                render$4(null, container);
              };
            },
            droppable: !node2.leaf,
            async handleDrop(data) {
              const nodeDefToAdd = data.data.data;
              if (nodeBookmarkStore.isBookmarked(nodeDefToAdd)) {
                await nodeBookmarkStore.toggleBookmark(nodeDefToAdd);
              }
              const folderNodeDef = node2.data;
              const nodePath = folderNodeDef.category + "/" + nodeDefToAdd.name;
              await nodeBookmarkStore.addBookmark(nodePath);
            },
            handleClick(e2) {
              if (this.leaf) {
                useLitegraphService().addNodeOnGraph(this.data);
              } else {
                toggleNodeOnEvent(e2, node2);
              }
            },
            contextMenuItems: extraMenuItems,
            ...node2.leaf ? {} : {
              async handleRename(newName) {
                if (this.data && this.data.isDummyFolder) {
                  await nodeBookmarkStore.renameBookmarkFolder(
                    this.data,
                    newName
                  );
                }
              },
              async handleDelete() {
                await nodeBookmarkStore.deleteBookmarkFolder(this.data);
              }
            }
          };
        }, "fillNodeInfo");
        return fillNodeInfo(bookmarkedRoot.value);
      }
    );
    const treeExplorerRef = ref(null);
    __expose({
      addNewBookmarkFolder: /* @__PURE__ */ __name(() => treeExplorerRef.value?.addFolderCommand("root"), "addNewBookmarkFolder")
    });
    const showCustomizationDialog = ref(false);
    const initialIcon = ref(nodeBookmarkStore.defaultBookmarkIcon);
    const initialColor = ref(nodeBookmarkStore.defaultBookmarkColor);
    const customizationTargetNodePath = ref("");
    const updateCustomization = /* @__PURE__ */ __name(async (icon2, color2) => {
      if (customizationTargetNodePath.value) {
        await nodeBookmarkStore.updateBookmarkCustomization(
          customizationTargetNodePath.value,
          { icon: icon2, color: color2 }
        );
      }
    }, "updateCustomization");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock(Fragment, null, [
        createVNode(TreeExplorer, {
          ref_key: "treeExplorerRef",
          ref: treeExplorerRef,
          class: "node-lib-bookmark-tree-explorer",
          root: renderedBookmarkedRoot.value,
          "expanded-keys": expandedKeys.value
        }, {
          folder: withCtx(({ node: node2 }) => [
            createVNode(_sfc_main$1x, { node: node2 }, null, 8, ["node"])
          ]),
          node: withCtx(({ node: node2 }) => [
            createVNode(NodeTreeLeaf, {
              node: node2,
              "open-node-help": props.openNodeHelp
            }, null, 8, ["node", "open-node-help"])
          ]),
          _: 1
        }, 8, ["root", "expanded-keys"]),
        createVNode(FolderCustomizationDialog, {
          modelValue: showCustomizationDialog.value,
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => showCustomizationDialog.value = $event),
          "initial-icon": initialIcon.value,
          "initial-color": initialColor.value,
          onConfirm: updateCustomization
        }, null, 8, ["modelValue", "initial-icon", "initial-color"])
      ], 64);
    };
  }
});
const _hoisted_1$1h = { class: "h-full" };
const _hoisted_2$11 = { class: "flex flex-col gap-1 p-2" };
const _hoisted_3$I = { class: "flex flex-col gap-1 p-2" };
const _hoisted_4$z = { class: "px-2 2xl:px-4" };
const _sfc_main$1v = /* @__PURE__ */ defineComponent({
  __name: "NodeLibrarySidebarTab",
  setup(__props) {
    const nodeDefStore = useNodeDefStore();
    const nodeBookmarkStore = useNodeBookmarkStore();
    const nodeHelpStore = useNodeHelpStore();
    const commandStore = useCommandStore();
    const expandedKeys = ref({});
    const { expandNode, toggleNodeOnEvent } = useTreeExpansion(expandedKeys);
    const nodeBookmarkTreeExplorerRef = ref(null);
    const searchBoxRef = ref();
    onMounted(() => {
      searchBoxRef.value?.focus();
    });
    const searchFilter = ref(null);
    const groupingPopover = ref(null);
    const sortingPopover = ref(null);
    const selectedGroupingId = useLocalStorage(
      "Comfy.NodeLibrary.GroupBy",
      DEFAULT_GROUPING_ID
    );
    const selectedSortingId = useLocalStorage(
      "Comfy.NodeLibrary.SortBy",
      DEFAULT_SORTING_ID
    );
    const searchQuery = ref("");
    const { currentHelpNode, isHelpOpen } = storeToRefs(nodeHelpStore);
    const { openHelp, closeHelp } = nodeHelpStore;
    const groupingOptions = computed(
      () => nodeOrganizationService.getGroupingStrategies().map((strategy) => ({
        id: strategy.id,
        label: strategy.label,
        icon: strategy.icon
      }))
    );
    const sortingOptions = computed(
      () => nodeOrganizationService.getSortingStrategies().map((strategy) => ({
        id: strategy.id,
        label: strategy.label,
        icon: strategy.icon
      }))
    );
    const selectedGroupingIcon = computed(
      () => nodeOrganizationService.getGroupingIcon(selectedGroupingId.value)
    );
    const selectedSortingIcon = computed(
      () => nodeOrganizationService.getSortingIcon(selectedSortingId.value)
    );
    const selectGrouping = /* @__PURE__ */ __name((groupingId) => {
      selectedGroupingId.value = groupingId;
      groupingPopover.value?.hide();
    }, "selectGrouping");
    const selectSorting = /* @__PURE__ */ __name((sortingId) => {
      selectedSortingId.value = sortingId;
      sortingPopover.value?.hide();
    }, "selectSorting");
    const resetOrganization = /* @__PURE__ */ __name(() => {
      selectedGroupingId.value = DEFAULT_GROUPING_ID;
      selectedSortingId.value = DEFAULT_SORTING_ID;
    }, "resetOrganization");
    const root = computed(() => {
      const nodes = filteredNodeDefs.value.length > 0 ? filteredNodeDefs.value : nodeDefStore.visibleNodeDefs;
      return nodeOrganizationService.organizeNodes(nodes, {
        groupBy: selectedGroupingId.value,
        sortBy: selectedSortingId.value
      });
    });
    const renderedRoot = computed(() => {
      const fillNodeInfo = /* @__PURE__ */ __name((node2) => {
        const children = node2.children?.map(fillNodeInfo);
        return {
          key: node2.key,
          label: node2.leaf ? node2.data.display_name : node2.label,
          leaf: node2.leaf,
          data: node2.data,
          getIcon() {
            if (this.leaf) {
              return "pi pi-circle-fill";
            }
          },
          children,
          draggable: node2.leaf,
          renderDragPreview(container) {
            const vnode = h(NodePreview, { nodeDef: node2.data });
            render$4(vnode, container);
            return () => {
              render$4(null, container);
            };
          },
          handleClick(e2) {
            if (this.leaf) {
              useLitegraphService().addNodeOnGraph(this.data);
            } else {
              toggleNodeOnEvent(e2, this);
            }
          }
        };
      }, "fillNodeInfo");
      return fillNodeInfo(root.value);
    });
    const filteredNodeDefs = ref([]);
    const filters = ref([]);
    const handleSearch = /* @__PURE__ */ __name(async (query) => {
      if (query.length === 0 && !filters.value.length) {
        filteredNodeDefs.value = [];
        expandedKeys.value = {};
        return;
      }
      const f = filters.value.map((f2) => f2.filter);
      filteredNodeDefs.value = nodeDefStore.nodeSearchService.searchNode(
        query,
        f,
        {
          limit: 64
        },
        {
          matchWildcards: false
        }
      );
      await nextTick();
      if (filteredNodeDefs.value.length > 0) {
        expandNode(root.value);
      }
    }, "handleSearch");
    const onAddFilter = /* @__PURE__ */ __name(async (filterAndValue) => {
      filters.value.push({
        filter: filterAndValue,
        badge: filterAndValue.filterDef.invokeSequence.toUpperCase(),
        badgeClass: filterAndValue.filterDef.invokeSequence + "-badge",
        text: filterAndValue.value,
        id: +/* @__PURE__ */ new Date()
      });
      await handleSearch(searchQuery.value);
    }, "onAddFilter");
    const onRemoveFilter = /* @__PURE__ */ __name(async (filterAndValue) => {
      const index2 = filters.value.findIndex((f) => f === filterAndValue);
      if (index2 !== -1) {
        filters.value.splice(index2, 1);
      }
      await handleSearch(searchQuery.value);
    }, "onRemoveFilter");
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock(Fragment, null, [
        createBaseVNode("div", _hoisted_1$1h, [
          !unref(isHelpOpen) ? (openBlock(), createBlock(SidebarTabTemplate, {
            key: 0,
            title: _ctx.$t("sideToolbar.nodeLibrary")
          }, {
            "tool-buttons": withCtx(() => [
              withDirectives((openBlock(), createBlock(_sfc_main$2A, {
                class: "new-folder-button",
                variant: "muted-textonly",
                size: "icon",
                "aria-label": _ctx.$t("g.newFolder"),
                onClick: _cache[0] || (_cache[0] = ($event) => nodeBookmarkTreeExplorerRef.value?.addNewBookmarkFolder())
              }, {
                default: withCtx(() => _cache[7] || (_cache[7] = [
                  createBaseVNode("i", { class: "icon-[lucide--folder-plus] size-4" }, null, -1)
                ])),
                _: 1
              }, 8, ["aria-label"])), [
                [
                  _directive_tooltip,
                  _ctx.$t("g.newFolder"),
                  void 0,
                  { bottom: true }
                ]
              ]),
              withDirectives((openBlock(), createBlock(_sfc_main$2A, {
                variant: "muted-textonly",
                size: "icon",
                "aria-label": _ctx.$t("sideToolbar.nodeLibraryTab.groupBy"),
                onClick: _cache[1] || (_cache[1] = ($event) => groupingPopover.value?.toggle($event))
              }, {
                default: withCtx(() => [
                  createBaseVNode("i", {
                    class: normalizeClass([selectedGroupingIcon.value, "size-4"])
                  }, null, 2)
                ]),
                _: 1
              }, 8, ["aria-label"])), [
                [
                  _directive_tooltip,
                  _ctx.$t("sideToolbar.nodeLibraryTab.groupBy"),
                  void 0,
                  { bottom: true }
                ]
              ]),
              withDirectives((openBlock(), createBlock(_sfc_main$2A, {
                variant: "muted-textonly",
                size: "icon",
                "aria-label": _ctx.$t("sideToolbar.nodeLibraryTab.sortMode"),
                onClick: _cache[2] || (_cache[2] = ($event) => sortingPopover.value?.toggle($event))
              }, {
                default: withCtx(() => [
                  createBaseVNode("i", {
                    class: normalizeClass([selectedSortingIcon.value, "size-4"])
                  }, null, 2)
                ]),
                _: 1
              }, 8, ["aria-label"])), [
                [
                  _directive_tooltip,
                  _ctx.$t("sideToolbar.nodeLibraryTab.sortMode"),
                  void 0,
                  { bottom: true }
                ]
              ]),
              withDirectives((openBlock(), createBlock(_sfc_main$2A, {
                variant: "muted-textonly",
                size: "icon",
                "aria-label": _ctx.$t("sideToolbar.nodeLibraryTab.resetView"),
                onClick: resetOrganization
              }, {
                default: withCtx(() => _cache[8] || (_cache[8] = [
                  createBaseVNode("i", { class: "icon-[lucide--filter-x] size-4" }, null, -1)
                ])),
                _: 1
              }, 8, ["aria-label"])), [
                [
                  _directive_tooltip,
                  _ctx.$t("sideToolbar.nodeLibraryTab.resetView"),
                  void 0,
                  { bottom: true }
                ]
              ]),
              withDirectives((openBlock(), createBlock(_sfc_main$2A, {
                variant: "muted-textonly",
                size: "icon",
                "aria-label": _ctx.$t("menu.refresh"),
                onClick: _cache[3] || (_cache[3] = () => unref(commandStore).execute("Comfy.RefreshNodeDefinitions"))
              }, {
                default: withCtx(() => _cache[9] || (_cache[9] = [
                  createBaseVNode("i", { class: "icon-[lucide--refresh-cw] size-4" }, null, -1)
                ])),
                _: 1
              }, 8, ["aria-label"])), [
                [
                  _directive_tooltip,
                  _ctx.$t("menu.refresh"),
                  void 0,
                  { bottom: true }
                ]
              ]),
              createVNode(unref(script$g), {
                ref_key: "groupingPopover",
                ref: groupingPopover
              }, {
                default: withCtx(() => [
                  createBaseVNode("div", _hoisted_2$11, [
                    (openBlock(true), createElementBlock(Fragment, null, renderList(groupingOptions.value, (option) => {
                      return openBlock(), createBlock(_sfc_main$2A, {
                        key: option.id,
                        variant: unref(selectedGroupingId) === option.id ? "primary" : "textonly",
                        class: "justify-start",
                        onClick: /* @__PURE__ */ __name(($event) => selectGrouping(option.id), "onClick")
                      }, {
                        default: withCtx(() => [
                          createBaseVNode("i", {
                            class: normalizeClass([option.icon, "size-4"])
                          }, null, 2),
                          createTextVNode(" " + toDisplayString(_ctx.$t(option.label)), 1)
                        ]),
                        _: 2
                      }, 1032, ["variant", "onClick"]);
                    }), 128))
                  ])
                ]),
                _: 1
              }, 512),
              createVNode(unref(script$g), {
                ref_key: "sortingPopover",
                ref: sortingPopover
              }, {
                default: withCtx(() => [
                  createBaseVNode("div", _hoisted_3$I, [
                    (openBlock(true), createElementBlock(Fragment, null, renderList(sortingOptions.value, (option) => {
                      return openBlock(), createBlock(_sfc_main$2A, {
                        key: option.id,
                        variant: unref(selectedSortingId) === option.id ? "primary" : "textonly",
                        class: "justify-start",
                        onClick: /* @__PURE__ */ __name(($event) => selectSorting(option.id), "onClick")
                      }, {
                        default: withCtx(() => [
                          createBaseVNode("i", {
                            class: normalizeClass([option.icon, "size-4"])
                          }, null, 2),
                          createTextVNode(" " + toDisplayString(_ctx.$t(option.label)), 1)
                        ]),
                        _: 2
                      }, 1032, ["variant", "onClick"]);
                    }), 128))
                  ])
                ]),
                _: 1
              }, 512)
            ]),
            header: withCtx(() => [
              createBaseVNode("div", _hoisted_4$z, [
                createVNode(SearchBox, {
                  ref_key: "searchBoxRef",
                  ref: searchBoxRef,
                  "model-value": searchQuery.value,
                  "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => searchQuery.value = $event),
                  class: "node-lib-search-box",
                  placeholder: _ctx.$t("g.searchNodes") + "...",
                  "filter-icon": "pi pi-filter",
                  filters: filters.value,
                  onSearch: handleSearch,
                  onShowFilter: _cache[5] || (_cache[5] = ($event) => searchFilter.value?.toggle($event)),
                  onRemoveFilter
                }, null, 8, ["model-value", "placeholder", "filters"]),
                createVNode(unref(script$g), {
                  ref_key: "searchFilter",
                  ref: searchFilter,
                  class: "ml-[-13px]"
                }, {
                  default: withCtx(() => [
                    createVNode(NodeSearchFilter, { onAddFilter })
                  ]),
                  _: 1
                }, 512)
              ])
            ]),
            body: withCtx(() => [
              createBaseVNode("div", null, [
                createVNode(_sfc_main$1w, {
                  ref_key: "nodeBookmarkTreeExplorerRef",
                  ref: nodeBookmarkTreeExplorerRef,
                  "filtered-node-defs": filteredNodeDefs.value,
                  "open-node-help": unref(openHelp)
                }, null, 8, ["filtered-node-defs", "open-node-help"]),
                withDirectives(createVNode(unref(script$h), {
                  type: "dashed",
                  class: "m-2"
                }, null, 512), [
                  [vShow, unref(nodeBookmarkStore).bookmarks.length > 0]
                ]),
                createVNode(TreeExplorer, {
                  "expanded-keys": expandedKeys.value,
                  "onUpdate:expandedKeys": _cache[6] || (_cache[6] = ($event) => expandedKeys.value = $event),
                  class: "node-lib-tree-explorer",
                  root: renderedRoot.value
                }, {
                  node: withCtx(({ node: node2 }) => [
                    createVNode(NodeTreeLeaf, {
                      node: node2,
                      "open-node-help": unref(openHelp)
                    }, null, 8, ["node", "open-node-help"])
                  ]),
                  _: 1
                }, 8, ["expanded-keys", "root"])
              ])
            ]),
            _: 1
          }, 8, ["title"])) : (openBlock(), createBlock(_sfc_main$1B, {
            key: 1,
            node: unref(currentHelpNode),
            onClose: unref(closeHelp)
          }, null, 8, ["node", "onClose"]))
        ]),
        _cache[10] || (_cache[10] = createBaseVNode("div", { id: "node-library-node-preview-container" }, null, -1))
      ], 64);
    };
  }
});
const useNodeLibrarySidebarTab = /* @__PURE__ */ __name(() => {
  return {
    id: "node-library",
    icon: "icon-[comfy--node]",
    title: "sideToolbar.nodeLibrary",
    tooltip: "sideToolbar.nodeLibrary",
    label: "sideToolbar.labels.nodes",
    component: markRaw(_sfc_main$1v),
    type: "vue"
  };
}, "useNodeLibrarySidebarTab");
const _hoisted_1$1g = { class: "flex items-center" };
const _hoisted_2$10 = {
  key: 0,
  class: "mr-2 shrink-0"
};
const _hoisted_3$H = {
  key: 1,
  class: "ml-2 shrink-0"
};
const _sfc_main$1u = /* @__PURE__ */ defineComponent({
  __name: "TextDivider",
  props: {
    text: {},
    position: { default: "left" },
    align: { default: "center" },
    type: { default: "solid" },
    layout: { default: "horizontal" }
  },
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1g, [
        _ctx.position === "left" ? (openBlock(), createElementBlock("span", _hoisted_2$10, toDisplayString(_ctx.text), 1)) : createCommentVNode("", true),
        createVNode(unref(script$h), {
          align: _ctx.align,
          type: _ctx.type,
          layout: _ctx.layout,
          class: "grow"
        }, null, 8, ["align", "type", "layout"]),
        _ctx.position === "right" ? (openBlock(), createElementBlock("span", _hoisted_3$H, toDisplayString(_ctx.text), 1)) : createCommentVNode("", true)
      ]);
    };
  }
});
const _sfc_main$1t = /* @__PURE__ */ defineComponent({
  __name: "WorkflowTreeLeaf",
  props: {
    node: {}
  },
  setup(__props) {
    const workflowBookmarkStore = useWorkflowBookmarkStore();
    const isBookmarked = computed(
      () => __props.node.data && workflowBookmarkStore.isBookmarked(__props.node.data.path)
    );
    const handleBookmarkClick = /* @__PURE__ */ __name(async () => {
      if (__props.node.data) {
        await workflowBookmarkStore.toggleBookmarked(__props.node.data.path);
      }
    }, "handleBookmarkClick");
    return (_ctx, _cache) => {
      return openBlock(), createBlock(TreeExplorerTreeNode, { node: _ctx.node }, {
        actions: withCtx(() => [
          createVNode(_sfc_main$2A, {
            variant: "muted-textonly",
            size: "icon-sm",
            "aria-label": _ctx.$t("icon.bookmark"),
            onClick: withModifiers(handleBookmarkClick, ["stop"])
          }, {
            default: withCtx(() => [
              createBaseVNode("i", {
                class: normalizeClass([
                  isBookmarked.value ? "pi pi-bookmark-fill" : "pi pi-bookmark",
                  "size-3.5"
                ])
              }, null, 2)
            ]),
            _: 1
          }, 8, ["aria-label"])
        ]),
        _: 1
      }, 8, ["node"]);
    };
  }
});
const _hoisted_1$1f = { class: "px-2 2xl:px-4" };
const _hoisted_2$$ = {
  key: 0,
  class: "comfyui-workflows-panel"
};
const _hoisted_3$G = {
  key: 0,
  class: "comfyui-workflows-open"
};
const _hoisted_4$y = { key: 0 };
const _hoisted_5$u = { class: "comfyui-workflows-bookmarks" };
const _hoisted_6$m = { class: "comfyui-workflows-browse" };
const _hoisted_7$j = {
  key: 1,
  class: "comfyui-workflows-search-panel"
};
const _sfc_main$1s = /* @__PURE__ */ defineComponent({
  __name: "WorkflowsSidebarTab",
  setup(__props) {
    const settingStore = useSettingStore();
    const workflowTabsPosition = computed(
      () => settingStore.get("Comfy.Workflow.WorkflowTabsPosition")
    );
    const searchBoxRef = ref();
    const searchQuery = ref("");
    const isSearching = computed(() => searchQuery.value.length > 0);
    const filteredWorkflows = ref([]);
    const filteredRoot = computed(() => {
      return buildWorkflowTree(filteredWorkflows.value);
    });
    const handleSearch = /* @__PURE__ */ __name(async (query) => {
      if (query.length === 0) {
        filteredWorkflows.value = [];
        expandedKeys.value = {};
        return;
      }
      const lowerQuery = query.toLocaleLowerCase();
      filteredWorkflows.value = workflowStore.workflows.filter((workflow) => {
        return workflow.path.toLocaleLowerCase().includes(lowerQuery);
      });
      await nextTick();
      expandNode(filteredRoot.value);
    }, "handleSearch");
    const workflowStore = useWorkflowStore();
    const workflowService2 = useWorkflowService();
    const workspaceStore = useWorkspaceStore();
    const { t: t2 } = useI18n();
    const expandedKeys = ref({});
    const { expandNode, toggleNodeOnEvent } = useTreeExpansion(expandedKeys);
    const dummyExpandedKeys = ref({});
    const handleCloseWorkflow = /* @__PURE__ */ __name(async (workflow) => {
      if (workflow) {
        await workflowService2.closeWorkflow(workflow, {
          warnIfUnsaved: !workspaceStore.shiftDown
        });
      }
    }, "handleCloseWorkflow");
    const buildWorkflowTree = /* @__PURE__ */ __name((workflows) => {
      return buildTree(
        workflows,
        (workflow) => workflow.key.split("/")
      );
    }, "buildWorkflowTree");
    const workflowsTree = computed(
      () => sortedTree(buildWorkflowTree(workflowStore.persistedWorkflows), {
        groupLeaf: true
      })
    );
    const bookmarkedWorkflowsTree = computed(
      () => buildTree(workflowStore.bookmarkedWorkflows, (workflow) => [workflow.key])
    );
    const openWorkflowsTree = computed(
      () => buildTree(workflowStore.openWorkflows, (workflow) => [workflow.key])
    );
    const renderTreeNode = /* @__PURE__ */ __name((node2, type) => {
      const children = node2.children?.map((child) => renderTreeNode(child, type));
      const workflow = node2.data;
      async function handleClick(e2) {
        if (this.leaf) {
          await workflowService2.openWorkflow(workflow);
        } else {
          toggleNodeOnEvent(e2, this);
        }
      }
      __name(handleClick, "handleClick");
      const actions = node2.leaf ? {
        handleClick,
        async handleRename(newName) {
          const newPath = type === "Browse" ? workflow.directory + "/" + appendJsonExt(newName) : ComfyWorkflow.basePath + appendJsonExt(newName);
          await workflowService2.renameWorkflow(workflow, newPath);
        },
        handleDelete: workflow.isTemporary ? void 0 : async function() {
          await workflowService2.deleteWorkflow(workflow);
        },
        contextMenuItems() {
          return [
            {
              label: t2("g.insert"),
              icon: "pi pi-file-export",
              command: /* @__PURE__ */ __name(async () => {
                const workflow2 = node2.data;
                await workflowService2.insertWorkflow(workflow2);
              }, "command")
            },
            {
              label: t2("g.duplicate"),
              icon: "pi pi-file-export",
              command: /* @__PURE__ */ __name(async () => {
                const workflow2 = node2.data;
                await workflowService2.duplicateWorkflow(workflow2);
              }, "command")
            }
          ];
        },
        draggable: true
      } : { handleClick };
      return {
        key: node2.key,
        label: node2.label,
        leaf: node2.leaf,
        data: node2.data,
        children,
        ...actions
      };
    }, "renderTreeNode");
    const selectionKeys = computed(() => ({
      [`root/${workflowStore.activeWorkflow?.key}`]: true
    }));
    const workflowBookmarkStore = useWorkflowBookmarkStore();
    onMounted(async () => {
      searchBoxRef.value?.focus();
      await workflowBookmarkStore.loadBookmarks();
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock(Fragment, null, [
        createVNode(SidebarTabTemplate, {
          title: _ctx.$t("sideToolbar.workflows"),
          class: "workflows-sidebar-tab"
        }, {
          "tool-buttons": withCtx(() => [
            withDirectives((openBlock(), createBlock(_sfc_main$2A, {
              variant: "muted-textonly",
              size: "icon",
              "aria-label": _ctx.$t("g.refresh"),
              onClick: _cache[0] || (_cache[0] = ($event) => unref(workflowStore).syncWorkflows())
            }, {
              default: withCtx(() => _cache[6] || (_cache[6] = [
                createBaseVNode("i", { class: "icon-[lucide--refresh-cw] size-4" }, null, -1)
              ])),
              _: 1
            }, 8, ["aria-label"])), [
              [
                _directive_tooltip,
                _ctx.$t("g.refresh"),
                void 0,
                { bottom: true }
              ]
            ])
          ]),
          header: withCtx(() => [
            createBaseVNode("div", _hoisted_1$1f, [
              createVNode(SearchBox, {
                ref_key: "searchBoxRef",
                ref: searchBoxRef,
                "model-value": searchQuery.value,
                "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => searchQuery.value = $event),
                class: "workflows-search-box",
                placeholder: _ctx.$t("g.searchWorkflows") + "...",
                onSearch: handleSearch
              }, null, 8, ["model-value", "placeholder"])
            ])
          ]),
          body: withCtx(() => [
            !isSearching.value ? (openBlock(), createElementBlock("div", _hoisted_2$$, [
              workflowTabsPosition.value === "Sidebar" ? (openBlock(), createElementBlock("div", _hoisted_3$G, [
                createVNode(_sfc_main$1u, {
                  text: unref(t2)("sideToolbar.workflowTab.workflowTreeType.open"),
                  type: "dashed",
                  class: "ml-2"
                }, null, 8, ["text"]),
                createVNode(TreeExplorer, {
                  "expanded-keys": dummyExpandedKeys.value,
                  "onUpdate:expandedKeys": _cache[2] || (_cache[2] = ($event) => dummyExpandedKeys.value = $event),
                  root: renderTreeNode(
                    openWorkflowsTree.value,
                    "Open"
                    /* Open */
                  ),
                  "selection-keys": selectionKeys.value
                }, {
                  node: withCtx(({ node: node2 }) => [
                    createVNode(TreeExplorerTreeNode, { node: node2 }, {
                      "before-label": withCtx(({ node: treeNode }) => [
                        treeNode.data?.isModified || !treeNode.data?.isPersisted ? (openBlock(), createElementBlock("span", _hoisted_4$y, "*")) : createCommentVNode("", true)
                      ]),
                      actions: withCtx(({ node: treeNode }) => [
                        createVNode(_sfc_main$2A, {
                          class: "close-workflow-button",
                          variant: unref(workspaceStore).shiftDown ? "destructive" : "textonly",
                          size: "icon-sm",
                          "aria-label": _ctx.$t("g.close"),
                          onClick: withModifiers(($event) => handleCloseWorkflow(treeNode.data), ["stop"])
                        }, {
                          default: withCtx(() => _cache[7] || (_cache[7] = [
                            createBaseVNode("i", { class: "icon-[lucide--x] size-3" }, null, -1)
                          ])),
                          _: 2
                        }, 1032, ["variant", "aria-label", "onClick"])
                      ]),
                      _: 2
                    }, 1032, ["node"])
                  ]),
                  _: 1
                }, 8, ["expanded-keys", "root", "selection-keys"])
              ])) : createCommentVNode("", true),
              withDirectives(createBaseVNode("div", _hoisted_5$u, [
                createVNode(_sfc_main$1u, {
                  text: unref(t2)("sideToolbar.workflowTab.workflowTreeType.bookmarks"),
                  type: "dashed",
                  class: "ml-2"
                }, null, 8, ["text"]),
                createVNode(TreeExplorer, {
                  "expanded-keys": dummyExpandedKeys.value,
                  "onUpdate:expandedKeys": _cache[3] || (_cache[3] = ($event) => dummyExpandedKeys.value = $event),
                  root: renderTreeNode(
                    bookmarkedWorkflowsTree.value,
                    "Bookmarks"
                    /* Bookmarks */
                  ),
                  "selection-keys": selectionKeys.value
                }, {
                  node: withCtx(({ node: node2 }) => [
                    createVNode(_sfc_main$1t, { node: node2 }, null, 8, ["node"])
                  ]),
                  _: 1
                }, 8, ["expanded-keys", "root", "selection-keys"])
              ], 512), [
                [vShow, unref(workflowStore).bookmarkedWorkflows.length > 0]
              ]),
              createBaseVNode("div", _hoisted_6$m, [
                createVNode(_sfc_main$1u, {
                  text: unref(t2)("sideToolbar.workflowTab.workflowTreeType.browse"),
                  type: "dashed",
                  class: "ml-2"
                }, null, 8, ["text"]),
                unref(workflowStore).persistedWorkflows.length > 0 ? (openBlock(), createBlock(TreeExplorer, {
                  key: 0,
                  "expanded-keys": expandedKeys.value,
                  "onUpdate:expandedKeys": _cache[4] || (_cache[4] = ($event) => expandedKeys.value = $event),
                  root: renderTreeNode(
                    workflowsTree.value,
                    "Browse"
                    /* Browse */
                  ),
                  "selection-keys": selectionKeys.value
                }, {
                  node: withCtx(({ node: node2 }) => [
                    createVNode(_sfc_main$1t, { node: node2 }, null, 8, ["node"])
                  ]),
                  _: 1
                }, 8, ["expanded-keys", "root", "selection-keys"])) : (openBlock(), createBlock(NoResultsPlaceholder, {
                  key: 1,
                  icon: "pi pi-folder",
                  title: _ctx.$t("g.empty"),
                  message: _ctx.$t("g.noWorkflowsFound")
                }, null, 8, ["title", "message"]))
              ])
            ])) : (openBlock(), createElementBlock("div", _hoisted_7$j, [
              createVNode(TreeExplorer, {
                "expanded-keys": expandedKeys.value,
                "onUpdate:expandedKeys": _cache[5] || (_cache[5] = ($event) => expandedKeys.value = $event),
                root: renderTreeNode(
                  filteredRoot.value,
                  "Browse"
                  /* Browse */
                )
              }, {
                node: withCtx(({ node: node2 }) => [
                  createVNode(_sfc_main$1t, { node: node2 }, null, 8, ["node"])
                ]),
                _: 1
              }, 8, ["expanded-keys", "root"])
            ]))
          ]),
          _: 1
        }, 8, ["title"]),
        createVNode(unref(script$o))
      ], 64);
    };
  }
});
const useWorkflowsSidebarTab = /* @__PURE__ */ __name(() => {
  const settingStore = useSettingStore();
  const workflowStore = useWorkflowStore();
  return {
    id: "workflows",
    icon: "icon-[comfy--workflow]",
    iconBadge: /* @__PURE__ */ __name(() => {
      if (settingStore.get("Comfy.Workflow.WorkflowTabsPosition") !== "Sidebar") {
        return null;
      }
      const value = workflowStore.openWorkflows.length.toString();
      return value === "0" ? null : value;
    }, "iconBadge"),
    title: "sideToolbar.workflows",
    tooltip: "sideToolbar.workflows",
    label: "sideToolbar.labels.workflows",
    component: markRaw(_sfc_main$1s),
    type: "vue"
  };
}, "useWorkflowsSidebarTab");
const useSidebarTabStore = defineStore("sidebarTab", () => {
  const sidebarTabs = ref([]);
  const activeSidebarTabId = ref(null);
  const activeSidebarTab = computed(() => {
    return sidebarTabs.value.find((tab) => tab.id === activeSidebarTabId.value) ?? null;
  });
  const toggleSidebarTab = /* @__PURE__ */ __name((tabId) => {
    activeSidebarTabId.value = activeSidebarTabId.value === tabId ? null : tabId;
  }, "toggleSidebarTab");
  const registerSidebarTab = /* @__PURE__ */ __name((tab) => {
    sidebarTabs.value = [...sidebarTabs.value, tab];
    const labelFunction = /* @__PURE__ */ __name(() => {
      const tabTitle = te(tab.title) ? t(tab.title) : tab.title;
      return `Toggle ${tabTitle} Sidebar`;
    }, "labelFunction");
    const tooltipFunction = tab.tooltip ? te(String(tab.tooltip)) ? () => t(String(tab.tooltip)) : String(tab.tooltip) : void 0;
    const menubarLabelFunction = /* @__PURE__ */ __name(() => {
      const menubarLabelKeys = {
        "node-library": "sideToolbar.nodeLibrary",
        "model-library": "sideToolbar.modelLibrary",
        workflows: "sideToolbar.workflows",
        assets: "sideToolbar.assets"
      };
      const key = menubarLabelKeys[tab.id];
      if (key && te(key)) {
        return t(key);
      }
      return tab.title;
    }, "menubarLabelFunction");
    useCommandStore().registerCommand({
      id: `Workspace.ToggleSidebarTab.${tab.id}`,
      icon: typeof tab.icon === "string" ? tab.icon : void 0,
      label: labelFunction,
      menubarLabel: menubarLabelFunction,
      tooltip: tooltipFunction,
      versionAdded: "1.3.9",
      category: "view-controls",
      function: /* @__PURE__ */ __name(async () => {
        const settingStore = useSettingStore();
        const commandStore = useCommandStore();
        if (tab.id === "model-library" && settingStore.get("Comfy.Assets.UseAssetAPI")) {
          await commandStore.commands.find((cmd) => cmd.id === "Comfy.BrowseModelAssets")?.function?.();
          return;
        }
        toggleSidebarTab(tab.id);
      }, "function"),
      active: /* @__PURE__ */ __name(() => activeSidebarTab.value?.id === tab.id, "active"),
      source: "System"
    });
  }, "registerSidebarTab");
  const unregisterSidebarTab = /* @__PURE__ */ __name((id2) => {
    const index2 = sidebarTabs.value.findIndex((tab) => tab.id === id2);
    if (index2 !== -1) {
      const tab = sidebarTabs.value[index2];
      if (tab.type === "custom" && tab.destroy) {
        tab.destroy();
      }
      const newSidebarTabs = [...sidebarTabs.value];
      newSidebarTabs.splice(index2, 1);
      sidebarTabs.value = newSidebarTabs;
    }
  }, "unregisterSidebarTab");
  const registerCoreSidebarTabs = /* @__PURE__ */ __name(() => {
    registerSidebarTab(useAssetsSidebarTab());
    registerSidebarTab(useNodeLibrarySidebarTab());
    registerSidebarTab(useModelLibrarySidebarTab());
    registerSidebarTab(useWorkflowsSidebarTab());
    const menuStore = useMenuItemStore();
    menuStore.registerCommands(
      ["View"],
      [
        "Workspace.ToggleBottomPanel",
        "Comfy.BrowseTemplates",
        "Workspace.ToggleFocusMode",
        "Comfy.ToggleCanvasInfo",
        "Comfy.Canvas.ToggleMinimap",
        "Comfy.Canvas.ToggleLinkVisibility"
      ]
    );
    menuStore.registerCommands(
      ["View"],
      ["Comfy.Canvas.ZoomIn", "Comfy.Canvas.ZoomOut", "Comfy.Canvas.FitView"]
    );
  }, "registerCoreSidebarTabs");
  return {
    sidebarTabs,
    activeSidebarTabId,
    activeSidebarTab,
    toggleSidebarTab,
    registerSidebarTab,
    unregisterSidebarTab,
    registerCoreSidebarTabs
  };
});
const useWorkspaceStore = defineStore("workspace", () => {
  const spinner = ref(false);
  const shiftDown = ref(false);
  const focusMode = ref(false);
  const toast = computed(() => useToastStore());
  const queueSettings = computed(() => useQueueSettingsStore());
  const command = computed(() => ({
    commands: useCommandStore().commands,
    execute: useCommandStore().execute
  }));
  const sidebarTab = computed(() => useSidebarTabStore());
  const setting = computed(() => ({
    settings: useSettingStore().settingsById,
    // Allow generic key access to settings as custom nodes may add their
    // own settings which is not tracked by the `Setting` schema.
    get: /* @__PURE__ */ __name((key) => useSettingStore().get(key), "get"),
    set: /* @__PURE__ */ __name((key, value) => useSettingStore().set(key, value), "set")
  }));
  const workflow = computed(() => useWorkflowStore());
  const colorPalette = useColorPaletteService();
  const dialog = useDialogService();
  const bottomPanel = useBottomPanelStore();
  const authStore = useFirebaseAuthStore();
  const apiKeyStore = useApiKeyAuthStore();
  const firebaseUser = computed(() => authStore.currentUser);
  const isApiKeyLogin = computed(() => apiKeyStore.isAuthenticated);
  const isLoggedIn = computed(
    () => !!isApiKeyLogin.value || firebaseUser.value !== null
  );
  const partialUserStore = {
    isLoggedIn
  };
  function registerSidebarTab(tab) {
    sidebarTab.value.registerSidebarTab(tab);
  }
  __name(registerSidebarTab, "registerSidebarTab");
  function unregisterSidebarTab(id2) {
    sidebarTab.value.unregisterSidebarTab(id2);
  }
  __name(unregisterSidebarTab, "unregisterSidebarTab");
  function getSidebarTabs() {
    return sidebarTab.value.sidebarTabs;
  }
  __name(getSidebarTabs, "getSidebarTabs");
  return {
    spinner,
    shiftDown,
    focusMode,
    toggleFocusMode: /* @__PURE__ */ __name(() => {
      focusMode.value = !focusMode.value;
    }, "toggleFocusMode"),
    toast,
    queueSettings,
    command,
    sidebarTab,
    setting,
    workflow,
    colorPalette,
    dialog,
    bottomPanel,
    user: partialUserStore,
    registerSidebarTab,
    unregisterSidebarTab,
    getSidebarTabs
  };
});
const useWorkflowService = /* @__PURE__ */ __name(() => {
  const settingStore = useSettingStore();
  const workflowStore = useWorkflowStore();
  const toastStore = useToastStore();
  const dialogService = useDialogService();
  const workflowThumbnail = useWorkflowThumbnail();
  const domWidgetStore = useDomWidgetStore();
  async function getFilename(defaultName) {
    if (settingStore.get("Comfy.PromptFilename")) {
      let filename = await dialogService.prompt({
        title: t("workflowService.exportWorkflow"),
        message: t("workflowService.enterFilename") + ":",
        defaultValue: defaultName
      });
      if (!filename) return null;
      if (!filename.toLowerCase().endsWith(".json")) {
        filename += ".json";
      }
      return filename;
    }
    return defaultName;
  }
  __name(getFilename, "getFilename");
  function addViewRestore(workflow) {
    if (!settingStore.get("Comfy.EnableWorkflowViewRestore")) return;
    const { offset, scale } = app$1.canvas.ds;
    const [x2, y] = offset;
    workflow.extra ??= {};
    workflow.extra.ds = { scale, offset: [x2, y] };
  }
  __name(addViewRestore, "addViewRestore");
  const exportWorkflow = /* @__PURE__ */ __name(async (filename, promptProperty) => {
    const workflow = workflowStore.activeWorkflow;
    if (workflow?.path) {
      filename = workflow.filename;
    }
    const p = await app$1.graphToPrompt();
    addViewRestore(p.workflow);
    const json = JSON.stringify(p[promptProperty], null, 2);
    const blob = new Blob([json], { type: "application/json" });
    const file = await getFilename(filename);
    if (!file) return;
    downloadBlob(file, blob);
  }, "exportWorkflow");
  const saveWorkflowAs = /* @__PURE__ */ __name(async (workflow) => {
    const newFilename = await workflow.promptSave();
    if (!newFilename) return;
    const newPath = workflow.directory + "/" + appendJsonExt(newFilename);
    const existingWorkflow = workflowStore.getWorkflowByPath(newPath);
    if (existingWorkflow && !existingWorkflow.isTemporary) {
      const res = await dialogService.confirm({
        title: t("sideToolbar.workflowTab.confirmOverwriteTitle"),
        type: "overwrite",
        message: t("sideToolbar.workflowTab.confirmOverwrite"),
        itemList: [newPath]
      });
      if (res !== true) return;
      if (existingWorkflow.path === workflow.path) {
        await saveWorkflow(workflow);
        return;
      }
      const deleted = await deleteWorkflow(existingWorkflow, true);
      if (!deleted) return;
    }
    if (workflow.isTemporary) {
      await renameWorkflow(workflow, newPath);
      await workflowStore.saveWorkflow(workflow);
    } else {
      const tempWorkflow = workflowStore.saveAs(workflow, newPath);
      await openWorkflow(tempWorkflow);
      await workflowStore.saveWorkflow(tempWorkflow);
    }
  }, "saveWorkflowAs");
  const saveWorkflow = /* @__PURE__ */ __name(async (workflow) => {
    if (workflow.isTemporary) {
      await saveWorkflowAs(workflow);
    } else {
      await workflowStore.saveWorkflow(workflow);
    }
  }, "saveWorkflow");
  const loadDefaultWorkflow = /* @__PURE__ */ __name(async () => {
    await app$1.loadGraphData(defaultGraph);
  }, "loadDefaultWorkflow");
  const loadBlankWorkflow = /* @__PURE__ */ __name(async () => {
    await app$1.loadGraphData(blankGraph);
  }, "loadBlankWorkflow");
  const reloadCurrentWorkflow = /* @__PURE__ */ __name(async () => {
    const workflow = workflowStore.activeWorkflow;
    if (workflow) {
      await openWorkflow(workflow, { force: true });
    }
  }, "reloadCurrentWorkflow");
  const openWorkflow = /* @__PURE__ */ __name(async (workflow, options = { force: false }) => {
    if (workflowStore.isActive(workflow) && !options.force) return;
    const loadFromRemote = !workflow.isLoaded;
    if (loadFromRemote) {
      await workflow.load();
    }
    await app$1.loadGraphData(
      toRaw(workflow.activeState),
      /* clean=*/
      true,
      /* restore_view=*/
      true,
      workflow,
      {
        showMissingModelsDialog: loadFromRemote,
        showMissingNodesDialog: loadFromRemote,
        checkForRerouteMigration: false
      }
    );
  }, "openWorkflow");
  const closeWorkflow = /* @__PURE__ */ __name(async (workflow, options = {
    warnIfUnsaved: true
  }) => {
    if (workflow.isModified && options.warnIfUnsaved) {
      const confirmed = await dialogService.confirm({
        title: t("sideToolbar.workflowTab.dirtyCloseTitle"),
        type: "dirtyClose",
        message: t("sideToolbar.workflowTab.dirtyClose"),
        itemList: [workflow.path],
        hint: options.hint
      });
      if (confirmed === null) return false;
      if (confirmed === true) {
        await saveWorkflow(workflow);
      }
    }
    if (workflowStore.openWorkflows.length === 1) {
      await loadDefaultWorkflow();
    }
    if (workflowStore.isActive(workflow)) {
      const mostRecentWorkflow = workflowStore.getMostRecentWorkflow();
      if (mostRecentWorkflow) {
        await openWorkflow(mostRecentWorkflow);
      } else {
        await loadNextOpenedWorkflow();
      }
    }
    await workflowStore.closeWorkflow(workflow);
    return true;
  }, "closeWorkflow");
  const renameWorkflow = /* @__PURE__ */ __name(async (workflow, newPath) => {
    await workflowStore.renameWorkflow(workflow, newPath);
  }, "renameWorkflow");
  const deleteWorkflow = /* @__PURE__ */ __name(async (workflow, silent = false) => {
    const bypassConfirm = !settingStore.get("Comfy.Workflow.ConfirmDelete");
    let confirmed = bypassConfirm || silent;
    if (!confirmed) {
      confirmed = await dialogService.confirm({
        title: t("sideToolbar.workflowTab.confirmDeleteTitle"),
        type: "delete",
        message: t("sideToolbar.workflowTab.confirmDelete"),
        itemList: [workflow.path]
      });
      if (!confirmed) return false;
    }
    if (workflowStore.isOpen(workflow)) {
      const closed = await closeWorkflow(workflow, {
        warnIfUnsaved: !confirmed
      });
      if (!closed) return false;
    }
    await workflowStore.deleteWorkflow(workflow);
    if (!silent) {
      toastStore.add({
        severity: "info",
        summary: t("sideToolbar.workflowTab.deleted"),
        life: 1e3
      });
    }
    return true;
  }, "deleteWorkflow");
  const beforeLoadNewGraph = /* @__PURE__ */ __name(() => {
    const workflowStore2 = useWorkspaceStore().workflow;
    const activeWorkflow = workflowStore2.activeWorkflow;
    if (activeWorkflow) {
      activeWorkflow.changeTracker.store();
      void workflowThumbnail.storeThumbnail(activeWorkflow);
      domWidgetStore.clear();
    }
  }, "beforeLoadNewGraph");
  const afterLoadNewGraph = /* @__PURE__ */ __name(async (value, workflowData) => {
    const workflowStore2 = useWorkspaceStore().workflow;
    if (value === null || typeof value === "string") {
      const path = value;
      if (path) {
        const fullPath = ComfyWorkflow.basePath + appendJsonExt(path);
        const existingWorkflow = workflowStore2.getWorkflowByPath(fullPath);
        if (existingWorkflow?.isPersisted && !existingWorkflow.isLoaded) {
          const loadedWorkflow2 = await workflowStore2.openWorkflow(existingWorkflow);
          loadedWorkflow2.changeTracker.reset(workflowData);
          loadedWorkflow2.changeTracker.restore();
          return;
        }
      }
      const tempWorkflow = workflowStore2.createNewTemporary(
        path ? appendJsonExt(path) : void 0,
        workflowData
      );
      await workflowStore2.openWorkflow(tempWorkflow);
      return;
    }
    const loadedWorkflow = await workflowStore2.openWorkflow(value);
    loadedWorkflow.changeTracker.reset(workflowData);
    loadedWorkflow.changeTracker.restore();
  }, "afterLoadNewGraph");
  const insertWorkflow = /* @__PURE__ */ __name(async (workflow, options = {}) => {
    const loadedWorkflow = await workflow.load();
    const workflowJSON = toRaw(loadedWorkflow.initialState);
    const old = localStorage.getItem("litegrapheditor_clipboard");
    const graph = new LGraph(workflowJSON);
    const canvasElement = document.createElement("canvas");
    const canvas2 = new LGraphCanvas(canvasElement, graph, {
      skip_events: true,
      skip_render: true
    });
    canvas2.selectItems();
    canvas2.copyToClipboard();
    app$1.canvas.pasteFromClipboard(options);
    if (old !== null) {
      localStorage.setItem("litegrapheditor_clipboard", old);
    }
  }, "insertWorkflow");
  const loadNextOpenedWorkflow = /* @__PURE__ */ __name(async () => {
    const nextWorkflow = workflowStore.openedWorkflowIndexShift(1);
    if (nextWorkflow) {
      await openWorkflow(nextWorkflow);
    }
  }, "loadNextOpenedWorkflow");
  const loadPreviousOpenedWorkflow = /* @__PURE__ */ __name(async () => {
    const previousWorkflow = workflowStore.openedWorkflowIndexShift(-1);
    if (previousWorkflow) {
      await openWorkflow(previousWorkflow);
    }
  }, "loadPreviousOpenedWorkflow");
  const duplicateWorkflow = /* @__PURE__ */ __name(async (workflow) => {
    const state2 = JSON.parse(JSON.stringify(workflow.activeState));
    const suffix = workflow.isPersisted ? " (Copy)" : "";
    const filename = workflow.filename.replace(/\s*\(\d+\)$/, "") + suffix;
    await app$1.loadGraphData(state2, true, true, filename);
  }, "duplicateWorkflow");
  return {
    exportWorkflow,
    saveWorkflowAs,
    saveWorkflow,
    loadDefaultWorkflow,
    loadBlankWorkflow,
    reloadCurrentWorkflow,
    openWorkflow,
    closeWorkflow,
    renameWorkflow,
    deleteWorkflow,
    insertWorkflow,
    loadNextOpenedWorkflow,
    loadPreviousOpenedWorkflow,
    duplicateWorkflow,
    afterLoadNewGraph,
    beforeLoadNewGraph
  };
}, "useWorkflowService");
async function confirmOverwrite(name2) {
  return await useDialogService().confirm({
    title: t("subgraphStore.overwriteBlueprintTitle"),
    type: "overwriteBlueprint",
    message: t("subgraphStore.overwriteBlueprint"),
    itemList: [name2]
  });
}
__name(confirmOverwrite, "confirmOverwrite");
const useSubgraphStore = defineStore("subgraph", () => {
  class SubgraphBlueprint extends ComfyWorkflow {
    static {
      __name(this, "SubgraphBlueprint");
    }
    static basePath = "subgraphs/";
    tintCanvasBg = "#22227740";
    hasPromptedSave = false;
    constructor(options, confirmFirstSave = false) {
      super(options);
      this.hasPromptedSave = !confirmFirstSave;
    }
    validateSubgraph() {
      if (!this.activeState?.definitions)
        throw new Error(
          "The root graph of a subgraph blueprint must consist of only a single subgraph node"
        );
      const { subgraphs } = this.activeState.definitions;
      const { nodes } = this.activeState;
      function isSubgraphNode(node2) {
        return node2 && subgraphs.some((s) => s.id === node2.type);
      }
      __name(isSubgraphNode, "isSubgraphNode");
      if (nodes.length == 1 && isSubgraphNode(nodes[0])) return;
      const errors = {};
      let firstSubgraphFound = false;
      for (let i = 0; i < nodes.length; i++) {
        if (!firstSubgraphFound && isSubgraphNode(nodes[i])) {
          firstSubgraphFound = true;
          continue;
        }
        errors[nodes[i].id] = {
          errors: [],
          class_type: nodes[i].type,
          dependent_outputs: []
        };
      }
      useExecutionStore().lastNodeErrors = errors;
      useCanvasStore().getCanvas().draw(true, true);
      throw new Error(
        "The root graph of a subgraph blueprint must consist of only a single subgraph node"
      );
    }
    async save() {
      this.validateSubgraph();
      if (!this.hasPromptedSave && useSettingStore().get("Comfy.Workflow.WarnBlueprintOverwrite")) {
        if (!await confirmOverwrite(this.filename)) return this;
        this.hasPromptedSave = true;
      }
      const ret = await super.save();
      useSubgraphStore().updateDef(await this.load());
      return ret;
    }
    async saveAs(path) {
      this.validateSubgraph();
      this.hasPromptedSave = true;
      const ret = await super.saveAs(path);
      useSubgraphStore().updateDef(await this.load());
      return ret;
    }
    async load({ force = false } = {}) {
      if (!force && this.isLoaded) return await super.load({ force });
      const loaded = await super.load({ force });
      const st2 = loaded.activeState;
      const sg = (st2.definitions?.subgraphs ?? []).find(
        (sg2) => sg2.id == st2.nodes[0].type
      );
      if (!sg)
        throw new Error(
          "Loaded subgraph blueprint does not contain valid subgraph"
        );
      sg.name = st2.nodes[0].title = this.filename;
      return loaded;
    }
    async promptSave() {
      return await useDialogService().prompt({
        title: t("subgraphStore.saveBlueprint"),
        message: t("subgraphStore.blueprintName") + ":",
        defaultValue: this.filename
      });
    }
    unload() {
    }
  }
  const subgraphCache = {};
  const typePrefix = "SubgraphBlueprint.";
  const subgraphDefCache = ref(/* @__PURE__ */ new Map());
  const canvasStore = useCanvasStore();
  const subgraphBlueprints = computed(() => [
    ...subgraphDefCache.value.values()
  ]);
  async function fetchSubgraphs() {
    async function loadBlueprint(options) {
      options.path = SubgraphBlueprint.basePath + options.path;
      const bp = await new SubgraphBlueprint(options, true).load();
      useWorkflowStore().attachWorkflow(bp);
      registerNodeDef(bp);
    }
    __name(loadBlueprint, "loadBlueprint");
    async function loadInstalledBlueprints() {
      async function loadGlobalBlueprint([k, v2]) {
        const path = SubgraphBlueprint.basePath + v2.name + ".json";
        const blueprint = new SubgraphBlueprint({
          path,
          modified: Date.now(),
          size: -1
        });
        blueprint.originalContent = blueprint.content = await v2.data;
        blueprint.filename = v2.name;
        useWorkflowStore().attachWorkflow(blueprint);
        const loaded = await blueprint.load();
        registerNodeDef(
          loaded,
          {
            python_module: v2.info.node_pack,
            display_name: v2.name
          },
          k
        );
      }
      __name(loadGlobalBlueprint, "loadGlobalBlueprint");
      const subgraphs = await api.getGlobalSubgraphs();
      await Promise.allSettled(
        Object.entries(subgraphs).map(loadGlobalBlueprint)
      );
    }
    __name(loadInstalledBlueprints, "loadInstalledBlueprints");
    const userSubs = (await api.listUserDataFullInfo(SubgraphBlueprint.basePath)).filter((f) => f.path.endsWith(".json"));
    const settled = await Promise.allSettled([
      ...userSubs.map(loadBlueprint),
      loadInstalledBlueprints()
    ]);
    const errors = settled.filter((i) => "reason" in i).map((i) => i.reason);
    errors.forEach((e2) => console.error("Failed to load subgraph blueprint", e2));
    if (errors.length > 0) {
      useToastStore().add({
        severity: "error",
        summary: t("subgraphStore.loadFailure"),
        detail: errors.length > 3 ? `x${errors.length}` : `${errors}`,
        life: 6e3
      });
    }
  }
  __name(fetchSubgraphs, "fetchSubgraphs");
  function registerNodeDef(workflow, overrides = {}, name2 = workflow.filename) {
    const subgraphNode = workflow.changeTracker.initialState.nodes[0];
    if (!subgraphNode) throw new Error("Invalid Subgraph Blueprint");
    subgraphNode.inputs ??= [];
    subgraphNode.outputs ??= [];
    const inputs = Object.fromEntries(
      subgraphNode.inputs.map((i) => [
        i.name,
        [`${i.type}`, void 0]
      ])
    );
    let description = "User generated subgraph blueprint";
    if (workflow.initialState.extra?.BlueprintDescription)
      description = `${workflow.initialState.extra.BlueprintDescription}`;
    const nodedefv1 = {
      input: { required: inputs },
      output: subgraphNode.outputs.map((o) => `${o.type}`),
      output_name: subgraphNode.outputs.map((o) => o.name),
      name: typePrefix + name2,
      display_name: name2,
      description,
      category: "Subgraph Blueprints",
      output_node: false,
      python_module: "blueprint",
      ...overrides
    };
    const nodeDefImpl = new ComfyNodeDefImpl(nodedefv1);
    subgraphDefCache.value.set(name2, nodeDefImpl);
    subgraphCache[name2] = workflow;
  }
  __name(registerNodeDef, "registerNodeDef");
  async function publishSubgraph() {
    const canvas2 = canvasStore.getCanvas();
    const subgraphNode = [...canvas2.selectedItems][0];
    if (canvas2.selectedItems.size !== 1 || !(subgraphNode instanceof SubgraphNode))
      throw new TypeError("Must have single SubgraphNode selected to publish");
    const { nodes = [], subgraphs = [] } = canvas2._serializeItems([
      subgraphNode
    ]);
    if (nodes.length != 1) {
      throw new TypeError("Must have single SubgraphNode selected to publish");
    }
    const workflowData = {
      revision: 0,
      last_node_id: subgraphNode.id,
      last_link_id: 0,
      nodes,
      links: [],
      version: 0.4,
      definitions: { subgraphs }
    };
    const name2 = await useDialogService().prompt({
      title: t("subgraphStore.saveBlueprint"),
      message: t("subgraphStore.blueprintName") + ":",
      defaultValue: subgraphNode.title
    });
    if (!name2) return;
    if (subgraphDefCache.value.has(name2) && !await confirmOverwrite(name2))
      return;
    const path = SubgraphBlueprint.basePath + name2 + ".json";
    const workflow = new SubgraphBlueprint({
      path,
      size: -1,
      modified: Date.now()
    });
    workflow.originalContent = JSON.stringify(workflowData);
    const loadedWorkflow = await workflow.load();
    workflow.size = 1;
    await workflow.save();
    useWorkflowStore().attachWorkflow(loadedWorkflow);
    registerNodeDef(loadedWorkflow);
    useToastStore().add({
      severity: "success",
      summary: t("subgraphStore.publishSuccess"),
      detail: t("subgraphStore.publishSuccessMessage"),
      life: 4e3
    });
  }
  __name(publishSubgraph, "publishSubgraph");
  function updateDef(blueprint) {
    registerNodeDef(blueprint);
  }
  __name(updateDef, "updateDef");
  async function editBlueprint(nodeType) {
    const name2 = nodeType.slice(typePrefix.length);
    if (!(name2 in subgraphCache))
      throw new Error("not yet loaded");
    useWorkflowStore().attachWorkflow(subgraphCache[name2]);
    await useWorkflowService().openWorkflow(subgraphCache[name2]);
    const canvas2 = useCanvasStore().getCanvas();
    if (canvas2.graph && "subgraph" in canvas2.graph.nodes[0])
      canvas2.setGraph(canvas2.graph.nodes[0].subgraph);
  }
  __name(editBlueprint, "editBlueprint");
  function getBlueprint(nodeType) {
    const name2 = nodeType.slice(typePrefix.length);
    if (!(name2 in subgraphCache))
      throw new Error("not yet loaded");
    return subgraphCache[name2].changeTracker.initialState;
  }
  __name(getBlueprint, "getBlueprint");
  async function deleteBlueprint(nodeType) {
    const name2 = nodeType.slice(typePrefix.length);
    if (!(name2 in subgraphCache))
      throw new Error("not yet loaded");
    if (!await useDialogService().confirm({
      title: t("subgraphStore.confirmDeleteTitle"),
      type: "delete",
      message: t("subgraphStore.confirmDelete"),
      itemList: [name2]
    }))
      return;
    await subgraphCache[name2].delete();
    delete subgraphCache[name2];
    subgraphDefCache.value.delete(name2);
  }
  __name(deleteBlueprint, "deleteBlueprint");
  function isSubgraphBlueprint(workflow) {
    return workflow instanceof SubgraphBlueprint;
  }
  __name(isSubgraphBlueprint, "isSubgraphBlueprint");
  return {
    deleteBlueprint,
    editBlueprint,
    fetchSubgraphs,
    getBlueprint,
    isSubgraphBlueprint,
    publishSubgraph,
    subgraphBlueprints,
    typePrefix,
    updateDef
  };
});
class ComfyNodeDefImpl {
  static {
    __name(this, "ComfyNodeDefImpl");
  }
  // ComfyNodeDef fields (V1)
  name;
  display_name;
  /**
   * Category is not marked as readonly as the bookmark system
   * needs to write to it to assign a node to a custom folder.
   */
  category;
  python_module;
  description;
  help;
  deprecated;
  experimental;
  output_node;
  api_node;
  /**
   * @deprecated Use `inputs` instead
   */
  input;
  /**
   * @deprecated Use `outputs` instead
   */
  output;
  /**
   * @deprecated Use `outputs[n].is_list` instead
   */
  output_is_list;
  /**
   * @deprecated Use `outputs[n].name` instead
   */
  output_name;
  /**
   * @deprecated Use `outputs[n].tooltip` instead
   */
  output_tooltips;
  /**
   * Order of inputs for each category (required, optional, hidden)
   */
  input_order;
  // V2 fields
  inputs;
  outputs;
  hidden;
  // ComfyNodeDefImpl fields
  nodeSource;
  /**
   * @internal
   * Migrate default input options to forceInput.
   */
  static #migrateDefaultInput(nodeDef) {
    const def = toolkit.cloneDeep(nodeDef);
    def.input ??= {};
    for (const [name2, spec] of Object.entries(def.input.required ?? {})) {
      const inputOptions = spec[1];
      if (inputOptions && inputOptions.defaultInput) {
        console.warn(
          `Use of defaultInput on required input ${nodeDef.python_module}:${nodeDef.name}:${name2} is deprecated. Please drop the defaultInput option.`
        );
      }
    }
    for (const [name2, spec] of Object.entries(def.input.optional ?? {})) {
      const inputOptions = spec[1];
      if (inputOptions && inputOptions.defaultInput) {
        console.warn(
          `Use of defaultInput on optional input ${nodeDef.python_module}:${nodeDef.name}:${name2} is deprecated. Please use forceInput instead.`
        );
        inputOptions.forceInput = true;
      }
    }
    return def;
  }
  constructor(def) {
    const obj = ComfyNodeDefImpl.#migrateDefaultInput(def);
    Object.assign(this, obj);
    this.name = obj.name;
    this.display_name = obj.display_name;
    this.category = obj.category;
    this.python_module = obj.python_module;
    this.description = obj.description;
    this.help = obj.help ?? "";
    this.deprecated = obj.deprecated ?? obj.category === "";
    this.experimental = obj.experimental ?? obj.category.startsWith("_for_testing");
    this.output_node = obj.output_node;
    this.api_node = !!obj.api_node;
    this.input = obj.input ?? {};
    this.output = obj.output ?? [];
    this.output_is_list = obj.output_is_list;
    this.output_name = obj.output_name;
    this.output_tooltips = obj.output_tooltips;
    this.input_order = obj.input_order;
    const defV2 = transformNodeDefV1ToV2(obj);
    this.inputs = defV2.inputs;
    this.outputs = defV2.outputs;
    this.hidden = defV2.hidden;
    this.nodeSource = getNodeSource(obj.python_module);
  }
  get nodePath() {
    return (this.category ? this.category + "/" : "") + this.name;
  }
  get isDummyFolder() {
    return this.name === "";
  }
  postProcessSearchScores(scores) {
    const nodeFrequencyStore = useNodeFrequencyStore();
    const nodeFrequency = nodeFrequencyStore.getNodeFrequencyByName(this.name);
    return [scores[0], -nodeFrequency, ...scores.slice(1)];
  }
  get isCoreNode() {
    return this.nodeSource.type === NodeSourceType.Core;
  }
  get nodeLifeCycleBadgeText() {
    if (this.deprecated) return "[DEPR]";
    if (this.experimental) return "[BETA]";
    return "";
  }
}
const SYSTEM_NODE_DEFS = {
  PrimitiveNode: {
    name: "PrimitiveNode",
    display_name: "Primitive",
    category: "utils",
    input: { required: {}, optional: {} },
    output: ["*"],
    output_name: ["connect to widget input"],
    output_is_list: [false],
    output_node: false,
    python_module: "nodes",
    description: "Primitive values like numbers, strings, and booleans."
  },
  Reroute: {
    name: "Reroute",
    display_name: "Reroute",
    category: "utils",
    input: { required: { "": ["*", {}] }, optional: {} },
    output: ["*"],
    output_name: [""],
    output_is_list: [false],
    output_node: false,
    python_module: "nodes",
    description: "Reroute the connection to another node."
  },
  Note: {
    name: "Note",
    display_name: "Note",
    category: "utils",
    input: {
      required: { text: ["STRING", { multiline: true }] },
      optional: {}
    },
    output: [],
    output_name: [],
    output_is_list: [],
    output_node: false,
    python_module: "nodes",
    description: "Node that add notes to your project"
  },
  MarkdownNote: {
    name: "MarkdownNote",
    display_name: "Markdown Note",
    category: "utils",
    input: {
      required: { text: ["STRING", { multiline: true }] },
      optional: {}
    },
    output: [],
    output_name: [],
    output_is_list: [],
    output_node: false,
    python_module: "nodes",
    description: "Node that add notes to your project. Reformats text as markdown."
  }
};
function buildNodeDefTree(nodeDefs, options = {}) {
  const { pathExtractor } = options;
  const defaultPathExtractor = /* @__PURE__ */ __name((nodeDef) => nodeDef.nodePath.split("/"), "defaultPathExtractor");
  return buildTree(nodeDefs, pathExtractor || defaultPathExtractor);
}
__name(buildNodeDefTree, "buildNodeDefTree");
function createDummyFolderNodeDef(folderPath) {
  return new ComfyNodeDefImpl({
    name: "",
    display_name: "",
    category: folderPath.endsWith("/") ? folderPath.slice(0, -1) : folderPath,
    python_module: "nodes",
    description: "Dummy Folder Node (User should never see this string)",
    input: {},
    output: [],
    output_name: [],
    output_is_list: [],
    output_node: false
  });
}
__name(createDummyFolderNodeDef, "createDummyFolderNodeDef");
const useNodeDefStore = defineStore("nodeDef", () => {
  const nodeDefsByName = ref({});
  const nodeDefsByDisplayName = ref({});
  const showDeprecated = ref(false);
  const showExperimental = ref(false);
  const nodeDefFilters = ref([]);
  const nodeDefs = computed(() => {
    const subgraphStore2 = useSubgraphStore();
    return [
      ...Object.values(nodeDefsByName.value),
      ...subgraphStore2.subgraphBlueprints
    ];
  });
  const nodeDataTypes = computed(() => {
    const types = /* @__PURE__ */ new Set();
    for (const nodeDef of nodeDefs.value) {
      for (const input of Object.values(nodeDef.inputs)) {
        types.add(input.type);
      }
      for (const output of nodeDef.outputs) {
        types.add(output.type);
      }
    }
    return types;
  });
  const visibleNodeDefs = computed(() => {
    return nodeDefs.value.filter(
      (nodeDef) => nodeDefFilters.value.every((filter2) => filter2.predicate(nodeDef))
    );
  });
  const nodeSearchService = computed(
    () => new NodeSearchService(visibleNodeDefs.value)
  );
  const nodeTree = computed(() => buildNodeDefTree(visibleNodeDefs.value));
  function updateNodeDefs(nodeDefs2) {
    const newNodeDefsByName = {};
    const newNodeDefsByDisplayName = {};
    for (const nodeDef of nodeDefs2) {
      const nodeDefImpl = nodeDef instanceof ComfyNodeDefImpl ? nodeDef : new ComfyNodeDefImpl(nodeDef);
      newNodeDefsByName[nodeDef.name] = nodeDefImpl;
      newNodeDefsByDisplayName[nodeDef.display_name] = nodeDefImpl;
    }
    nodeDefsByName.value = newNodeDefsByName;
    nodeDefsByDisplayName.value = newNodeDefsByDisplayName;
  }
  __name(updateNodeDefs, "updateNodeDefs");
  function addNodeDef(nodeDef) {
    const nodeDefImpl = new ComfyNodeDefImpl(nodeDef);
    nodeDefsByName.value[nodeDef.name] = nodeDefImpl;
    nodeDefsByDisplayName.value[nodeDef.display_name] = nodeDefImpl;
  }
  __name(addNodeDef, "addNodeDef");
  function fromLGraphNode(node2) {
    const nodeTypeName = node2.constructor?.nodeData?.name ?? node2.type;
    if (!nodeTypeName) return null;
    const nodeDef = nodeDefsByName.value[nodeTypeName] ?? null;
    return nodeDef;
  }
  __name(fromLGraphNode, "fromLGraphNode");
  function getInputSpecForWidget(node2, widgetName) {
    if (!node2.isSubgraphNode()) {
      const nodeDef = fromLGraphNode(node2);
      if (!nodeDef) return void 0;
      return nodeDef.inputs[widgetName];
    }
    const widget = node2.widgets?.find((w) => w.name === widgetName);
    if (!widget || !isProxyWidget(widget)) return void 0;
    const { nodeId, widgetName: subWidgetName } = widget._overlay;
    const subNode = node2.subgraph.getNodeById(nodeId);
    if (!subNode) return void 0;
    return getInputSpecForWidget(subNode, subWidgetName);
  }
  __name(getInputSpecForWidget, "getInputSpecForWidget");
  function registerNodeDefFilter(filter2) {
    nodeDefFilters.value = [...nodeDefFilters.value, filter2];
  }
  __name(registerNodeDefFilter, "registerNodeDefFilter");
  function unregisterNodeDefFilter(id2) {
    nodeDefFilters.value = nodeDefFilters.value.filter((f) => f.id !== id2);
  }
  __name(unregisterNodeDefFilter, "unregisterNodeDefFilter");
  function registerCoreNodeDefFilters() {
    registerNodeDefFilter({
      id: "core.deprecated",
      name: "Hide Deprecated Nodes",
      description: "Hides nodes marked as deprecated unless explicitly enabled",
      predicate: /* @__PURE__ */ __name((nodeDef) => showDeprecated.value || !nodeDef.deprecated, "predicate")
    });
    registerNodeDefFilter({
      id: "core.experimental",
      name: "Hide Experimental Nodes",
      description: "Hides nodes marked as experimental unless explicitly enabled",
      predicate: /* @__PURE__ */ __name((nodeDef) => showExperimental.value || !nodeDef.experimental, "predicate")
    });
    registerNodeDefFilter({
      id: "core.subgraph",
      name: "Hide Subgraph Nodes",
      description: "Temporarily hides subgraph nodes from node library and search",
      predicate: /* @__PURE__ */ __name((nodeDef) => {
        return !(nodeDef.category === "subgraph" && nodeDef.python_module === "nodes");
      }, "predicate")
    });
  }
  __name(registerCoreNodeDefFilters, "registerCoreNodeDefFilters");
  registerCoreNodeDefFilters();
  return {
    nodeDefsByName,
    nodeDefsByDisplayName,
    showDeprecated,
    showExperimental,
    nodeDefFilters,
    nodeDefs,
    nodeDataTypes,
    visibleNodeDefs,
    nodeSearchService,
    nodeTree,
    updateNodeDefs,
    addNodeDef,
    fromLGraphNode,
    getInputSpecForWidget,
    registerNodeDefFilter,
    unregisterNodeDefFilter
  };
});
const useNodeFrequencyStore = defineStore("nodeFrequency", () => {
  const topNodeDefLimit = ref(64);
  const nodeFrequencyLookup = ref({});
  const nodeNamesByFrequency = computed(
    () => Object.keys(nodeFrequencyLookup.value)
  );
  const isLoaded = ref(false);
  const loadNodeFrequencies = /* @__PURE__ */ __name(async () => {
    if (!isLoaded.value) {
      try {
        const response = await axios.get("assets/sorted-custom-node-map.json");
        nodeFrequencyLookup.value = response.data;
        isLoaded.value = true;
      } catch (error) {
        console.error("Error loading node frequencies:", error);
      }
    }
  }, "loadNodeFrequencies");
  const getNodeFrequency = /* @__PURE__ */ __name((nodeDef) => {
    return getNodeFrequencyByName(nodeDef.name);
  }, "getNodeFrequency");
  const getNodeFrequencyByName = /* @__PURE__ */ __name((nodeName) => {
    return nodeFrequencyLookup.value[nodeName] ?? 0;
  }, "getNodeFrequencyByName");
  const nodeDefStore = useNodeDefStore();
  const topNodeDefs = computed(() => {
    return nodeNamesByFrequency.value.map((nodeName) => nodeDefStore.nodeDefsByName[nodeName]).filter((nodeDef) => nodeDef !== void 0).slice(0, topNodeDefLimit.value);
  });
  return {
    nodeNamesByFrequency,
    topNodeDefs,
    isLoaded,
    loadNodeFrequencies,
    getNodeFrequency,
    getNodeFrequencyByName
  };
});
const TelemetryEvents = {
  // Authentication Flow
  USER_SIGN_UP_OPENED: "app:user_sign_up_opened",
  USER_AUTH_COMPLETED: "app:user_auth_completed",
  USER_LOGGED_IN: "app:user_logged_in",
  // Subscription Flow
  RUN_BUTTON_CLICKED: "app:run_button_click",
  SUBSCRIPTION_REQUIRED_MODAL_OPENED: "app:subscription_required_modal_opened",
  SUBSCRIBE_NOW_BUTTON_CLICKED: "app:subscribe_now_button_clicked",
  MONTHLY_SUBSCRIPTION_SUCCEEDED: "app:monthly_subscription_succeeded",
  MONTHLY_SUBSCRIPTION_CANCELLED: "app:monthly_subscription_cancelled",
  ADD_API_CREDIT_BUTTON_CLICKED: "app:add_api_credit_button_clicked",
  API_CREDIT_TOPUP_BUTTON_PURCHASE_CLICKED: "app:api_credit_topup_button_purchase_clicked",
  API_CREDIT_TOPUP_SUCCEEDED: "app:api_credit_topup_succeeded",
  // Onboarding Survey
  USER_SURVEY_OPENED: "app:user_survey_opened",
  USER_SURVEY_SUBMITTED: "app:user_survey_submitted",
  // Email Verification
  USER_EMAIL_VERIFY_OPENED: "app:user_email_verify_opened",
  USER_EMAIL_VERIFY_REQUESTED: "app:user_email_verify_requested",
  USER_EMAIL_VERIFY_COMPLETED: "app:user_email_verify_completed",
  // Template Tracking
  TEMPLATE_WORKFLOW_OPENED: "app:template_workflow_opened",
  TEMPLATE_LIBRARY_OPENED: "app:template_library_opened",
  TEMPLATE_LIBRARY_CLOSED: "app:template_library_closed",
  // Workflow Management
  WORKFLOW_IMPORTED: "app:workflow_imported",
  WORKFLOW_OPENED: "app:workflow_opened",
  // Page Visibility
  PAGE_VISIBILITY_CHANGED: "app:page_visibility_changed",
  // Tab Tracking
  TAB_COUNT_TRACKING: "app:tab_count_tracking",
  // Node Search Analytics
  NODE_SEARCH: "app:node_search",
  NODE_SEARCH_RESULT_SELECTED: "app:node_search_result_selected",
  // Template Filter Analytics
  TEMPLATE_FILTER_CHANGED: "app:template_filter_changed",
  // Settings
  SETTING_CHANGED: "app:setting_changed",
  // Help Center Analytics
  HELP_CENTER_OPENED: "app:help_center_opened",
  HELP_RESOURCE_CLICKED: "app:help_resource_clicked",
  HELP_CENTER_CLOSED: "app:help_center_closed",
  // Workflow Creation
  WORKFLOW_CREATED: "app:workflow_created",
  // Execution Lifecycle
  EXECUTION_START: "execution_start",
  EXECUTION_ERROR: "execution_error",
  EXECUTION_SUCCESS: "execution_success",
  // Generic UI Button Click
  UI_BUTTON_CLICKED: "app:ui_button_clicked"
};
const INDUSTRY_CATEGORIES = [
  {
    name: "Film / TV / Animation",
    userCount: 2885,
    keywords: [
      "film",
      "tv",
      "television",
      "animation",
      "animation studio",
      "tv production",
      "film production",
      "story",
      "anime",
      "video",
      "cinematography",
      "visual effects",
      "vfx",
      "vfx artist",
      "movie",
      "cinema",
      "documentary",
      "documentary filmmaker",
      "broadcast",
      "streaming",
      "production",
      "director",
      "filmmaker",
      "post-production",
      "editing"
    ]
  },
  {
    name: "Marketing / Advertising / Social Media",
    userCount: 1340,
    keywords: [
      "marketing",
      "advertising",
      "youtube",
      "tiktok",
      "social media",
      "content creation",
      "influencer",
      "brand",
      "promotion",
      "digital marketing",
      "seo",
      "campaigns",
      "copywriting",
      "growth",
      "engagement"
    ]
  },
  {
    name: "Software / IT / AI",
    userCount: 1100,
    keywords: [
      "software",
      "software development",
      "software engineer",
      "it",
      "ai",
      "ai research",
      "corporate ai research",
      "ai research lab",
      "tech company ai research",
      "developer",
      "app developer",
      "consulting",
      "tech",
      "tech startup",
      "programmer",
      "data science",
      "machine learning",
      "coding",
      "programming",
      "web development",
      "app development",
      "saas"
    ]
  },
  {
    name: "Product & Industrial Design",
    userCount: 1050,
    keywords: [
      "product design",
      "industrial",
      "manufacturing",
      "3d rendering",
      "product visualization",
      "mechanical",
      "automotive",
      "cad",
      "prototype",
      "design engineering",
      "invention"
    ]
  },
  {
    name: "Fine Art / Contemporary Art",
    userCount: 780,
    keywords: [
      "fine art",
      "art",
      "illustration",
      "contemporary",
      "artist",
      "painting",
      "drawing",
      "sculpture",
      "gallery",
      "canvas",
      "digital art",
      "mixed media",
      "abstract",
      "portrait"
    ]
  },
  {
    name: "Education / Research",
    userCount: 640,
    keywords: [
      "education",
      "student",
      "teacher",
      "research",
      "university research",
      "academic ai research",
      "university ai research",
      "ai research at university",
      "learning",
      "university",
      "school",
      "academic",
      "professor",
      "curriculum",
      "training",
      "instruction",
      "pedagogy"
    ]
  },
  {
    name: "Architecture / Engineering / Construction",
    userCount: 420,
    keywords: [
      "architecture",
      "architecture firm",
      "construction",
      "engineering",
      "civil",
      "civil engineering",
      "cad",
      "building",
      "structural",
      "landscape",
      "landscape architecture",
      "interior design",
      "real estate",
      "planning",
      "blueprints"
    ]
  },
  {
    name: "Gaming / Interactive Media",
    userCount: 410,
    keywords: [
      "gaming",
      "game dev",
      "game development",
      "indie game studio",
      "vr development",
      "roblox",
      "interactive",
      "interactive media",
      "virtual world",
      "vr",
      "ar",
      "metaverse",
      "simulation",
      "unity",
      "unity developer",
      "unreal",
      "indie games"
    ]
  },
  {
    name: "Photography / Videography",
    userCount: 70,
    keywords: [
      "photography",
      "photo",
      "videography",
      "camera",
      "image",
      "portrait",
      "wedding",
      "commercial photo",
      "stock photography",
      "photojournalism",
      "event photography"
    ]
  },
  {
    name: "Fashion / Beauty / Retail",
    userCount: 25,
    keywords: [
      "fashion",
      "fashion design",
      "beauty",
      "beauty industry",
      "jewelry",
      "jewelry design",
      "custom jewelry design",
      "retail",
      "retail store",
      "style",
      "clothing",
      "cosmetics",
      "makeup",
      "accessories",
      "boutique"
    ]
  },
  {
    name: "Music / Performing Arts",
    userCount: 25,
    keywords: [
      "music",
      "music production",
      "vj",
      "dance",
      "projection mapping",
      "audio visual",
      "concert",
      "concert production",
      "performance",
      "theater",
      "stage",
      "live events"
    ]
  },
  {
    name: "Healthcare / Medical / Life Science",
    userCount: 30,
    keywords: [
      "healthcare",
      "medical",
      "medical research",
      "doctor",
      "biotech",
      "life science",
      "pharmaceutical",
      "clinical",
      "clinical research",
      "hospital",
      "medicine",
      "health"
    ]
  },
  {
    name: "E-commerce / Print-on-Demand / Business",
    userCount: 15,
    keywords: [
      "ecommerce",
      "e-commerce",
      "print on demand",
      "shop",
      "business",
      "commercial",
      "startup",
      "entrepreneur",
      "sales",
      "online store"
    ]
  },
  {
    name: "Nonprofit / Government / Public Sector",
    userCount: 15,
    keywords: [
      "501c3",
      "ngo",
      "government",
      "public service",
      "policy",
      "nonprofit",
      "charity",
      "civic",
      "community",
      "social impact"
    ]
  },
  {
    name: "Adult / NSFW",
    userCount: 10,
    keywords: [
      "nsfw",
      "nsfw content",
      "adult",
      "adult entertainment",
      "erotic",
      "explicit",
      "xxx",
      "porn"
    ]
  }
];
const USE_CASE_CATEGORIES = [
  {
    name: "Content Creation & Marketing",
    keywords: [
      "content creation",
      "social media",
      "marketing",
      "marketing campaigns",
      "advertising",
      "youtube",
      "youtube thumbnail",
      "youtube thumbnail generation",
      "tiktok",
      "instagram",
      "thumbnails",
      "posts",
      "campaigns",
      "brand content"
    ]
  },
  {
    name: "Art & Illustration",
    keywords: [
      "art",
      "illustration",
      "drawing",
      "painting",
      "concept art",
      "creating concept art",
      "character design",
      "digital art",
      "fantasy art",
      "portraits"
    ]
  },
  {
    name: "Product Visualization & Design",
    keywords: [
      "product",
      "product mockup",
      "product mockup creation",
      "visualization",
      "prototype visualization",
      "design",
      "prototype",
      "mockup",
      "3d rendering",
      "industrial design",
      "product photos"
    ]
  },
  {
    name: "Film & Video Production",
    keywords: [
      "film",
      "video",
      "video editing",
      "movie",
      "movie production",
      "animation",
      "vfx",
      "visual effects",
      "storyboard",
      "storyboard creation",
      "cinematography",
      "post production"
    ]
  },
  {
    name: "Gaming & Interactive Media",
    keywords: [
      "game",
      "gaming",
      "game asset generation",
      "game assets",
      "game development",
      "game textures",
      "interactive",
      "vr",
      "vr content creation",
      "ar",
      "virtual",
      "simulation",
      "metaverse",
      "textures"
    ]
  },
  {
    name: "Architecture & Construction",
    keywords: [
      "architecture",
      "architectural rendering",
      "building",
      "building visualization",
      "construction",
      "interior design",
      "interior design mockups",
      "landscape",
      "real estate",
      "real estate visualization",
      "floor plans",
      "renderings"
    ]
  },
  {
    name: "Education & Training",
    keywords: [
      "education",
      "educational",
      "educational content",
      "training",
      "training materials",
      "learning",
      "teaching",
      "tutorial",
      "tutorial creation",
      "course",
      "academic",
      "academic projects",
      "instructional",
      "workshops"
    ]
  },
  {
    name: "Research & Development",
    keywords: [
      "research",
      "research experiments",
      "development",
      "experiment",
      "prototype",
      "prototype testing",
      "testing",
      "analysis",
      "study",
      "innovation",
      "innovation projects",
      "r&d",
      "scientific visualization"
    ]
  },
  {
    name: "Personal & Hobby",
    keywords: [
      "personal",
      "personal art projects",
      "hobby",
      "hobby work",
      "fun",
      "fun experiments",
      "experiment",
      "learning",
      "curiosity",
      "explore",
      "creative",
      "creative exploration",
      "side project"
    ]
  },
  {
    name: "Photography & Image Processing",
    keywords: [
      "photography",
      "product photography",
      "portrait photography",
      "photo",
      "photo editing",
      "image",
      "image enhancement",
      "portrait",
      "editing",
      "enhancement",
      "restoration",
      "photo manipulation"
    ]
  }
];
const FUSE_OPTIONS = {
  keys: ["keywords"],
  threshold: 0.53,
  // Higher = more lenient matching
  minMatchCharLength: 5,
  includeScore: true,
  includeMatches: true,
  ignoreLocation: true,
  findAllMatches: true
};
new Fuse(INDUSTRY_CATEGORIES, FUSE_OPTIONS);
new Fuse(USE_CASE_CATEGORIES, FUSE_OPTIONS);
new Set(
  Object.values(TelemetryEvents)
);
let _telemetryProvider = null;
function useTelemetry() {
  return _telemetryProvider;
}
__name(useTelemetry, "useTelemetry");
const subgraphNodeIdToSubgraph = /* @__PURE__ */ __name((id2, graph) => {
  const node2 = graph.getNodeById(id2);
  if (node2?.isSubgraphNode()) return node2.subgraph;
}, "subgraphNodeIdToSubgraph");
function getSubgraphsFromInstanceIds(currentGraph, subgraphNodeIds, subgraphs = []) {
  if (subgraphNodeIds.length === 1) return subgraphs;
  const currentPart = subgraphNodeIds.shift();
  if (currentPart === void 0) return subgraphs;
  const subgraph = subgraphNodeIdToSubgraph(currentPart, currentGraph);
  if (!subgraph) {
    console.warn(`Subgraph not found: ${currentPart}`);
    return void 0;
  }
  subgraphs.push(subgraph);
  return getSubgraphsFromInstanceIds(subgraph, subgraphNodeIds, subgraphs);
}
__name(getSubgraphsFromInstanceIds, "getSubgraphsFromInstanceIds");
function executionIdToNodeLocatorId(nodeId) {
  const nodeIdStr = String(nodeId);
  if (!nodeIdStr.includes(":")) {
    return nodeIdStr;
  }
  const parts2 = nodeIdStr.split(":");
  const localNodeId = parts2[parts2.length - 1];
  const subgraphs = getSubgraphsFromInstanceIds(app$1.rootGraph, parts2);
  if (!subgraphs) return void 0;
  const nodeLocatorId = createNodeLocatorId(subgraphs.at(-1).id, localNodeId);
  return nodeLocatorId;
}
__name(executionIdToNodeLocatorId, "executionIdToNodeLocatorId");
const useExecutionStore = defineStore("execution", () => {
  const workflowStore = useWorkflowStore();
  const canvasStore = useCanvasStore();
  const clientId = ref(null);
  const activePromptId = ref(null);
  const queuedPrompts = ref({});
  const lastNodeErrors = ref(null);
  const lastExecutionError = ref(null);
  const nodeProgressStates = ref({});
  const nodeProgressStatesByPrompt = ref({});
  const promptIdToWorkflowId = ref(/* @__PURE__ */ new Map());
  const initializingPromptIds = ref(/* @__PURE__ */ new Set());
  const mergeExecutionProgressStates = /* @__PURE__ */ __name((currentState, newState) => {
    if (currentState === void 0) {
      return newState;
    }
    const mergedState = { ...currentState };
    if (mergedState.state === "error") {
      return mergedState;
    } else if (newState.state === "running") {
      const newPerc = newState.max > 0 ? newState.value / newState.max : 0;
      const oldPerc = mergedState.max > 0 ? mergedState.value / mergedState.max : 0;
      if (mergedState.state !== "running" || oldPerc === 0 || newPerc < oldPerc) {
        mergedState.value = newState.value;
        mergedState.max = newState.max;
      }
      mergedState.state = "running";
    }
    return mergedState;
  }, "mergeExecutionProgressStates");
  const nodeLocationProgressStates = computed(() => {
    const result = {};
    const states = nodeProgressStates.value;
    for (const state2 of Object.values(states)) {
      const parts2 = String(state2.display_node_id).split(":");
      for (let i = 0; i < parts2.length; i++) {
        const executionId = parts2.slice(0, i + 1).join(":");
        const locatorId = executionIdToNodeLocatorId(executionId);
        if (!locatorId) continue;
        result[locatorId] = mergeExecutionProgressStates(
          result[locatorId],
          state2
        );
      }
    }
    return result;
  });
  const executingNodeIds = computed(() => {
    return Object.entries(nodeProgressStates.value).filter(([_, state2]) => state2.state === "running").map(([nodeId, _]) => nodeId);
  });
  const executingNodeId = computed(() => {
    return executingNodeIds.value[0] ?? null;
  });
  const uniqueExecutingNodeIdStrings = computed(
    () => new Set(executingNodeIds.value.map(String))
  );
  const executingNode = computed(() => {
    if (!executingNodeId.value) return null;
    const workflow = activePrompt.value?.workflow;
    if (!workflow) return null;
    const canvasState = workflow.changeTracker?.activeState ?? null;
    if (!canvasState) return null;
    return canvasState.nodes.find((n) => String(n.id) === executingNodeId.value) ?? null;
  });
  const _executingNodeProgress = ref(null);
  const executingNodeProgress = computed(
    () => _executingNodeProgress.value ? _executingNodeProgress.value.value / _executingNodeProgress.value.max : null
  );
  const activePrompt = computed(
    () => queuedPrompts.value[activePromptId.value ?? ""]
  );
  const totalNodesToExecute = computed(() => {
    if (!activePrompt.value) return 0;
    return Object.values(activePrompt.value.nodes).length;
  });
  const isIdle = computed(() => !activePromptId.value);
  const nodesExecuted = computed(() => {
    if (!activePrompt.value) return 0;
    return Object.values(activePrompt.value.nodes).filter(Boolean).length;
  });
  const executionProgress = computed(() => {
    if (!activePrompt.value) return 0;
    const total = totalNodesToExecute.value;
    const done = nodesExecuted.value;
    return total > 0 ? done / total : 0;
  });
  const lastExecutionErrorNodeLocatorId = computed(() => {
    const err = lastExecutionError.value;
    if (!err) return null;
    return executionIdToNodeLocatorId(String(err.node_id));
  });
  const lastExecutionErrorNodeId = computed(() => {
    const locator = lastExecutionErrorNodeLocatorId.value;
    if (!locator) return null;
    const localId = workflowStore.nodeLocatorIdToNodeId(locator);
    return localId != null ? String(localId) : null;
  });
  function bindExecutionEvents() {
    api.addEventListener("notification", handleNotification);
    api.addEventListener("execution_start", handleExecutionStart);
    api.addEventListener("execution_cached", handleExecutionCached);
    api.addEventListener("execution_interrupted", handleExecutionInterrupted);
    api.addEventListener("execution_success", handleExecutionSuccess);
    api.addEventListener("executed", handleExecuted);
    api.addEventListener("executing", handleExecuting);
    api.addEventListener("progress", handleProgress);
    api.addEventListener("progress_state", handleProgressState);
    api.addEventListener("status", handleStatus);
    api.addEventListener("execution_error", handleExecutionError);
    api.addEventListener("progress_text", handleProgressText);
  }
  __name(bindExecutionEvents, "bindExecutionEvents");
  function unbindExecutionEvents() {
    api.removeEventListener("notification", handleNotification);
    api.removeEventListener("execution_start", handleExecutionStart);
    api.removeEventListener("execution_cached", handleExecutionCached);
    api.removeEventListener("execution_interrupted", handleExecutionInterrupted);
    api.removeEventListener("execution_success", handleExecutionSuccess);
    api.removeEventListener("executed", handleExecuted);
    api.removeEventListener("executing", handleExecuting);
    api.removeEventListener("progress", handleProgress);
    api.removeEventListener("progress_state", handleProgressState);
    api.removeEventListener("status", handleStatus);
    api.removeEventListener("execution_error", handleExecutionError);
    api.removeEventListener("progress_text", handleProgressText);
  }
  __name(unbindExecutionEvents, "unbindExecutionEvents");
  function handleExecutionStart(e2) {
    lastExecutionError.value = null;
    activePromptId.value = e2.detail.prompt_id;
    queuedPrompts.value[activePromptId.value] ??= { nodes: {} };
    clearInitializationByPromptId(activePromptId.value);
  }
  __name(handleExecutionStart, "handleExecutionStart");
  function handleExecutionCached(e2) {
    if (!activePrompt.value) return;
    for (const n of e2.detail.nodes) {
      activePrompt.value.nodes[n] = true;
    }
  }
  __name(handleExecutionCached, "handleExecutionCached");
  function handleExecutionInterrupted(e2) {
    const pid = e2.detail.prompt_id;
    if (activePromptId.value)
      clearInitializationByPromptId(activePromptId.value);
    resetExecutionState(pid);
  }
  __name(handleExecutionInterrupted, "handleExecutionInterrupted");
  function handleExecuted(e2) {
    if (!activePrompt.value) return;
    activePrompt.value.nodes[e2.detail.node] = true;
  }
  __name(handleExecuted, "handleExecuted");
  function handleExecutionSuccess(e2) {
    const pid = e2.detail.prompt_id;
    resetExecutionState(pid);
  }
  __name(handleExecutionSuccess, "handleExecutionSuccess");
  function handleExecuting(e2) {
    _executingNodeProgress.value = null;
    if (!activePrompt.value) return;
    if (typeof e2.detail !== "string") {
      if (activePromptId.value) {
        delete queuedPrompts.value[activePromptId.value];
      }
      activePromptId.value = null;
    }
  }
  __name(handleExecuting, "handleExecuting");
  function handleProgressState(e2) {
    const { nodes, prompt_id: pid } = e2.detail;
    const previousForPrompt = nodeProgressStatesByPrompt.value[pid] || {};
    for (const nodeId in nodes) {
      const nodeState = nodes[nodeId];
      if (nodeState.state === "running" && !previousForPrompt[nodeId]) {
        const { revokePreviewsByExecutionId } = useNodeOutputStore();
        revokePreviewsByExecutionId(nodeId);
      }
    }
    nodeProgressStatesByPrompt.value = {
      ...nodeProgressStatesByPrompt.value,
      [pid]: nodes
    };
    nodeProgressStates.value = nodes;
    if (executingNodeId.value && nodes[executingNodeId.value]) {
      const nodeState = nodes[executingNodeId.value];
      _executingNodeProgress.value = {
        value: nodeState.value,
        max: nodeState.max,
        prompt_id: nodeState.prompt_id,
        node: nodeState.display_node_id || nodeState.node_id
      };
    }
  }
  __name(handleProgressState, "handleProgressState");
  function handleProgress(e2) {
    _executingNodeProgress.value = e2.detail;
  }
  __name(handleProgress, "handleProgress");
  function handleStatus() {
    if (api.clientId) {
      clientId.value = api.clientId;
      api.removeEventListener("status", handleStatus);
    }
  }
  __name(handleStatus, "handleStatus");
  function handleExecutionError(e2) {
    lastExecutionError.value = e2.detail;
    const pid = e2.detail?.prompt_id;
    if (e2.detail?.prompt_id) clearInitializationByPromptId(e2.detail.prompt_id);
    resetExecutionState(pid);
  }
  __name(handleExecutionError, "handleExecutionError");
  function handleNotification(e2) {
    const payload = e2.detail;
    const text = payload?.value || "";
    const id2 = payload?.id ? payload.id : "";
    if (!id2) return;
    if (text.includes("Waiting for a machine")) {
      const next = new Set(initializingPromptIds.value);
      next.add(id2);
      initializingPromptIds.value = next;
    }
  }
  __name(handleNotification, "handleNotification");
  function clearInitializationByPromptId(promptId) {
    if (!promptId) return;
    if (!initializingPromptIds.value.has(promptId)) return;
    const next = new Set(initializingPromptIds.value);
    next.delete(promptId);
    initializingPromptIds.value = next;
  }
  __name(clearInitializationByPromptId, "clearInitializationByPromptId");
  function isPromptInitializing(promptId) {
    if (!promptId) return false;
    return initializingPromptIds.value.has(String(promptId));
  }
  __name(isPromptInitializing, "isPromptInitializing");
  function resetExecutionState(pid) {
    nodeProgressStates.value = {};
    const promptId = pid ?? activePromptId.value ?? null;
    if (promptId) {
      const map2 = { ...nodeProgressStatesByPrompt.value };
      delete map2[promptId];
      nodeProgressStatesByPrompt.value = map2;
    }
    if (activePromptId.value) {
      delete queuedPrompts.value[activePromptId.value];
    }
    activePromptId.value = null;
    _executingNodeProgress.value = null;
  }
  __name(resetExecutionState, "resetExecutionState");
  function getNodeIdIfExecuting(nodeId) {
    const nodeIdStr = String(nodeId);
    return nodeIdStr.includes(":") ? workflowStore.executionIdToCurrentId(nodeIdStr) : nodeIdStr;
  }
  __name(getNodeIdIfExecuting, "getNodeIdIfExecuting");
  function handleProgressText(e2) {
    const { nodeId, text } = e2.detail;
    if (!text || !nodeId) return;
    const currentId = getNodeIdIfExecuting(nodeId);
    const node2 = canvasStore.getCanvas().graph?.getNodeById(currentId);
    if (!node2) return;
    useNodeProgressText().showTextPreview(node2, text);
  }
  __name(handleProgressText, "handleProgressText");
  function storePrompt({
    nodes,
    id: id2,
    workflow
  }) {
    queuedPrompts.value[id2] ??= { nodes: {} };
    const queuedPrompt = queuedPrompts.value[id2];
    queuedPrompt.nodes = {
      ...nodes.reduce((p, n) => {
        p[n] = false;
        return p;
      }, {}),
      ...queuedPrompt.nodes
    };
    queuedPrompt.workflow = workflow;
    const wid = workflow?.activeState?.id ?? workflow?.initialState?.id;
    if (wid) {
      promptIdToWorkflowId.value.set(String(id2), String(wid));
    }
  }
  __name(storePrompt, "storePrompt");
  function registerPromptWorkflowIdMapping(promptId, workflowId) {
    if (!promptId || !workflowId) return;
    promptIdToWorkflowId.value.set(String(promptId), String(workflowId));
  }
  __name(registerPromptWorkflowIdMapping, "registerPromptWorkflowIdMapping");
  const nodeLocatorIdToExecutionId = /* @__PURE__ */ __name((locatorId) => {
    const executionId = workflowStore.nodeLocatorIdToNodeExecutionId(locatorId);
    return executionId;
  }, "nodeLocatorIdToExecutionId");
  const runningPromptIds = computed(() => {
    const result = [];
    for (const [pid, nodes] of Object.entries(
      nodeProgressStatesByPrompt.value
    )) {
      if (Object.values(nodes).some((n) => n.state === "running")) {
        result.push(pid);
      }
    }
    return result;
  });
  const runningWorkflowCount = computed(
    () => runningPromptIds.value.length
  );
  const nodeErrorsByLocatorId = computed(
    () => {
      if (!lastNodeErrors.value) return {};
      const map2 = {};
      for (const [executionId, nodeError] of Object.entries(
        lastNodeErrors.value
      )) {
        const locatorId = executionIdToNodeLocatorId(executionId);
        if (locatorId) {
          map2[locatorId] = nodeError;
        }
      }
      return map2;
    }
  );
  const getNodeErrors = /* @__PURE__ */ __name((nodeLocatorId) => {
    return nodeErrorsByLocatorId.value[nodeLocatorId];
  }, "getNodeErrors");
  const slotHasError = /* @__PURE__ */ __name((nodeLocatorId, slotName) => {
    const nodeError = getNodeErrors(nodeLocatorId);
    if (!nodeError) return false;
    return nodeError.errors.some((e2) => e2.extra_info?.input_name === slotName);
  }, "slotHasError");
  watch(lastNodeErrors, () => {
    if (!app$1.rootGraph) return;
    forEachNode(app$1.rootGraph, (node2) => {
      node2.has_errors = false;
      if (node2.inputs) {
        for (const slot of node2.inputs) {
          slot.hasErrors = false;
        }
      }
    });
    if (!lastNodeErrors.value) return;
    for (const [executionId, nodeError] of Object.entries(
      lastNodeErrors.value
    )) {
      const node2 = getNodeByExecutionId(app$1.rootGraph, executionId);
      if (!node2) continue;
      node2.has_errors = true;
      if (node2.inputs) {
        for (const error of nodeError.errors) {
          const slotName = error.extra_info?.input_name;
          if (!slotName) continue;
          const slot = node2.inputs.find((s) => s.name === slotName);
          if (slot) {
            slot.hasErrors = true;
          }
        }
      }
      const parts2 = executionId.split(":");
      for (let i = parts2.length - 1; i > 0; i--) {
        const parentExecutionId = parts2.slice(0, i).join(":");
        const parentNode = getNodeByExecutionId(
          app$1.rootGraph,
          parentExecutionId
        );
        if (parentNode) {
          parentNode.has_errors = true;
        }
      }
    }
  });
  return {
    isIdle,
    clientId,
    activePromptId,
    queuedPrompts,
    lastNodeErrors,
    lastExecutionError,
    lastExecutionErrorNodeId,
    executingNodeId,
    executingNodeIds,
    activePrompt,
    totalNodesToExecute,
    nodesExecuted,
    executionProgress,
    executingNode,
    executingNodeProgress,
    nodeProgressStates,
    nodeLocationProgressStates,
    nodeProgressStatesByPrompt,
    runningPromptIds,
    runningWorkflowCount,
    initializingPromptIds,
    isPromptInitializing,
    bindExecutionEvents,
    unbindExecutionEvents,
    storePrompt,
    registerPromptWorkflowIdMapping,
    uniqueExecutingNodeIdStrings,
    // Raw executing progress data for backward compatibility in ComfyApp.
    _executingNodeProgress,
    // NodeLocatorId conversion helpers
    executionIdToNodeLocatorId,
    nodeLocatorIdToExecutionId,
    promptIdToWorkflowId,
    // Node error lookup helpers
    getNodeErrors,
    slotHasError
  };
});
const PREVIEW_REVOKE_DELAY_MS = 400;
const createOutputs = /* @__PURE__ */ __name((filenames, type, isAnimated) => {
  return {
    images: filenames.map((image) => ({ type, ...parseFilePath(image) })),
    animated: filenames.map(
      (image) => isAnimated && (image.endsWith(".webp") || image.endsWith(".png"))
    )
  };
}, "createOutputs");
const useNodeOutputStore = defineStore("nodeOutput", () => {
  const { nodeIdToNodeLocatorId, nodeToNodeLocatorId } = useWorkflowStore();
  const { executionIdToNodeLocatorId: executionIdToNodeLocatorId2 } = useExecutionStore();
  const scheduledRevoke = {};
  const latestOutput = ref([]);
  function scheduleRevoke(locator, cb) {
    scheduledRevoke[locator]?.stop();
    const { stop } = useTimeoutFn(() => {
      delete scheduledRevoke[locator];
      cb();
    }, PREVIEW_REVOKE_DELAY_MS);
    scheduledRevoke[locator] = { stop };
  }
  __name(scheduleRevoke, "scheduleRevoke");
  const nodeOutputs = ref({});
  const nodePreviewImages = ref(
    app$1.nodePreviewImages || {}
  );
  function getNodeOutputs(node2) {
    return app$1.nodeOutputs[nodeToNodeLocatorId(node2)];
  }
  __name(getNodeOutputs, "getNodeOutputs");
  function getNodePreviews(node2) {
    return app$1.nodePreviewImages[nodeToNodeLocatorId(node2)];
  }
  __name(getNodePreviews, "getNodePreviews");
  const isImageOutputs = /* @__PURE__ */ __name((node2, outputs) => {
    if (node2.animatedImages || isVideoNode(node2)) return false;
    if (!outputs?.images?.length) return false;
    if (outputs.images.some((image) => image.filename?.endsWith("svg")))
      return false;
    return true;
  }, "isImageOutputs");
  function getPreviewParam(node2, outputs) {
    return isImageOutputs(node2, outputs) ? app$1.getPreviewFormatParam() : "";
  }
  __name(getPreviewParam, "getPreviewParam");
  function getNodeImageUrls(node2) {
    const previews = getNodePreviews(node2);
    if (previews?.length) return previews;
    const outputs = getNodeOutputs(node2);
    if (!outputs?.images?.length) return;
    const rand = app$1.getRandParam();
    const previewParam = getPreviewParam(node2, outputs);
    return outputs.images.map((image) => {
      const imgUrlPart = new URLSearchParams(image);
      return api.apiURL(`/view?${imgUrlPart}${previewParam}${rand}`);
    });
  }
  __name(getNodeImageUrls, "getNodeImageUrls");
  function setOutputsByLocatorId(nodeLocatorId, outputs, options = {}) {
    if (options.merge) {
      const existingOutput = app$1.nodeOutputs[nodeLocatorId];
      if (existingOutput && outputs) {
        for (const k in outputs) {
          const existingValue = existingOutput[k];
          const newValue2 = outputs[k];
          if (Array.isArray(existingValue) && Array.isArray(newValue2)) {
            existingOutput[k] = existingValue.concat(newValue2);
          } else {
            existingOutput[k] = newValue2;
          }
        }
        return;
      }
    }
    latestOutput.value = outputs?.images?.map((image) => {
      const imgUrlPart = new URLSearchParams(image);
      const rand = app$1.getRandParam();
      return api.apiURL(`/view?${imgUrlPart}${rand}`);
    }) ?? [];
    app$1.nodeOutputs[nodeLocatorId] = outputs;
    nodeOutputs.value[nodeLocatorId] = outputs;
  }
  __name(setOutputsByLocatorId, "setOutputsByLocatorId");
  function setNodeOutputs(node2, filenames, {
    folder = "input",
    isAnimated = false
  } = {}) {
    if (!filenames || !node2) return;
    const locatorId = nodeToNodeLocatorId(node2);
    if (!locatorId) return;
    if (typeof filenames === "string") {
      setOutputsByLocatorId(
        locatorId,
        createOutputs([filenames], folder, isAnimated)
      );
    } else if (!Array.isArray(filenames)) {
      setOutputsByLocatorId(locatorId, filenames);
    } else {
      const resultItems = createOutputs(filenames, folder, isAnimated);
      if (!resultItems?.images?.length) return;
      setOutputsByLocatorId(locatorId, resultItems);
    }
  }
  __name(setNodeOutputs, "setNodeOutputs");
  function setNodeOutputsByExecutionId(executionId, outputs, options = {}) {
    const nodeLocatorId = executionIdToNodeLocatorId2(executionId);
    if (!nodeLocatorId) return;
    setOutputsByLocatorId(nodeLocatorId, outputs, options);
  }
  __name(setNodeOutputsByExecutionId, "setNodeOutputsByExecutionId");
  function setNodePreviewsByExecutionId(executionId, previewImages) {
    const nodeLocatorId = executionIdToNodeLocatorId2(executionId);
    if (!nodeLocatorId) return;
    if (scheduledRevoke[nodeLocatorId]) {
      scheduledRevoke[nodeLocatorId].stop();
      delete scheduledRevoke[nodeLocatorId];
    }
    latestOutput.value = previewImages;
    app$1.nodePreviewImages[nodeLocatorId] = previewImages;
    nodePreviewImages.value[nodeLocatorId] = previewImages;
  }
  __name(setNodePreviewsByExecutionId, "setNodePreviewsByExecutionId");
  function setNodePreviewsByNodeId(nodeId, previewImages) {
    const nodeLocatorId = nodeIdToNodeLocatorId(nodeId);
    if (scheduledRevoke[nodeLocatorId]) {
      scheduledRevoke[nodeLocatorId].stop();
      delete scheduledRevoke[nodeLocatorId];
    }
    app$1.nodePreviewImages[nodeLocatorId] = previewImages;
    nodePreviewImages.value[nodeLocatorId] = previewImages;
  }
  __name(setNodePreviewsByNodeId, "setNodePreviewsByNodeId");
  function revokePreviewsByExecutionId(executionId) {
    const nodeLocatorId = executionIdToNodeLocatorId2(executionId);
    if (!nodeLocatorId) return;
    scheduleRevoke(
      nodeLocatorId,
      () => revokePreviewsByLocatorId(nodeLocatorId)
    );
  }
  __name(revokePreviewsByExecutionId, "revokePreviewsByExecutionId");
  function revokePreviewsByLocatorId(nodeLocatorId) {
    const previews = app$1.nodePreviewImages[nodeLocatorId];
    if (!previews?.[Symbol.iterator]) return;
    for (const url of previews) {
      URL.revokeObjectURL(url);
    }
    delete app$1.nodePreviewImages[nodeLocatorId];
    delete nodePreviewImages.value[nodeLocatorId];
  }
  __name(revokePreviewsByLocatorId, "revokePreviewsByLocatorId");
  function revokeAllPreviews() {
    for (const nodeLocatorId of Object.keys(app$1.nodePreviewImages)) {
      const previews = app$1.nodePreviewImages[nodeLocatorId];
      if (!previews?.[Symbol.iterator]) continue;
      for (const url of previews) {
        URL.revokeObjectURL(url);
      }
    }
    app$1.nodePreviewImages = {};
    nodePreviewImages.value = {};
  }
  __name(revokeAllPreviews, "revokeAllPreviews");
  function revokeSubgraphPreviews(subgraphNode) {
    const graphId = subgraphNode.graph.isRootGraph ? "" : subgraphNode.graph.id + ":";
    revokePreviewsByLocatorId(graphId + subgraphNode.id);
    for (const node2 of subgraphNode.subgraph.nodes) {
      revokePreviewsByLocatorId(subgraphNode.subgraph.id + node2.id);
    }
  }
  __name(revokeSubgraphPreviews, "revokeSubgraphPreviews");
  function removeNodeOutputs(nodeId) {
    const nodeLocatorId = nodeIdToNodeLocatorId(Number(nodeId));
    if (!nodeLocatorId) return false;
    const hadOutputs = !!app$1.nodeOutputs[nodeLocatorId];
    delete app$1.nodeOutputs[nodeLocatorId];
    delete nodeOutputs.value[nodeLocatorId];
    if (app$1.nodePreviewImages[nodeLocatorId]) {
      delete app$1.nodePreviewImages[nodeLocatorId];
      delete nodePreviewImages.value[nodeLocatorId];
    }
    return hadOutputs;
  }
  __name(removeNodeOutputs, "removeNodeOutputs");
  function restoreOutputs(outputs) {
    app$1.nodeOutputs = outputs;
    nodeOutputs.value = outputs;
  }
  __name(restoreOutputs, "restoreOutputs");
  function updateNodeImages(node2) {
    if (!node2.images?.length) return;
    const nodeLocatorId = nodeIdToNodeLocatorId(node2.id);
    if (nodeLocatorId) {
      const existingOutputs = app$1.nodeOutputs[nodeLocatorId];
      if (existingOutputs) {
        const updatedOutputs = {
          ...existingOutputs,
          images: node2.images
        };
        app$1.nodeOutputs[nodeLocatorId] = updatedOutputs;
        nodeOutputs.value[nodeLocatorId] = updatedOutputs;
      }
    }
  }
  __name(updateNodeImages, "updateNodeImages");
  function resetAllOutputsAndPreviews() {
    app$1.nodeOutputs = {};
    nodeOutputs.value = {};
    revokeAllPreviews();
  }
  __name(resetAllOutputsAndPreviews, "resetAllOutputsAndPreviews");
  return {
    // Getters
    getNodeOutputs,
    getNodeImageUrls,
    getNodePreviews,
    getPreviewParam,
    // Setters
    setNodeOutputs,
    setNodeOutputsByExecutionId,
    setNodePreviewsByExecutionId,
    setNodePreviewsByNodeId,
    updateNodeImages,
    // Cleanup
    revokePreviewsByExecutionId,
    revokeAllPreviews,
    revokeSubgraphPreviews,
    removeNodeOutputs,
    restoreOutputs,
    resetAllOutputsAndPreviews,
    // State
    nodeOutputs,
    nodePreviewImages,
    latestOutput
  };
});
function useSubgraphOperations() {
  const { getSelectedNodes } = useSelectedLiteGraphItems();
  const canvasStore = useCanvasStore();
  const workflowStore = useWorkflowStore();
  const nodeOutputStore = useNodeOutputStore();
  const nodeDefStore = useNodeDefStore();
  const nodeBookmarkStore = useNodeBookmarkStore();
  const convertToSubgraph = /* @__PURE__ */ __name(() => {
    const canvas2 = canvasStore.getCanvas();
    const graph = canvas2.subgraph ?? canvas2.graph;
    if (!graph) {
      return null;
    }
    const res = graph.convertToSubgraph(canvas2.selectedItems);
    if (!res) {
      return;
    }
    const { node: node2 } = res;
    canvas2.select(node2);
    canvasStore.updateSelectedItems();
    workflowStore.activeWorkflow?.changeTracker?.checkState();
  }, "convertToSubgraph");
  const doUnpack = /* @__PURE__ */ __name((subgraphNodes, skipMissingNodes) => {
    const canvas2 = canvasStore.getCanvas();
    const graph = canvas2.subgraph ?? canvas2.graph;
    if (!graph) return;
    for (const subgraphNode of subgraphNodes) {
      nodeOutputStore.revokeSubgraphPreviews(subgraphNode);
      graph.unpackSubgraph(subgraphNode, { skipMissingNodes });
    }
    workflowStore.activeWorkflow?.changeTracker?.checkState();
  }, "doUnpack");
  const unpackSubgraph = /* @__PURE__ */ __name(() => {
    const canvas2 = canvasStore.getCanvas();
    const graph = canvas2.subgraph ?? canvas2.graph;
    if (!graph) {
      return;
    }
    const selectedItems = Array.from(canvas2.selectedItems);
    const subgraphNodes = selectedItems.filter(
      (item) => item instanceof SubgraphNode
    );
    if (subgraphNodes.length === 0) {
      return;
    }
    doUnpack(subgraphNodes, true);
  }, "unpackSubgraph");
  const addSubgraphToLibrary = /* @__PURE__ */ __name(async () => {
    const selectedItems = Array.from(canvasStore.selectedItems);
    if (selectedItems.length === 1) {
      const item = selectedItems[0];
      if (isLGraphNode(item)) {
        const nodeDef = nodeDefStore.fromLGraphNode(item);
        if (nodeDef) {
          await nodeBookmarkStore.addBookmark(nodeDef.nodePath);
          return;
        }
      }
    }
    const selectedNodes = getSelectedNodes();
    if (selectedNodes.length === 0) {
      return;
    }
    const hasSubgraphs = selectedNodes.some(
      (node2) => node2 instanceof SubgraphNode
    );
    if (!hasSubgraphs) {
      convertToSubgraph();
      return;
    }
    for (const node2 of selectedNodes) {
      if (node2 instanceof SubgraphNode) {
        const nodeDef = nodeDefStore.fromLGraphNode(node2);
        if (nodeDef) {
          await nodeBookmarkStore.addBookmark(nodeDef.nodePath);
        }
      }
    }
  }, "addSubgraphToLibrary");
  const isSubgraphSelected = /* @__PURE__ */ __name(() => {
    const selectedItems = Array.from(canvasStore.selectedItems);
    return selectedItems.some((item) => item instanceof SubgraphNode);
  }, "isSubgraphSelected");
  const hasSelectableNodes = /* @__PURE__ */ __name(() => {
    return getSelectedNodes().length > 0;
  }, "hasSelectableNodes");
  return {
    convertToSubgraph,
    unpackSubgraph,
    addSubgraphToLibrary,
    isSubgraphSelected,
    hasSelectableNodes
  };
}
__name(useSubgraphOperations, "useSubgraphOperations");
function useNodeAnimatedImage() {
  function showAnimatedPreview(node2) {
    if (!node2.imgs?.length) return;
    if (!node2.widgets) return;
    const widgetIdx = node2.widgets.findIndex(
      (w) => w.name === ANIM_PREVIEW_WIDGET
    );
    node2.imgs[0].classList.value = "block size-full object-contain";
    if (widgetIdx > -1) {
      const widget = node2.widgets[widgetIdx];
      if (!isDOMWidget(widget)) return;
      widget.element.replaceChildren(node2.imgs[0]);
    } else {
      const element = document.createElement("div");
      element.appendChild(node2.imgs[0]);
      const widget = node2.addDOMWidget(ANIM_PREVIEW_WIDGET, "img", element, {
        hideOnZoom: false
      });
      node2.overIndex = 0;
      const { handleWheel, handlePointer, forwardEventToCanvas } = useCanvasInteractions();
      node2.imgs[0].style.pointerEvents = "none";
      element.addEventListener("wheel", handleWheel);
      element.addEventListener("pointermove", handlePointer);
      element.addEventListener("pointerup", handlePointer);
      element.addEventListener(
        "pointerdown",
        (e2) => {
          return e2.button !== 2 ? handlePointer(e2) : forwardEventToCanvas(e2);
        },
        true
      );
      widget.serialize = false;
      widget.serializeValue = () => void 0;
    }
  }
  __name(showAnimatedPreview, "showAnimatedPreview");
  function removeAnimatedPreview(node2) {
    if (!node2.widgets) return;
    const widgetIdx = node2.widgets.findIndex(
      (w) => w.name === ANIM_PREVIEW_WIDGET
    );
    if (widgetIdx > -1) {
      node2.widgets[widgetIdx].onRemove?.();
      node2.widgets.splice(widgetIdx, 1);
    }
  }
  __name(removeAnimatedPreview, "removeAnimatedPreview");
  return {
    showAnimatedPreview,
    removeAnimatedPreview
  };
}
__name(useNodeAnimatedImage, "useNodeAnimatedImage");
function calculateImageGrid(imgs, dw, dh) {
  let best = 0;
  let w = imgs[0].naturalWidth;
  let h2 = imgs[0].naturalHeight;
  const numImages = imgs.length;
  let cellWidth, cellHeight, cols, rows, shiftX;
  for (let c2 = 1; c2 <= numImages; c2++) {
    const r = Math.ceil(numImages / c2);
    const cW = dw / c2;
    const cH = dh / r;
    const scaleX = cW / w;
    const scaleY = cH / h2;
    const scale = Math.min(scaleX, scaleY, 1);
    const imageW = w * scale;
    const imageH = h2 * scale;
    const area = imageW * imageH * numImages;
    if (area > best) {
      best = area;
      cellWidth = imageW;
      cellHeight = imageH;
      cols = c2;
      rows = r;
      shiftX = c2 * ((cW - imageW) / 2);
    }
  }
  return { cellWidth, cellHeight, cols, rows, shiftX };
}
__name(calculateImageGrid, "calculateImageGrid");
function createImageHost(node2) {
  const el = $el("div.comfy-img-preview");
  let currentImgs;
  let first = true;
  function updateSize() {
    let w = null;
    let h2 = null;
    if (currentImgs) {
      let elH = el.clientHeight;
      if (first) {
        first = false;
        if (elH < 190) {
          elH = 190;
        }
        el.style.setProperty("--comfy-widget-min-height", elH.toString());
      } else {
        el.style.setProperty("--comfy-widget-min-height", null);
      }
      const nw = node2.size[0];
      ({ cellWidth: w, cellHeight: h2 } = calculateImageGrid(
        currentImgs,
        nw - 20,
        elH
      ));
      w += "px";
      h2 += "px";
      el.style.setProperty("--comfy-img-preview-width", w);
      el.style.setProperty("--comfy-img-preview-height", h2);
    }
  }
  __name(updateSize, "updateSize");
  return {
    el,
    getCurrentImage() {
      return currentImgs?.[0];
    },
    // @ts-expect-error fixme ts strict error
    updateImages(imgs) {
      if (imgs !== currentImgs) {
        if (currentImgs == null) {
          requestAnimationFrame(() => {
            updateSize();
          });
        }
        el.replaceChildren(...imgs);
        currentImgs = imgs;
        node2.onResize?.(node2.size);
        node2.graph?.setDirtyCanvas(true, true);
      }
    },
    getHeight() {
      updateSize();
    },
    onDraw() {
      el.style.pointerEvents = "all";
      const over = document.elementFromPoint(
        app$1.canvas.mouse[0],
        app$1.canvas.mouse[1]
      );
      el.style.pointerEvents = "none";
      if (!over) return;
      const idx = currentImgs.indexOf(over);
      node2.overIndex = idx;
    }
  };
}
__name(createImageHost, "createImageHost");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.imagePreview = window.comfyAPI.imagePreview || {};
window.comfyAPI.imagePreview.calculateImageGrid = calculateImageGrid;
window.comfyAPI.imagePreview.createImageHost = createImageHost;
let deferredImageRenders = [];
let deferredRenderScheduled = false;
function scheduleDeferredImageRender() {
  if (deferredRenderScheduled) return;
  deferredRenderScheduled = true;
  queueMicrotask(() => {
    const renders = deferredImageRenders;
    deferredImageRenders = [];
    deferredRenderScheduled = false;
    for (const render2 of renders) {
      render2();
    }
  });
}
__name(scheduleDeferredImageRender, "scheduleDeferredImageRender");
const renderPreview = /* @__PURE__ */ __name((ctx, node2, shiftY, computedHeight) => {
  const canvas2 = useCanvasStore().getCanvas();
  const mouse = canvas2.graph_mouse;
  if (!canvas2.pointer_is_down && node2.pointerDown) {
    if (mouse[0] === node2.pointerDown.pos[0] && mouse[1] === node2.pointerDown.pos[1]) {
      node2.imageIndex = node2.pointerDown.index;
    }
    node2.pointerDown = null;
  }
  const imgs = node2.imgs ?? [];
  let { imageIndex } = node2;
  const numImages = imgs.length;
  if (numImages === 1 && !imageIndex) {
    node2.imageIndex = imageIndex = 0;
  }
  const settingStore = useSettingStore();
  const allowImageSizeDraw = settingStore.get("Comfy.Node.AllowImageSizeDraw");
  const IMAGE_TEXT_SIZE_TEXT_HEIGHT = allowImageSizeDraw ? 15 : 0;
  const dw = node2.size[0];
  const dh = computedHeight ? computedHeight - IMAGE_TEXT_SIZE_TEXT_HEIGHT : 0;
  if (imageIndex == null) {
    let cellWidth;
    let cellHeight;
    let shiftX;
    let cell_padding;
    let cols;
    const compact_mode = is_all_same_aspect_ratio(imgs);
    if (!compact_mode) {
      cell_padding = 2;
      const largestDimension = imgs.reduce(
        (acc, current) => Math.max(acc, current.naturalWidth, current.naturalHeight),
        0
      );
      const fakeImgs = [];
      fakeImgs.length = imgs.length;
      fakeImgs[0] = {
        naturalWidth: largestDimension,
        naturalHeight: largestDimension
      };
      ({ cellWidth, cellHeight, cols, shiftX } = calculateImageGrid(
        fakeImgs,
        dw,
        dh
      ));
    } else {
      cell_padding = 0;
      ({ cellWidth, cellHeight, cols, shiftX } = calculateImageGrid(
        imgs,
        dw,
        dh
      ));
    }
    let anyHovered = false;
    node2.imageRects = [];
    for (let i = 0; i < numImages; i++) {
      const img2 = imgs[i];
      const row = Math.floor(i / cols);
      const col = i % cols;
      const x22 = col * cellWidth + shiftX;
      const y2 = row * cellHeight + shiftY;
      if (!anyHovered) {
        anyHovered = LiteGraph.isInsideRectangle(
          mouse[0],
          mouse[1],
          x22 + node2.pos[0],
          y2 + node2.pos[1],
          cellWidth,
          cellHeight
        );
        if (anyHovered) {
          node2.overIndex = i;
          let value = 110;
          if (canvas2.pointer_is_down) {
            if (!node2.pointerDown || node2.pointerDown.index !== i) {
              node2.pointerDown = { index: i, pos: [...mouse] };
            }
            value = 125;
          }
          ctx.filter = `contrast(${value}%) brightness(${value}%)`;
          canvas2.canvas.style.cursor = "pointer";
        }
      }
      node2.imageRects.push([x22, y2, cellWidth, cellHeight]);
      const wratio = cellWidth / img2.width;
      const hratio = cellHeight / img2.height;
      const ratio = Math.min(wratio, hratio);
      const imgHeight = ratio * img2.height;
      const imgY = row * cellHeight + shiftY + (cellHeight - imgHeight) / 2;
      const imgWidth = ratio * img2.width;
      const imgX = col * cellWidth + shiftX + (cellWidth - imgWidth) / 2;
      const transform2 = ctx.getTransform();
      const filter2 = ctx.filter;
      const drawParams = {
        img: img2,
        x: imgX + cell_padding,
        y: imgY + cell_padding,
        w: imgWidth - cell_padding * 2,
        h: imgHeight - cell_padding * 2
      };
      deferredImageRenders.push(() => {
        ctx.save();
        ctx.setTransform(transform2);
        ctx.filter = filter2;
        ctx.drawImage(
          drawParams.img,
          drawParams.x,
          drawParams.y,
          drawParams.w,
          drawParams.h
        );
        ctx.restore();
      });
      scheduleDeferredImageRender();
      if (!compact_mode) {
        ctx.strokeStyle = "#8F8F8F";
        ctx.lineWidth = 1;
        ctx.strokeRect(
          x22 + cell_padding,
          y2 + cell_padding,
          cellWidth - cell_padding * 2,
          cellHeight - cell_padding * 2
        );
      }
      ctx.filter = "none";
    }
    if (!anyHovered) {
      node2.pointerDown = null;
      node2.overIndex = null;
    }
    return;
  }
  const img = imgs[imageIndex];
  let w = img.naturalWidth;
  let h2 = img.naturalHeight;
  const scaleX = dw / w;
  const scaleY = dh / h2;
  const scale = Math.min(scaleX, scaleY, 1);
  w *= scale;
  h2 *= scale;
  const x2 = (dw - w) / 2;
  const y = (dh - h2) / 2 + shiftY;
  const transform = ctx.getTransform();
  deferredImageRenders.push(() => {
    ctx.save();
    ctx.setTransform(transform);
    ctx.drawImage(img, x2, y, w, h2);
    ctx.restore();
  });
  scheduleDeferredImageRender();
  if (allowImageSizeDraw) {
    ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR;
    ctx.textAlign = "center";
    ctx.font = "10px sans-serif";
    const sizeText = `${Math.round(img.naturalWidth)} × ${Math.round(img.naturalHeight)}`;
    const textY = y + h2 + 10;
    ctx.fillText(sizeText, x2 + w / 2, textY);
  }
  const drawButton = /* @__PURE__ */ __name((x22, y2, sz, text) => {
    const hovered = LiteGraph.isInsideRectangle(
      mouse[0],
      mouse[1],
      x22 + node2.pos[0],
      y2 + node2.pos[1],
      sz,
      sz
    );
    let fill = "#333";
    let textFill = "#fff";
    let isClicking = false;
    if (hovered) {
      canvas2.canvas.style.cursor = "pointer";
      if (canvas2.pointer_is_down) {
        fill = "#1e90ff";
        isClicking = true;
      } else {
        fill = "#eee";
        textFill = "#000";
      }
    }
    deferredImageRenders.push(() => {
      ctx.save();
      ctx.setTransform(transform);
      ctx.fillStyle = fill;
      ctx.beginPath();
      ctx.roundRect(x22, y2, sz, sz, [4]);
      ctx.fill();
      ctx.fillStyle = textFill;
      ctx.font = "12px Inter, sans-serif";
      ctx.textAlign = "center";
      ctx.fillText(text, x22 + 15, y2 + 20);
      ctx.restore();
    });
    return isClicking;
  }, "drawButton");
  if (!(numImages > 1)) return;
  const imageNum = (node2.imageIndex ?? 0) + 1;
  if (drawButton(dw - 40, dh + shiftY - 40, 30, `${imageNum}/${numImages}`)) {
    const i = imageNum >= numImages ? 0 : imageNum;
    if (!node2.pointerDown || node2.pointerDown.index !== i) {
      node2.pointerDown = { index: i, pos: [...mouse] };
    }
  }
  if (drawButton(dw - 40, shiftY + 10, 30, `x`)) {
    if (!node2.pointerDown || node2.pointerDown.index !== null) {
      node2.pointerDown = { index: null, pos: [...mouse] };
    }
  }
}, "renderPreview");
class ImagePreviewWidget extends BaseWidget {
  static {
    __name(this, "ImagePreviewWidget");
  }
  constructor(node2, name2, options) {
    const widget = {
      name: name2,
      options,
      type: "custom",
      /** Dummy value to satisfy type requirements. */
      value: "",
      y: 0
    };
    super(widget, node2);
    this.serialize = false;
  }
  drawWidget(ctx) {
    renderPreview(ctx, this.node, this.y, this.computedHeight);
  }
  onPointerDown(pointer, node2) {
    pointer.onDragStart = () => {
      const { canvas: canvas2 } = app$1;
      const { graph } = canvas2;
      canvas2.emitBeforeChange();
      graph?.beforeChange();
      pointer.finally = () => {
        canvas2.isDragging = false;
        graph?.afterChange();
        canvas2.emitAfterChange();
      };
      canvas2.processSelect(node2, pointer.eDown);
      canvas2.isDragging = true;
    };
    pointer.onDragEnd = (e2) => {
      const { canvas: canvas2 } = app$1;
      if (e2.shiftKey || LiteGraph.alwaysSnapToGrid)
        canvas2.graph?.snapToGrid(canvas2.selectedItems);
      canvas2.setDirty(true, true);
    };
    return true;
  }
  onClick() {
  }
  computeLayoutSize() {
    return {
      minHeight: 220,
      minWidth: 1
    };
  }
}
const useImagePreviewWidget = /* @__PURE__ */ __name(() => {
  const widgetConstructor = /* @__PURE__ */ __name((node2, inputSpec) => {
    return node2.addCustomWidget(
      new ImagePreviewWidget(node2, inputSpec.name, {
        serialize: false,
        canvasOnly: true
      })
    );
  }, "widgetConstructor");
  return widgetConstructor;
}, "useImagePreviewWidget");
const CANVAS_IMAGE_PREVIEW_WIDGET = "$$canvas-image-preview";
function useNodeCanvasImagePreview() {
  const imagePreviewWidget = useImagePreviewWidget();
  function showCanvasImagePreview(node2) {
    if (!node2.imgs?.length) return;
    if (!node2.widgets?.find((w) => w.name === CANVAS_IMAGE_PREVIEW_WIDGET)) {
      imagePreviewWidget(node2, {
        type: "IMAGE_PREVIEW",
        name: CANVAS_IMAGE_PREVIEW_WIDGET
      });
    }
  }
  __name(showCanvasImagePreview, "showCanvasImagePreview");
  function removeCanvasImagePreview(node2) {
    if (!node2.widgets) return;
    const widgetIdx = node2.widgets.findIndex(
      (w) => w.name === CANVAS_IMAGE_PREVIEW_WIDGET
    );
    if (widgetIdx > -1) {
      node2.widgets[widgetIdx].onRemove?.();
      node2.widgets.splice(widgetIdx, 1);
    }
  }
  __name(removeCanvasImagePreview, "removeCanvasImagePreview");
  return {
    showCanvasImagePreview,
    removeCanvasImagePreview
  };
}
__name(useNodeCanvasImagePreview, "useNodeCanvasImagePreview");
const useRightSidePanelStore = defineStore("rightSidePanel", () => {
  const isOpen = ref(false);
  const activeTab = ref("parameters");
  const isEditingSubgraph = computed(() => activeTab.value === "subgraph");
  function openPanel(tab) {
    isOpen.value = true;
    if (tab) {
      activeTab.value = tab;
    }
  }
  __name(openPanel, "openPanel");
  function closePanel() {
    isOpen.value = false;
  }
  __name(closePanel, "closePanel");
  function togglePanel() {
    isOpen.value = !isOpen.value;
  }
  __name(togglePanel, "togglePanel");
  return {
    isOpen,
    activeTab,
    isEditingSubgraph,
    openPanel,
    closePanel,
    togglePanel
  };
});
function getOrderedInputSpecs(nodeDefImpl, inputs) {
  const orderedInputSpecs = [];
  if (!nodeDefImpl.input_order) {
    return Object.values(inputs);
  }
  if (nodeDefImpl.input_order.required) {
    for (const name2 of nodeDefImpl.input_order.required) {
      const inputSpec = inputs[name2];
      if (inputSpec && !inputSpec.isOptional) {
        orderedInputSpecs.push(inputSpec);
      }
    }
  }
  if (nodeDefImpl.input_order.optional) {
    for (const name2 of nodeDefImpl.input_order.optional) {
      const inputSpec = inputs[name2];
      if (inputSpec && inputSpec.isOptional) {
        orderedInputSpecs.push(inputSpec);
      }
    }
  }
  const processedNames = new Set(orderedInputSpecs.map((spec) => spec.name));
  for (const inputSpec of Object.values(inputs)) {
    if (!processedNames.has(inputSpec.name)) {
      orderedInputSpecs.push(inputSpec);
    }
  }
  return orderedInputSpecs;
}
__name(getOrderedInputSpecs, "getOrderedInputSpecs");
var BrushShape = /* @__PURE__ */ ((BrushShape2) => {
  BrushShape2["Arc"] = "arc";
  BrushShape2["Rect"] = "rect";
  return BrushShape2;
})(BrushShape || {});
var Tools = /* @__PURE__ */ ((Tools2) => {
  Tools2["MaskPen"] = "pen";
  Tools2["PaintPen"] = "rgbPaint";
  Tools2["Eraser"] = "eraser";
  Tools2["MaskBucket"] = "paintBucket";
  Tools2["MaskColorFill"] = "colorSelect";
  return Tools2;
})(Tools || {});
const allTools = [
  "pen",
  "rgbPaint",
  "eraser",
  "paintBucket",
  "colorSelect"
  /* MaskColorFill */
];
var CompositionOperation = /* @__PURE__ */ ((CompositionOperation2) => {
  CompositionOperation2["SourceOver"] = "source-over";
  CompositionOperation2["DestinationOut"] = "destination-out";
  return CompositionOperation2;
})(CompositionOperation || {});
var MaskBlendMode = /* @__PURE__ */ ((MaskBlendMode2) => {
  MaskBlendMode2["Black"] = "black";
  MaskBlendMode2["White"] = "white";
  MaskBlendMode2["Negative"] = "negative";
  return MaskBlendMode2;
})(MaskBlendMode || {});
var ColorComparisonMethod = /* @__PURE__ */ ((ColorComparisonMethod2) => {
  ColorComparisonMethod2["Simple"] = "simple";
  ColorComparisonMethod2["HSL"] = "hsl";
  ColorComparisonMethod2["LAB"] = "lab";
  return ColorComparisonMethod2;
})(ColorComparisonMethod || {});
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.types = window.comfyAPI.types || {};
window.comfyAPI.types.BrushShape = BrushShape;
window.comfyAPI.types.Tools = Tools;
window.comfyAPI.types.allTools = allTools;
window.comfyAPI.types.CompositionOperation = CompositionOperation;
window.comfyAPI.types.MaskBlendMode = MaskBlendMode;
window.comfyAPI.types.ColorComparisonMethod = ColorComparisonMethod;
function useCanvasHistory(maxStates = 20) {
  const store = useMaskEditorStore();
  const states = ref([]);
  const currentStateIndex = ref(-1);
  const initialized = ref(false);
  const canUndo = computed(
    () => states.value.length > 1 && currentStateIndex.value > 0
  );
  const canRedo = computed(() => {
    return states.value.length > 1 && currentStateIndex.value < states.value.length - 1;
  });
  const saveInitialState = /* @__PURE__ */ __name(() => {
    const maskCtx = store.maskCtx;
    const rgbCtx = store.rgbCtx;
    const maskCanvas = store.maskCanvas;
    const rgbCanvas = store.rgbCanvas;
    if (!maskCtx || !rgbCtx || !maskCanvas || !rgbCanvas) {
      requestAnimationFrame(saveInitialState);
      return;
    }
    if (!maskCanvas.width || !rgbCanvas.width) {
      requestAnimationFrame(saveInitialState);
      return;
    }
    states.value = [];
    const maskState = maskCtx.getImageData(
      0,
      0,
      maskCanvas.width,
      maskCanvas.height
    );
    const rgbState = rgbCtx.getImageData(
      0,
      0,
      rgbCanvas.width,
      rgbCanvas.height
    );
    states.value.push({ mask: maskState, rgb: rgbState });
    currentStateIndex.value = 0;
    initialized.value = true;
  }, "saveInitialState");
  const saveState = /* @__PURE__ */ __name((providedMaskData, providedRgbData) => {
    const maskCtx = store.maskCtx;
    const rgbCtx = store.rgbCtx;
    const maskCanvas = store.maskCanvas;
    const rgbCanvas = store.rgbCanvas;
    if (!maskCtx || !rgbCtx || !maskCanvas || !rgbCanvas) return;
    if (!initialized.value || currentStateIndex.value === -1) {
      saveInitialState();
      return;
    }
    states.value = states.value.slice(0, currentStateIndex.value + 1);
    let maskState;
    let rgbState;
    if (providedMaskData && providedRgbData) {
      maskState = providedMaskData;
      rgbState = providedRgbData;
    } else {
      maskState = maskCtx.getImageData(
        0,
        0,
        maskCanvas.width,
        maskCanvas.height
      );
      rgbState = rgbCtx.getImageData(0, 0, rgbCanvas.width, rgbCanvas.height);
    }
    states.value.push({ mask: maskState, rgb: rgbState });
    currentStateIndex.value++;
    if (states.value.length > maxStates) {
      const removed = states.value.shift();
      if (removed) {
        if (removed.mask instanceof ImageBitmap) removed.mask.close();
        if (removed.rgb instanceof ImageBitmap) removed.rgb.close();
      }
      currentStateIndex.value--;
    }
  }, "saveState");
  const undo = /* @__PURE__ */ __name(() => {
    if (!canUndo.value) {
      alert("No more undo states available");
      return;
    }
    currentStateIndex.value--;
    restoreState(states.value[currentStateIndex.value]);
  }, "undo");
  const redo = /* @__PURE__ */ __name(() => {
    if (!canRedo.value) {
      alert("No more redo states available");
      return;
    }
    currentStateIndex.value++;
    restoreState(states.value[currentStateIndex.value]);
  }, "redo");
  const restoreState = /* @__PURE__ */ __name((state2) => {
    const maskCtx = store.maskCtx;
    const rgbCtx = store.rgbCtx;
    if (!maskCtx || !rgbCtx) return;
    if (state2.mask instanceof ImageBitmap) {
      maskCtx.clearRect(0, 0, state2.mask.width, state2.mask.height);
      maskCtx.drawImage(state2.mask, 0, 0);
    } else {
      maskCtx.putImageData(state2.mask, 0, 0);
    }
    if (state2.rgb instanceof ImageBitmap) {
      rgbCtx.clearRect(0, 0, state2.rgb.width, state2.rgb.height);
      rgbCtx.drawImage(state2.rgb, 0, 0);
    } else {
      rgbCtx.putImageData(state2.rgb, 0, 0);
    }
  }, "restoreState");
  const clearStates = /* @__PURE__ */ __name(() => {
    states.value.forEach((state2) => {
      if (state2.mask instanceof ImageBitmap) state2.mask.close();
      if (state2.rgb instanceof ImageBitmap) state2.rgb.close();
    });
    states.value = [];
    currentStateIndex.value = -1;
    initialized.value = false;
  }, "clearStates");
  return {
    canUndo,
    canRedo,
    currentStateIndex,
    saveInitialState,
    saveState,
    undo,
    redo,
    clearStates
  };
}
__name(useCanvasHistory, "useCanvasHistory");
const useMaskEditorStore = defineStore("maskEditor", () => {
  const brushSettings = ref({
    type: BrushShape.Arc,
    size: 10,
    opacity: 0.7,
    hardness: 1,
    stepSize: 10
  });
  const maskBlendMode = ref(MaskBlendMode.Black);
  const activeLayer = ref("mask");
  const rgbColor = ref("#FF0000");
  const currentTool = ref(Tools.MaskPen);
  const isAdjustingBrush = ref(false);
  const paintBucketTolerance = ref(5);
  const fillOpacity = ref(100);
  const colorSelectTolerance = ref(20);
  const colorSelectLivePreview = ref(false);
  const colorComparisonMethod = ref(
    ColorComparisonMethod.Simple
  );
  const applyWholeImage = ref(false);
  const maskBoundary = ref(false);
  const maskTolerance = ref(0);
  const selectionOpacity = ref(100);
  const zoomRatio = ref(1);
  const displayZoomRatio = ref(1);
  const panOffset = ref({ x: 0, y: 0 });
  const cursorPoint = ref({ x: 0, y: 0 });
  const resetZoomTrigger = ref(0);
  const clearTrigger = ref(0);
  const maskCanvas = ref(null);
  const maskCtx = ref(null);
  const rgbCanvas = ref(null);
  const rgbCtx = ref(null);
  const imgCanvas = ref(null);
  const imgCtx = ref(null);
  const canvasContainer = ref(null);
  const canvasBackground = ref(null);
  const pointerZone = ref(null);
  const image = ref(null);
  const maskOpacity = ref(0.8);
  const brushVisible = ref(true);
  const isPanning = ref(false);
  const brushPreviewGradientVisible = ref(false);
  const canvasHistory = useCanvasHistory(20);
  const tgpuRoot = ref(null);
  watch(maskCanvas, (canvas2) => {
    if (canvas2) {
      maskCtx.value = canvas2.getContext("2d", { willReadFrequently: true });
    }
  });
  watch(rgbCanvas, (canvas2) => {
    if (canvas2) {
      rgbCtx.value = canvas2.getContext("2d", { willReadFrequently: true });
    }
  });
  watch(imgCanvas, (canvas2) => {
    if (canvas2) {
      imgCtx.value = canvas2.getContext("2d", { willReadFrequently: true });
    }
  });
  const canUndo = computed(() => {
    return canvasHistory.canUndo.value;
  });
  const canRedo = computed(() => {
    return canvasHistory.canRedo.value;
  });
  const maskColor = computed(() => {
    switch (maskBlendMode.value) {
      case MaskBlendMode.Black:
        return { r: 0, g: 0, b: 0 };
      case MaskBlendMode.White:
        return { r: 255, g: 255, b: 255 };
      case MaskBlendMode.Negative:
        return { r: 255, g: 255, b: 255 };
      default:
        return { r: 0, g: 0, b: 0 };
    }
  });
  function setBrushSize(size) {
    brushSettings.value.size = toolkit.clamp(size, 1, 500);
  }
  __name(setBrushSize, "setBrushSize");
  function setBrushOpacity(opacity) {
    brushSettings.value.opacity = toolkit.clamp(opacity, 0, 1);
  }
  __name(setBrushOpacity, "setBrushOpacity");
  function setBrushHardness(hardness) {
    brushSettings.value.hardness = toolkit.clamp(hardness, 0, 1);
  }
  __name(setBrushHardness, "setBrushHardness");
  function setBrushStepSize(step) {
    brushSettings.value.stepSize = toolkit.clamp(step, 1, 100);
  }
  __name(setBrushStepSize, "setBrushStepSize");
  function resetBrushToDefault() {
    brushSettings.value.type = BrushShape.Arc;
    brushSettings.value.size = 20;
    brushSettings.value.opacity = 1;
    brushSettings.value.hardness = 1;
    brushSettings.value.stepSize = 5;
  }
  __name(resetBrushToDefault, "resetBrushToDefault");
  function setPaintBucketTolerance(tolerance) {
    paintBucketTolerance.value = toolkit.clamp(tolerance, 0, 255);
  }
  __name(setPaintBucketTolerance, "setPaintBucketTolerance");
  function setFillOpacity(opacity) {
    fillOpacity.value = toolkit.clamp(opacity, 0, 100);
  }
  __name(setFillOpacity, "setFillOpacity");
  function setColorSelectTolerance(tolerance) {
    colorSelectTolerance.value = toolkit.clamp(tolerance, 0, 255);
  }
  __name(setColorSelectTolerance, "setColorSelectTolerance");
  function setMaskTolerance(tolerance) {
    maskTolerance.value = toolkit.clamp(tolerance, 0, 255);
  }
  __name(setMaskTolerance, "setMaskTolerance");
  function setSelectionOpacity(opacity) {
    selectionOpacity.value = toolkit.clamp(opacity, 0, 100);
  }
  __name(setSelectionOpacity, "setSelectionOpacity");
  function setZoomRatio(ratio) {
    zoomRatio.value = Math.max(0.1, Math.min(10, ratio));
  }
  __name(setZoomRatio, "setZoomRatio");
  function setPanOffset(offset) {
    panOffset.value = { ...offset };
  }
  __name(setPanOffset, "setPanOffset");
  function setCursorPoint(point) {
    cursorPoint.value = { ...point };
  }
  __name(setCursorPoint, "setCursorPoint");
  function resetZoom() {
    resetZoomTrigger.value++;
  }
  __name(resetZoom, "resetZoom");
  function triggerClear() {
    clearTrigger.value++;
  }
  __name(triggerClear, "triggerClear");
  function setMaskOpacity(opacity) {
    maskOpacity.value = toolkit.clamp(opacity, 0, 1);
  }
  __name(setMaskOpacity, "setMaskOpacity");
  function resetState() {
    brushSettings.value = {
      type: BrushShape.Arc,
      size: 10,
      opacity: 0.7,
      hardness: 1,
      stepSize: 5
    };
    maskBlendMode.value = MaskBlendMode.Black;
    activeLayer.value = "mask";
    rgbColor.value = "#FF0000";
    currentTool.value = Tools.MaskPen;
    isAdjustingBrush.value = false;
    paintBucketTolerance.value = 5;
    fillOpacity.value = 100;
    colorSelectTolerance.value = 20;
    colorSelectLivePreview.value = false;
    colorComparisonMethod.value = ColorComparisonMethod.Simple;
    applyWholeImage.value = false;
    maskBoundary.value = false;
    maskTolerance.value = 0;
    selectionOpacity.value = 100;
    zoomRatio.value = 1;
    panOffset.value = { x: 0, y: 0 };
    cursorPoint.value = { x: 0, y: 0 };
    maskOpacity.value = 0.8;
  }
  __name(resetState, "resetState");
  return {
    brushSettings,
    maskBlendMode,
    activeLayer,
    rgbColor,
    currentTool,
    isAdjustingBrush,
    paintBucketTolerance,
    fillOpacity,
    colorSelectTolerance,
    colorSelectLivePreview,
    colorComparisonMethod,
    applyWholeImage,
    maskBoundary,
    maskTolerance,
    selectionOpacity,
    zoomRatio,
    displayZoomRatio,
    panOffset,
    cursorPoint,
    resetZoomTrigger,
    maskCanvas,
    maskCtx,
    rgbCanvas,
    rgbCtx,
    imgCanvas,
    imgCtx,
    canvasContainer,
    canvasBackground,
    pointerZone,
    image,
    maskOpacity,
    canUndo,
    canRedo,
    maskColor,
    brushVisible,
    isPanning,
    brushPreviewGradientVisible,
    canvasHistory,
    tgpuRoot,
    setBrushSize,
    setBrushOpacity,
    setBrushHardness,
    setBrushStepSize,
    resetBrushToDefault,
    setPaintBucketTolerance,
    setFillOpacity,
    setColorSelectTolerance,
    setMaskTolerance,
    setSelectionOpacity,
    setZoomRatio,
    setPanOffset,
    setCursorPoint,
    resetZoom,
    triggerClear,
    clearTrigger,
    setMaskOpacity,
    resetState
  };
});
const getPixelAlpha = /* @__PURE__ */ __name((data, x2, y, width2) => {
  return data[(y * width2 + x2) * 4 + 3];
}, "getPixelAlpha");
const getPixelColor = /* @__PURE__ */ __name((data, x2, y, width2) => {
  const index2 = (y * width2 + x2) * 4;
  return {
    r: data[index2],
    g: data[index2 + 1],
    b: data[index2 + 2]
  };
}, "getPixelColor");
const setPixel = /* @__PURE__ */ __name((data, x2, y, width2, alpha, color2) => {
  const index2 = (y * width2 + x2) * 4;
  data[index2] = color2.r;
  data[index2 + 1] = color2.g;
  data[index2 + 2] = color2.b;
  data[index2 + 3] = alpha;
}, "setPixel");
const rgbToHSL = /* @__PURE__ */ __name((r, g2, b) => {
  r /= 255;
  g2 /= 255;
  b /= 255;
  const max = Math.max(r, g2, b);
  const min = Math.min(r, g2, b);
  let h2 = 0;
  let s = 0;
  const l = (max + min) / 2;
  if (max !== min) {
    const d2 = max - min;
    s = l > 0.5 ? d2 / (2 - max - min) : d2 / (max + min);
    switch (max) {
      case r:
        h2 = (g2 - b) / d2 + (g2 < b ? 6 : 0);
        break;
      case g2:
        h2 = (b - r) / d2 + 2;
        break;
      case b:
        h2 = (r - g2) / d2 + 4;
        break;
    }
    h2 /= 6;
  }
  return {
    h: h2 * 360,
    s: s * 100,
    l: l * 100
  };
}, "rgbToHSL");
const rgbToLab = /* @__PURE__ */ __name((rgb) => {
  let r = rgb.r / 255;
  let g2 = rgb.g / 255;
  let b = rgb.b / 255;
  r = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
  g2 = g2 > 0.04045 ? Math.pow((g2 + 0.055) / 1.055, 2.4) : g2 / 12.92;
  b = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
  r *= 100;
  g2 *= 100;
  b *= 100;
  const x2 = r * 0.4124 + g2 * 0.3576 + b * 0.1805;
  const y = r * 0.2126 + g2 * 0.7152 + b * 0.0722;
  const z2 = r * 0.0193 + g2 * 0.1192 + b * 0.9505;
  const xn = 95.047;
  const yn = 100;
  const zn = 108.883;
  const xyz = [x2 / xn, y / yn, z2 / zn];
  for (let i = 0; i < xyz.length; i++) {
    xyz[i] = xyz[i] > 8856e-6 ? Math.pow(xyz[i], 1 / 3) : 7.787 * xyz[i] + 16 / 116;
  }
  return {
    l: 116 * xyz[1] - 16,
    a: 500 * (xyz[0] - xyz[1]),
    b: 200 * (xyz[1] - xyz[2])
  };
}, "rgbToLab");
const isPixelInRangeSimple = /* @__PURE__ */ __name((pixel, target, tolerance) => {
  const distance2 = Math.sqrt(
    Math.pow(pixel.r - target.r, 2) + Math.pow(pixel.g - target.g, 2) + Math.pow(pixel.b - target.b, 2)
  );
  return distance2 <= tolerance;
}, "isPixelInRangeSimple");
const isPixelInRangeHSL = /* @__PURE__ */ __name((pixel, target, tolerance) => {
  const pixelHSL = rgbToHSL(pixel.r, pixel.g, pixel.b);
  const targetHSL = rgbToHSL(target.r, target.g, target.b);
  const hueDiff = Math.abs(pixelHSL.h - targetHSL.h);
  const satDiff = Math.abs(pixelHSL.s - targetHSL.s);
  const lightDiff = Math.abs(pixelHSL.l - targetHSL.l);
  const distance2 = Math.sqrt(
    Math.pow(hueDiff / 360 * 255, 2) + Math.pow(satDiff / 100 * 255, 2) + Math.pow(lightDiff / 100 * 255, 2)
  );
  return distance2 <= tolerance;
}, "isPixelInRangeHSL");
const isPixelInRangeLab = /* @__PURE__ */ __name((pixel, target, tolerance) => {
  const pixelLab = rgbToLab(pixel);
  const targetLab = rgbToLab(target);
  const deltaE = Math.sqrt(
    Math.pow(pixelLab.l - targetLab.l, 2) + Math.pow(pixelLab.a - targetLab.a, 2) + Math.pow(pixelLab.b - targetLab.b, 2)
  );
  const normalizedDeltaE = deltaE / 100 * 255;
  return normalizedDeltaE <= tolerance;
}, "isPixelInRangeLab");
const isPixelInRange = /* @__PURE__ */ __name((pixel, target, tolerance, method) => {
  switch (method) {
    case ColorComparisonMethod.Simple:
      return isPixelInRangeSimple(pixel, target, tolerance);
    case ColorComparisonMethod.HSL:
      return isPixelInRangeHSL(pixel, target, tolerance);
    case ColorComparisonMethod.LAB:
      return isPixelInRangeLab(pixel, target, tolerance);
    default:
      return isPixelInRangeSimple(pixel, target, tolerance);
  }
}, "isPixelInRange");
function useCanvasTools() {
  const store = useMaskEditorStore();
  const lastColorSelectPoint = ref(null);
  const paintBucketFill = /* @__PURE__ */ __name((point) => {
    const ctx = store.maskCtx;
    const canvas2 = store.maskCanvas;
    if (!ctx || !canvas2) return;
    const startX = Math.floor(point.x);
    const startY = Math.floor(point.y);
    const width2 = canvas2.width;
    const height = canvas2.height;
    if (startX < 0 || startX >= width2 || startY < 0 || startY >= height) {
      return;
    }
    const imageData = ctx.getImageData(0, 0, width2, height);
    const data = imageData.data;
    const targetAlpha = getPixelAlpha(data, startX, startY, width2);
    const isFillMode = targetAlpha !== 255;
    if (targetAlpha === -1) return;
    const maskColor = store.maskColor;
    const tolerance = store.paintBucketTolerance;
    const fillOpacity = Math.floor(store.fillOpacity / 100 * 255);
    const stack = [];
    const visited = new Uint8Array(width2 * height);
    const shouldProcessPixel = /* @__PURE__ */ __name((currentAlpha, targetAlpha2, tolerance2, isFillMode2) => {
      if (currentAlpha === -1) return false;
      if (isFillMode2) {
        return currentAlpha !== 255 && Math.abs(currentAlpha - targetAlpha2) <= tolerance2;
      } else {
        return currentAlpha === 255 || Math.abs(currentAlpha - targetAlpha2) <= tolerance2;
      }
    }, "shouldProcessPixel");
    if (shouldProcessPixel(targetAlpha, targetAlpha, tolerance, isFillMode)) {
      stack.push([startX, startY]);
    }
    while (stack.length > 0) {
      const [x2, y] = stack.pop();
      const visitedIndex = y * width2 + x2;
      if (visited[visitedIndex]) continue;
      const currentAlpha = getPixelAlpha(data, x2, y, width2);
      if (!shouldProcessPixel(currentAlpha, targetAlpha, tolerance, isFillMode)) {
        continue;
      }
      visited[visitedIndex] = 1;
      setPixel(data, x2, y, width2, isFillMode ? fillOpacity : 0, maskColor);
      const checkNeighbor = /* @__PURE__ */ __name((nx, ny) => {
        if (nx < 0 || nx >= width2 || ny < 0 || ny >= height) return;
        if (!visited[ny * width2 + nx]) {
          const alpha = getPixelAlpha(data, nx, ny, width2);
          if (shouldProcessPixel(alpha, targetAlpha, tolerance, isFillMode)) {
            stack.push([nx, ny]);
          }
        }
      }, "checkNeighbor");
      checkNeighbor(x2 - 1, y);
      checkNeighbor(x2 + 1, y);
      checkNeighbor(x2, y - 1);
      checkNeighbor(x2, y + 1);
    }
    ctx.putImageData(imageData, 0, 0);
    store.canvasHistory.saveState();
  }, "paintBucketFill");
  const colorSelectFill = /* @__PURE__ */ __name(async (point) => {
    const maskCtx = store.maskCtx;
    const imgCtx = store.imgCtx;
    const imgCanvas = store.imgCanvas;
    if (!maskCtx || !imgCtx || !imgCanvas) return;
    const width2 = imgCanvas.width;
    const height = imgCanvas.height;
    lastColorSelectPoint.value = point;
    const maskData = maskCtx.getImageData(0, 0, width2, height);
    const maskDataArray = maskData.data;
    const imageDataArray = imgCtx.getImageData(0, 0, width2, height).data;
    const tolerance = store.colorSelectTolerance;
    const method = store.colorComparisonMethod;
    const maskColor = store.maskColor;
    const selectOpacity = Math.floor(store.selectionOpacity / 100 * 255);
    const applyWholeImage = store.applyWholeImage;
    const maskBoundary = store.maskBoundary;
    const maskTolerance = store.maskTolerance;
    if (applyWholeImage) {
      const targetPixel = getPixelColor(
        imageDataArray,
        Math.floor(point.x),
        Math.floor(point.y),
        width2
      );
      const CHUNK_SIZE = 1e4;
      for (let i = 0; i < width2 * height; i += CHUNK_SIZE) {
        const endIndex = Math.min(i + CHUNK_SIZE, width2 * height);
        for (let pixelIndex = i; pixelIndex < endIndex; pixelIndex++) {
          const x2 = pixelIndex % width2;
          const y = Math.floor(pixelIndex / width2);
          if (isPixelInRange(
            getPixelColor(imageDataArray, x2, y, width2),
            targetPixel,
            tolerance,
            method
          )) {
            setPixel(maskDataArray, x2, y, width2, selectOpacity, maskColor);
          }
        }
        await new Promise((resolve) => setTimeout(resolve, 0));
      }
    } else {
      const startX = Math.floor(point.x);
      const startY = Math.floor(point.y);
      if (startX < 0 || startX >= width2 || startY < 0 || startY >= height) {
        return;
      }
      const targetPixel = getPixelColor(imageDataArray, startX, startY, width2);
      const stack = [];
      const visited = new Uint8Array(width2 * height);
      stack.push([startX, startY]);
      while (stack.length > 0) {
        const [x2, y] = stack.pop();
        const visitedIndex = y * width2 + x2;
        if (visited[visitedIndex] || !isPixelInRange(
          getPixelColor(imageDataArray, x2, y, width2),
          targetPixel,
          tolerance,
          method
        )) {
          continue;
        }
        visited[visitedIndex] = 1;
        setPixel(maskDataArray, x2, y, width2, selectOpacity, maskColor);
        const checkNeighbor = /* @__PURE__ */ __name((nx, ny) => {
          if (nx < 0 || nx >= width2 || ny < 0 || ny >= height) return;
          if (visited[ny * width2 + nx]) return;
          if (!isPixelInRange(
            getPixelColor(imageDataArray, nx, ny, width2),
            targetPixel,
            tolerance,
            method
          ))
            return;
          if (maskBoundary && 255 - getPixelAlpha(maskDataArray, nx, ny, width2) <= maskTolerance)
            return;
          stack.push([nx, ny]);
        }, "checkNeighbor");
        checkNeighbor(x2 - 1, y);
        checkNeighbor(x2 + 1, y);
        checkNeighbor(x2, y - 1);
        checkNeighbor(x2, y + 1);
      }
    }
    maskCtx.putImageData(maskData, 0, 0);
    store.canvasHistory.saveState();
  }, "colorSelectFill");
  const invertMask = /* @__PURE__ */ __name(() => {
    const ctx = store.maskCtx;
    const canvas2 = store.maskCanvas;
    if (!ctx || !canvas2) return;
    const imageData = ctx.getImageData(0, 0, canvas2.width, canvas2.height);
    const data = imageData.data;
    let maskR = 0, maskG = 0, maskB = 0;
    for (let i = 0; i < data.length; i += 4) {
      if (data[i + 3] > 0) {
        maskR = data[i];
        maskG = data[i + 1];
        maskB = data[i + 2];
        break;
      }
    }
    for (let i = 0; i < data.length; i += 4) {
      const alpha = data[i + 3];
      data[i + 3] = 255 - alpha;
      if (alpha === 0) {
        data[i] = maskR;
        data[i + 1] = maskG;
        data[i + 2] = maskB;
      }
    }
    ctx.putImageData(imageData, 0, 0);
    store.canvasHistory.saveState();
  }, "invertMask");
  const clearMask = /* @__PURE__ */ __name(() => {
    const maskCtx = store.maskCtx;
    const maskCanvas = store.maskCanvas;
    const rgbCtx = store.rgbCtx;
    const rgbCanvas = store.rgbCanvas;
    if (maskCtx && maskCanvas) {
      maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
    }
    if (rgbCtx && rgbCanvas) {
      rgbCtx.clearRect(0, 0, rgbCanvas.width, rgbCanvas.height);
    }
    store.canvasHistory.saveState();
  }, "clearMask");
  const clearLastColorSelectPoint = /* @__PURE__ */ __name(() => {
    lastColorSelectPoint.value = null;
  }, "clearLastColorSelectPoint");
  watch(
    [
      () => store.colorSelectTolerance,
      () => store.colorComparisonMethod,
      () => store.selectionOpacity
    ],
    async () => {
      if (lastColorSelectPoint.value && store.colorSelectLivePreview && store.canUndo) {
        store.canvasHistory.undo();
        await colorSelectFill(lastColorSelectPoint.value);
      }
    }
  );
  return {
    paintBucketFill,
    colorSelectFill,
    clearLastColorSelectPoint,
    invertMask,
    clearMask
  };
}
__name(useCanvasTools, "useCanvasTools");
const useMaskEditorDataStore = defineStore("maskEditorData", () => {
  const inputData = ref(null);
  const outputData = ref(null);
  const sourceNode = ref(null);
  const isLoading = ref(false);
  const loadError = ref(null);
  const hasValidInput = computed(() => inputData.value !== null);
  const hasValidOutput = computed(() => outputData.value !== null);
  const isReady = computed(() => hasValidInput.value && !isLoading.value);
  const reset = /* @__PURE__ */ __name(() => {
    inputData.value = null;
    outputData.value = null;
    sourceNode.value = null;
    isLoading.value = false;
    loadError.value = null;
  }, "reset");
  const setLoading = /* @__PURE__ */ __name((loading, error) => {
    isLoading.value = loading;
    if (error) {
      loadError.value = error;
    }
  }, "setLoading");
  return {
    inputData,
    outputData,
    sourceNode,
    isLoading,
    loadError,
    hasValidInput,
    hasValidOutput,
    isReady,
    reset,
    setLoading
  };
});
function imageLayerFilenamesByTimestamp(timestamp) {
  return {
    maskedImage: `clipspace-mask-${timestamp}.png`,
    paint: `clipspace-paint-${timestamp}.png`,
    paintedImage: `clipspace-painted-${timestamp}.png`,
    paintedMaskedImage: `clipspace-painted-masked-${timestamp}.png`
  };
}
__name(imageLayerFilenamesByTimestamp, "imageLayerFilenamesByTimestamp");
function useMaskEditorSaver() {
  const dataStore = useMaskEditorDataStore();
  const editorStore = useMaskEditorStore();
  const nodeOutputStore = useNodeOutputStore();
  const save = /* @__PURE__ */ __name(async () => {
    const sourceNode = dataStore.sourceNode;
    if (!sourceNode || !dataStore.inputData) {
      throw new Error("No source node or input data");
    }
    try {
      const outputData = await prepareOutputData();
      dataStore.outputData = outputData;
      await updateNodePreview(sourceNode, outputData);
      await uploadAllLayers(outputData);
      updateNodeWithServerReferences(sourceNode, outputData);
      app$1.canvas.setDirty(true);
    } catch (error) {
      console.error("[MaskEditorSaver] Save failed:", error);
      throw error;
    }
  }, "save");
  async function prepareOutputData() {
    const maskCanvas = editorStore.maskCanvas;
    const paintCanvas = editorStore.rgbCanvas;
    const imgCanvas = editorStore.imgCanvas;
    if (!maskCanvas || !paintCanvas || !imgCanvas) {
      throw new Error("Canvas not initialized");
    }
    const timestamp = Date.now();
    const filenames = imageLayerFilenamesByTimestamp(timestamp);
    const [maskedImage, paintLayer, paintedImage, paintedMaskedImage] = await Promise.all([
      createMaskedImage(imgCanvas, maskCanvas, filenames.maskedImage),
      createPaintLayer(paintCanvas, filenames.paint),
      createPaintedImage(imgCanvas, paintCanvas, filenames.paintedImage),
      createPaintedMaskedImage(
        imgCanvas,
        paintCanvas,
        maskCanvas,
        filenames.paintedMaskedImage
      )
    ]);
    return {
      maskedImage,
      paintLayer,
      paintedImage,
      paintedMaskedImage
    };
  }
  __name(prepareOutputData, "prepareOutputData");
  async function createMaskedImage(imgCanvas, maskCanvas, filename) {
    const canvas2 = document.createElement("canvas");
    canvas2.width = imgCanvas.width;
    canvas2.height = imgCanvas.height;
    const ctx = canvas2.getContext("2d");
    ctx.drawImage(imgCanvas, 0, 0);
    const maskCtx = maskCanvas.getContext("2d");
    const maskData = maskCtx.getImageData(
      0,
      0,
      maskCanvas.width,
      maskCanvas.height
    );
    const refinedMaskData = new Uint8ClampedArray(maskData.data.length);
    for (let i = 0; i < maskData.data.length; i += 4) {
      refinedMaskData[i] = 0;
      refinedMaskData[i + 1] = 0;
      refinedMaskData[i + 2] = 0;
      refinedMaskData[i + 3] = 255 - maskData.data[i + 3];
    }
    const imageData = ctx.getImageData(0, 0, canvas2.width, canvas2.height);
    for (let i = 0; i < imageData.data.length; i += 4) {
      imageData.data[i + 3] = refinedMaskData[i + 3];
    }
    ctx.putImageData(imageData, 0, 0);
    const blob = await canvasToBlob(canvas2);
    const ref2 = createFileRef(filename);
    return { canvas: canvas2, blob, ref: ref2 };
  }
  __name(createMaskedImage, "createMaskedImage");
  async function createPaintLayer(paintCanvas, filename) {
    const canvas2 = cloneCanvas(paintCanvas);
    const blob = await canvasToBlob(canvas2);
    const ref2 = createFileRef(filename);
    return { canvas: canvas2, blob, ref: ref2 };
  }
  __name(createPaintLayer, "createPaintLayer");
  async function createPaintedImage(imgCanvas, paintCanvas, filename) {
    const canvas2 = document.createElement("canvas");
    canvas2.width = imgCanvas.width;
    canvas2.height = imgCanvas.height;
    const ctx = canvas2.getContext("2d");
    ctx.drawImage(imgCanvas, 0, 0);
    ctx.globalCompositeOperation = "source-over";
    ctx.drawImage(paintCanvas, 0, 0);
    const blob = await canvasToBlob(canvas2);
    const ref2 = createFileRef(filename);
    return { canvas: canvas2, blob, ref: ref2 };
  }
  __name(createPaintedImage, "createPaintedImage");
  async function createPaintedMaskedImage(imgCanvas, paintCanvas, maskCanvas, filename) {
    const canvas2 = document.createElement("canvas");
    canvas2.width = imgCanvas.width;
    canvas2.height = imgCanvas.height;
    const ctx = canvas2.getContext("2d");
    ctx.drawImage(imgCanvas, 0, 0);
    ctx.globalCompositeOperation = "source-over";
    ctx.drawImage(paintCanvas, 0, 0);
    const maskCtx = maskCanvas.getContext("2d");
    const maskData = maskCtx.getImageData(
      0,
      0,
      maskCanvas.width,
      maskCanvas.height
    );
    const refinedMaskData = new Uint8ClampedArray(maskData.data.length);
    for (let i = 0; i < maskData.data.length; i += 4) {
      refinedMaskData[i] = 0;
      refinedMaskData[i + 1] = 0;
      refinedMaskData[i + 2] = 0;
      refinedMaskData[i + 3] = 255 - maskData.data[i + 3];
    }
    const imageData = ctx.getImageData(0, 0, canvas2.width, canvas2.height);
    for (let i = 0; i < imageData.data.length; i += 4) {
      imageData.data[i + 3] = refinedMaskData[i + 3];
    }
    ctx.putImageData(imageData, 0, 0);
    const blob = await canvasToBlob(canvas2);
    const ref2 = createFileRef(filename);
    return { canvas: canvas2, blob, ref: ref2 };
  }
  __name(createPaintedMaskedImage, "createPaintedMaskedImage");
  async function uploadAllLayers(outputData) {
    const sourceRef = dataStore.inputData.sourceRef;
    const actualMaskedRef = await uploadMask(outputData.maskedImage, sourceRef);
    const actualPaintRef = await uploadImage(outputData.paintLayer, sourceRef);
    const actualPaintedRef = await uploadImage(
      outputData.paintedImage,
      sourceRef
    );
    const actualPaintedMaskedRef = await uploadMask(
      outputData.paintedMaskedImage,
      actualPaintedRef
    );
    outputData.maskedImage.ref = actualMaskedRef;
    outputData.paintLayer.ref = actualPaintRef;
    outputData.paintedImage.ref = actualPaintedRef;
    outputData.paintedMaskedImage.ref = actualPaintedMaskedRef;
  }
  __name(uploadAllLayers, "uploadAllLayers");
  async function uploadMask(layer, originalRef) {
    const formData = new FormData();
    formData.append("image", layer.blob, layer.ref.filename);
    formData.append("original_ref", JSON.stringify(originalRef));
    formData.append("type", "input");
    formData.append("subfolder", "clipspace");
    const response = await api.fetchApi("/upload/mask", {
      method: "POST",
      body: formData
    });
    if (!response.ok) {
      throw new Error(`Failed to upload mask: ${layer.ref.filename}`);
    }
    try {
      const data = await response.json();
      if (data?.name) {
        return {
          filename: data.name,
          subfolder: data.subfolder || layer.ref.subfolder,
          type: data.type || layer.ref.type
        };
      }
    } catch (error) {
      console.warn("[MaskEditorSaver] Failed to parse upload response:", error);
    }
    return layer.ref;
  }
  __name(uploadMask, "uploadMask");
  async function uploadImage(layer, originalRef) {
    const formData = new FormData();
    formData.append("image", layer.blob, layer.ref.filename);
    formData.append("original_ref", JSON.stringify(originalRef));
    formData.append("type", "input");
    formData.append("subfolder", "clipspace");
    const response = await api.fetchApi("/upload/image", {
      method: "POST",
      body: formData
    });
    if (!response.ok) {
      throw new Error(`Failed to upload image: ${layer.ref.filename}`);
    }
    try {
      const data = await response.json();
      if (data?.name) {
        return {
          filename: data.name,
          subfolder: data.subfolder || layer.ref.subfolder,
          type: data.type || layer.ref.type
        };
      }
    } catch (error) {
      console.warn("[MaskEditorSaver] Failed to parse upload response:", error);
    }
    return layer.ref;
  }
  __name(uploadImage, "uploadImage");
  async function updateNodePreview(node2, outputData) {
    const canvas2 = outputData.paintedMaskedImage.canvas;
    const dataUrl = canvas2.toDataURL("image/png");
    const mainImg = await loadImageFromUrl(dataUrl);
    node2.imgs = [mainImg];
    app$1.canvas.setDirty(true);
  }
  __name(updateNodePreview, "updateNodePreview");
  function updateNodeWithServerReferences(node2, outputData) {
    const mainRef = outputData.paintedMaskedImage.ref;
    node2.images = [mainRef];
    const imageWidget = node2.widgets?.find((w) => w.name === "image");
    if (imageWidget) {
      let widgetValue;
      {
        widgetValue = (mainRef.subfolder ? mainRef.subfolder + "/" : "") + mainRef.filename + (mainRef.type ? ` [${mainRef.type}]` : "");
      }
      imageWidget.value = widgetValue;
      if (node2.properties) {
        node2.properties["image"] = widgetValue;
      }
      if (node2.widgets_values && node2.widgets) {
        const widgetIndex = node2.widgets.indexOf(imageWidget);
        if (widgetIndex >= 0) {
          node2.widgets_values[widgetIndex] = widgetValue;
        }
      }
      imageWidget.callback?.(widgetValue);
    }
    nodeOutputStore.updateNodeImages(node2);
  }
  __name(updateNodeWithServerReferences, "updateNodeWithServerReferences");
  function loadImageFromUrl(url) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.crossOrigin = "anonymous";
      img.onload = () => resolve(img);
      img.onerror = (error) => {
        console.error("[MaskEditorSaver] Failed to load image:", url, error);
        reject(new Error(`Failed to load image: ${url}`));
      };
      img.src = url;
    });
  }
  __name(loadImageFromUrl, "loadImageFromUrl");
  function cloneCanvas(source) {
    const canvas2 = document.createElement("canvas");
    canvas2.width = source.width;
    canvas2.height = source.height;
    const ctx = canvas2.getContext("2d");
    ctx.drawImage(source, 0, 0);
    return canvas2;
  }
  __name(cloneCanvas, "cloneCanvas");
  function canvasToBlob(canvas2) {
    return new Promise((resolve, reject) => {
      canvas2.toBlob((blob) => {
        if (blob) resolve(blob);
        else reject(new Error("Failed to create blob from canvas"));
      }, "image/png");
    });
  }
  __name(canvasToBlob, "canvasToBlob");
  function createFileRef(filename) {
    return {
      filename,
      subfolder: "clipspace",
      type: "input"
    };
  }
  __name(createFileRef, "createFileRef");
  return {
    save
  };
}
__name(useMaskEditorSaver, "useMaskEditorSaver");
const _hoisted_1$1e = { class: "flex w-full items-center justify-between gap-3" };
const _hoisted_2$_ = { class: "flex items-center gap-3" };
const _hoisted_3$F = { class: "m-0 text-lg font-semibold" };
const _hoisted_4$x = { class: "flex items-center gap-4" };
const _hoisted_5$t = ["title"];
const _hoisted_6$l = ["title"];
const _hoisted_7$i = { class: "flex gap-3" };
const iconButtonClass = "flex h-7.5 w-12.5 items-center justify-center rounded-[10px] border border-[var(--p-form-field-border-color)] pointer-events-auto transition-colors duration-100 bg-[var(--comfy-menu-bg)] hover:bg-secondary-background-hover";
const textButtonClass$1 = "h-7.5 w-15 rounded-[10px] border border-[var(--p-form-field-border-color)] text-[var(--input-text)] font-sans pointer-events-auto transition-colors duration-100 bg-[var(--comfy-menu-bg)] hover:bg-secondary-background-hover";
const _sfc_main$1r = /* @__PURE__ */ defineComponent({
  __name: "TopBarHeader",
  setup(__props) {
    const store = useMaskEditorStore();
    const dialogStore = useDialogStore();
    const canvasTools = useCanvasTools();
    const saver = useMaskEditorSaver();
    const saveButtonText = ref(t("g.save"));
    const saveEnabled = ref(true);
    const onUndo = /* @__PURE__ */ __name(() => {
      store.canvasHistory.undo();
    }, "onUndo");
    const onRedo = /* @__PURE__ */ __name(() => {
      store.canvasHistory.redo();
    }, "onRedo");
    const onInvert = /* @__PURE__ */ __name(() => {
      canvasTools.invertMask();
    }, "onInvert");
    const onClear = /* @__PURE__ */ __name(() => {
      canvasTools.clearMask();
      store.triggerClear();
    }, "onClear");
    const handleSave = /* @__PURE__ */ __name(async () => {
      saveButtonText.value = t("g.saving");
      saveEnabled.value = false;
      try {
        store.brushVisible = false;
        await saver.save();
        dialogStore.closeDialog();
      } catch (error) {
        console.error("[TopBarHeader] Save failed:", error);
        store.brushVisible = true;
        saveButtonText.value = t("g.save");
        saveEnabled.value = true;
      }
    }, "handleSave");
    const handleCancel = /* @__PURE__ */ __name(() => {
      dialogStore.closeDialog({ key: "global-mask-editor" });
    }, "handleCancel");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1e, [
        createBaseVNode("div", _hoisted_2$_, [
          createBaseVNode("h3", _hoisted_3$F, toDisplayString(unref(t)("maskEditor.title")), 1),
          createBaseVNode("div", _hoisted_4$x, [
            createBaseVNode("button", {
              class: normalizeClass(iconButtonClass),
              title: unref(t)("maskEditor.undo"),
              onClick: onUndo
            }, _cache[0] || (_cache[0] = [
              createBaseVNode("svg", {
                viewBox: "0 0 15 15",
                class: "h-6.25 w-6.25 pointer-events-none fill-[var(--input-text)]"
              }, [
                createBaseVNode("path", { d: "M8.77,12.18c-.25,0-.46-.2-.46-.46s.2-.46.46-.46c1.47,0,2.67-1.2,2.67-2.67,0-1.57-1.34-2.67-3.26-2.67h-3.98l1.43,1.43c.18.18.18.47,0,.64-.18.18-.47.18-.64,0l-2.21-2.21c-.18-.18-.18-.47,0-.64l2.21-2.21c.18-.18.47-.18.64,0,.18.18.18.47,0,.64l-1.43,1.43h3.98c2.45,0,4.17,1.47,4.17,3.58,0,1.97-1.61,3.58-3.58,3.58Z" })
              ], -1)
            ]), 8, _hoisted_5$t),
            createBaseVNode("button", {
              class: normalizeClass(iconButtonClass),
              title: unref(t)("maskEditor.redo"),
              onClick: onRedo
            }, _cache[1] || (_cache[1] = [
              createBaseVNode("svg", {
                viewBox: "0 0 15 15",
                class: "h-6.25 w-6.25 pointer-events-none fill-[var(--input-text)]"
              }, [
                createBaseVNode("path", {
                  class: "cls-1",
                  d: "M6.23,12.18c-1.97,0-3.58-1.61-3.58-3.58,0-2.11,1.71-3.58,4.17-3.58h3.98l-1.43-1.43c-.18-.18-.18-.47,0-.64.18-.18.46-.18.64,0l2.21,2.21c.09.09.13.2.13.32s-.05.24-.13.32l-2.21,2.21c-.18.18-.47.18-.64,0-.18-.18-.18-.47,0-.64l1.43-1.43h-3.98c-1.92,0-3.26,1.1-3.26,2.67,0,1.47,1.2,2.67,2.67,2.67.25,0,.46.2.46.46s-.2.46-.46.46Z"
                })
              ], -1)
            ]), 8, _hoisted_6$l),
            createBaseVNode("button", {
              class: normalizeClass(textButtonClass$1),
              onClick: onInvert
            }, toDisplayString(unref(t)("maskEditor.invert")), 1),
            createBaseVNode("button", {
              class: normalizeClass(textButtonClass$1),
              onClick: onClear
            }, toDisplayString(unref(t)("maskEditor.clear")), 1)
          ])
        ]),
        createBaseVNode("div", _hoisted_7$i, [
          createVNode(_sfc_main$2A, {
            variant: "primary",
            disabled: !saveEnabled.value,
            onClick: handleSave
          }, {
            default: withCtx(() => [
              _cache[2] || (_cache[2] = createBaseVNode("i", { class: "pi pi-check" }, null, -1)),
              createTextVNode(" " + toDisplayString(saveButtonText.value), 1)
            ]),
            _: 1
          }, 8, ["disabled"]),
          createVNode(_sfc_main$2A, {
            variant: "secondary",
            onClick: handleCancel
          }, {
            default: withCtx(() => [
              _cache[3] || (_cache[3] = createBaseVNode("i", { class: "pi pi-times" }, null, -1)),
              createTextVNode(" " + toDisplayString(unref(t)("g.cancel")), 1)
            ]),
            _: 1
          })
        ])
      ]);
    };
  }
});
function useCanvasManager() {
  const store = useMaskEditorStore();
  const prepareMask = /* @__PURE__ */ __name(async (image, maskCanvasEl, maskContext) => {
    const maskColor = store.maskColor;
    maskContext.drawImage(image, 0, 0, maskCanvasEl.width, maskCanvasEl.height);
    const maskData = maskContext.getImageData(
      0,
      0,
      maskCanvasEl.width,
      maskCanvasEl.height
    );
    for (let i = 0; i < maskData.data.length; i += 4) {
      const alpha = maskData.data[i + 3];
      maskData.data[i] = maskColor.r;
      maskData.data[i + 1] = maskColor.g;
      maskData.data[i + 2] = maskColor.b;
      maskData.data[i + 3] = 255 - alpha;
    }
    maskContext.globalCompositeOperation = "source-over";
    maskContext.putImageData(maskData, 0, 0);
  }, "prepareMask");
  const invalidateCanvas = /* @__PURE__ */ __name(async (origImage, maskImage, paintImage) => {
    const { imgCanvas, maskCanvas, rgbCanvas, imgCtx, maskCtx, rgbCtx } = store;
    if (!imgCanvas || !maskCanvas || !rgbCanvas || !imgCtx || !maskCtx || !rgbCtx) {
      throw new Error("Canvas elements or contexts not available");
    }
    imgCanvas.width = origImage.width;
    imgCanvas.height = origImage.height;
    maskCanvas.width = origImage.width;
    maskCanvas.height = origImage.height;
    rgbCanvas.width = origImage.width;
    rgbCanvas.height = origImage.height;
    imgCtx.drawImage(origImage, 0, 0, origImage.width, origImage.height);
    if (paintImage) {
      rgbCtx.drawImage(paintImage, 0, 0, paintImage.width, paintImage.height);
    }
    await prepareMask(maskImage, maskCanvas, maskCtx);
  }, "invalidateCanvas");
  const setCanvasBackground = /* @__PURE__ */ __name(() => {
    const canvasBackground = store.canvasBackground;
    if (!canvasBackground) return;
    if (store.maskBlendMode === MaskBlendMode.Black) {
      canvasBackground.style.backgroundColor = "rgba(0,0,0,1)";
    } else if (store.maskBlendMode === MaskBlendMode.White) {
      canvasBackground.style.backgroundColor = "rgba(255,255,255,1)";
    } else if (store.maskBlendMode === MaskBlendMode.Negative) {
      canvasBackground.style.backgroundColor = "rgba(255,255,255,1)";
    }
  }, "setCanvasBackground");
  const updateMaskColor = /* @__PURE__ */ __name(async () => {
    const { maskCanvas, maskCtx, maskColor, maskBlendMode, maskOpacity } = store;
    if (!maskCanvas || !maskCtx) return;
    if (maskBlendMode === MaskBlendMode.Negative) {
      maskCanvas.style.mixBlendMode = "difference";
      maskCanvas.style.opacity = "1";
    } else {
      maskCanvas.style.mixBlendMode = "initial";
      maskCanvas.style.opacity = String(maskOpacity);
    }
    maskCtx.fillStyle = `rgb(${maskColor.r}, ${maskColor.g}, ${maskColor.b})`;
    setCanvasBackground();
    const maskData = maskCtx.getImageData(
      0,
      0,
      maskCanvas.width,
      maskCanvas.height
    );
    for (let i = 0; i < maskData.data.length; i += 4) {
      maskData.data[i] = maskColor.r;
      maskData.data[i + 1] = maskColor.g;
      maskData.data[i + 2] = maskColor.b;
    }
    maskCtx.putImageData(maskData, 0, 0);
  }, "updateMaskColor");
  return {
    invalidateCanvas,
    updateMaskColor
  };
}
__name(useCanvasManager, "useCanvasManager");
function useImageLoaderInternal() {
  const store = useMaskEditorStore();
  const dataStore = useMaskEditorDataStore();
  const canvasManager = useCanvasManager();
  const loadImages = /* @__PURE__ */ __name(async () => {
    const inputData = dataStore.inputData;
    if (!inputData) {
      throw new Error("No input data available in dataStore");
    }
    const { imgCanvas, maskCanvas, rgbCanvas, imgCtx, maskCtx } = store;
    if (!imgCanvas || !maskCanvas || !rgbCanvas || !imgCtx || !maskCtx) {
      throw new Error("Canvas elements or contexts not available");
    }
    imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
    maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
    const baseImage = inputData.baseLayer.image;
    const maskImage = inputData.maskLayer.image;
    const paintImage = inputData.paintLayer?.image;
    maskCanvas.width = baseImage.width;
    maskCanvas.height = baseImage.height;
    rgbCanvas.width = baseImage.width;
    rgbCanvas.height = baseImage.height;
    store.image = baseImage;
    await canvasManager.invalidateCanvas(
      baseImage,
      maskImage,
      paintImage || null
    );
    await canvasManager.updateMaskColor();
    return baseImage;
  }, "loadImages");
  return {
    loadImages
  };
}
__name(useImageLoaderInternal, "useImageLoaderInternal");
const useImageLoader = createSharedComposable(useImageLoaderInternal);
function useKeyboard() {
  const store = useMaskEditorStore();
  const keysDown = ref([]);
  const isKeyDown = /* @__PURE__ */ __name((key) => {
    return keysDown.value.includes(key);
  }, "isKeyDown");
  const clearKeys = /* @__PURE__ */ __name(() => {
    keysDown.value = [];
  }, "clearKeys");
  const handleKeyDown = /* @__PURE__ */ __name((event) => {
    if (!keysDown.value.includes(event.key)) {
      keysDown.value.push(event.key);
    }
    if (event.key === " ") {
      event.preventDefault();
      const activeElement = document.activeElement;
      if (activeElement && activeElement.blur) {
        activeElement.blur();
      }
    }
    if ((event.ctrlKey || event.metaKey) && !event.altKey) {
      const key = event.key.toUpperCase();
      if (key === "Y" && !event.shiftKey || key === "Z" && event.shiftKey) {
        store.canvasHistory.redo();
      } else if (key === "Z" && !event.shiftKey) {
        store.canvasHistory.undo();
      }
    }
  }, "handleKeyDown");
  const handleKeyUp = /* @__PURE__ */ __name((event) => {
    keysDown.value = keysDown.value.filter((key) => key !== event.key);
  }, "handleKeyUp");
  const addListeners = /* @__PURE__ */ __name(() => {
    document.addEventListener("keydown", handleKeyDown);
    document.addEventListener("keyup", handleKeyUp);
    window.addEventListener("blur", clearKeys);
  }, "addListeners");
  const removeListeners = /* @__PURE__ */ __name(() => {
    document.removeEventListener("keydown", handleKeyDown);
    document.removeEventListener("keyup", handleKeyUp);
    window.removeEventListener("blur", clearKeys);
  }, "removeListeners");
  return {
    isKeyDown,
    addListeners,
    removeListeners
  };
}
__name(useKeyboard, "useKeyboard");
const paintedMaskedImagePrefix = "clipspace-painted-masked-";
function imageLayerFilenamesIfApplicable(inputImageFilename) {
  const isPaintedMaskedImageFilename = inputImageFilename.startsWith(
    paintedMaskedImagePrefix
  );
  if (!isPaintedMaskedImageFilename) return void 0;
  const suffix = inputImageFilename.slice(paintedMaskedImagePrefix.length);
  const timestamp = parseInt(suffix.split(".")[0], 10);
  return {
    maskedImage: `clipspace-mask-${timestamp}.png`,
    paint: `clipspace-paint-${timestamp}.png`,
    paintedImage: `clipspace-painted-${timestamp}.png`,
    paintedMaskedImage: `${paintedMaskedImagePrefix}${timestamp}.png`
  };
}
__name(imageLayerFilenamesIfApplicable, "imageLayerFilenamesIfApplicable");
function toRef(filename) {
  return {
    filename,
    subfolder: "clipspace",
    type: "input"
  };
}
__name(toRef, "toRef");
function mkFileUrl(props) {
  const params = new URLSearchParams();
  params.set("filename", props.ref.filename);
  if (props.ref.subfolder) {
    params.set("subfolder", props.ref.subfolder);
  }
  if (props.ref.type) {
    params.set("type", props.ref.type);
  }
  const pathPlusQueryParams = api.apiURL(
    "/view?" + params.toString() + app$1.getPreviewFormatParam() + app$1.getRandParam()
  );
  const imageElement = new Image();
  imageElement.crossOrigin = "anonymous";
  imageElement.src = pathPlusQueryParams;
  return imageElement.src;
}
__name(mkFileUrl, "mkFileUrl");
function useMaskEditorLoader() {
  const dataStore = useMaskEditorDataStore();
  const nodeOutputStore = useNodeOutputStore();
  const loadFromNode = /* @__PURE__ */ __name(async (node2) => {
    dataStore.setLoading(true);
    try {
      validateNode(node2);
      let nodeImageUrl = getNodeImageUrl(node2);
      let nodeImageRef = parseImageRef(nodeImageUrl);
      let widgetFilename;
      if (node2.widgets) {
        const imageWidget = node2.widgets.find((w) => w.name === "image");
        if (imageWidget) {
          if (typeof imageWidget.value === "string") {
            widgetFilename = imageWidget.value;
          } else if (typeof imageWidget.value === "object" && imageWidget.value && "filename" in imageWidget.value && typeof imageWidget.value.filename === "string") {
            widgetFilename = imageWidget.value.filename;
          }
        }
      }
      if (widgetFilename && !widgetFilename.startsWith("$")) {
        try {
          let filename = widgetFilename;
          let subfolder = void 0;
          let type = "input";
          const typeMatch = filename.match(/ \[([^\]]+)\]$/);
          if (typeMatch) {
            type = typeMatch[1];
            filename = filename.substring(
              0,
              filename.length - typeMatch[0].length
            );
          }
          const lastSlashIndex = filename.lastIndexOf("/");
          if (lastSlashIndex !== -1) {
            subfolder = filename.substring(0, lastSlashIndex);
            filename = filename.substring(lastSlashIndex + 1);
          }
          nodeImageRef = {
            filename,
            type,
            subfolder
          };
          nodeImageUrl = mkFileUrl({ ref: nodeImageRef });
        } catch (e2) {
          console.warn("Failed to parse widget filename as ref", e2);
        }
      }
      const fileToQuery = widgetFilename || nodeImageRef.filename;
      let maskLayersFromApi;
      if (isCloud) ;
      let imageLayerFilenames = imageLayerFilenamesIfApplicable(
        nodeImageRef.filename
      );
      if (maskLayersFromApi) {
        const baseFile = maskLayersFromApi.painted_masked || maskLayersFromApi.painted;
        if (baseFile) {
          imageLayerFilenames = {
            maskedImage: baseFile,
            paint: maskLayersFromApi.paint || "",
            paintedImage: maskLayersFromApi.painted || "",
            paintedMaskedImage: maskLayersFromApi.painted_masked || baseFile
          };
        }
      }
      const baseImageUrl = imageLayerFilenames?.maskedImage ? mkFileUrl({ ref: toRef(imageLayerFilenames.maskedImage) }) : nodeImageUrl;
      const sourceRef = imageLayerFilenames?.maskedImage ? parseImageRef(baseImageUrl) : nodeImageRef;
      let paintLayerUrl = null;
      if (maskLayersFromApi?.paint) {
        paintLayerUrl = mkFileUrl({ ref: toRef(maskLayersFromApi.paint) });
      } else if (imageLayerFilenames?.paint) {
        paintLayerUrl = mkFileUrl({ ref: toRef(imageLayerFilenames.paint) });
      }
      const [baseLayer, maskLayer, paintLayer] = await Promise.all([
        loadImageLayer(baseImageUrl, "rgb"),
        loadImageLayer(baseImageUrl, "a"),
        paintLayerUrl ? loadPaintLayer(paintLayerUrl) : Promise.resolve(void 0)
      ]);
      dataStore.inputData = {
        baseLayer,
        maskLayer,
        paintLayer,
        sourceRef,
        nodeId: node2.id
      };
      dataStore.sourceNode = node2;
      dataStore.setLoading(false);
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : "Failed to load from node";
      console.error("[MaskEditorLoader]", errorMessage, error);
      dataStore.setLoading(false, errorMessage);
      throw error;
    }
  }, "loadFromNode");
  function validateNode(node2) {
    if (!node2) {
      throw new Error("Node is null or undefined");
    }
    const hasImages = node2.imgs?.length || node2.previewMediaType === "image";
    if (!hasImages) {
      throw new Error("Node has no images");
    }
  }
  __name(validateNode, "validateNode");
  function getNodeImageUrl(node2) {
    if (node2.images?.[0]) {
      const img = node2.images[0];
      const params = new URLSearchParams({
        filename: img.filename,
        type: img.type || "output",
        subfolder: img.subfolder || ""
      });
      return api.apiURL(`/view?${params.toString()}`);
    }
    const outputs = nodeOutputStore.getNodeOutputs(node2);
    if (outputs?.images?.[0]) {
      const img = outputs.images[0];
      if (!img.filename) {
        throw new Error("nodeOutputStore image missing filename");
      }
      const params = new URLSearchParams();
      params.set("filename", img.filename);
      params.set("type", img.type || "output");
      params.set("subfolder", img.subfolder || "");
      return api.apiURL(`/view?${params.toString()}`);
    }
    if (node2.imgs?.length) {
      const index2 = node2.imageIndex ?? 0;
      const imgSrc = node2.imgs[index2].src;
      if (imgSrc && !imgSrc.startsWith("data:")) {
        return imgSrc;
      }
    }
    throw new Error("Unable to get image URL from node");
  }
  __name(getNodeImageUrl, "getNodeImageUrl");
  function parseImageRef(url) {
    try {
      const urlObj = new URL(url);
      const filename = urlObj.searchParams.get("filename");
      if (!filename) {
        throw new Error("Image URL missing filename parameter");
      }
      return {
        filename,
        subfolder: urlObj.searchParams.get("subfolder") || void 0,
        type: urlObj.searchParams.get("type") || void 0
      };
    } catch (error) {
      try {
        const urlObj = new URL(url, window.location.origin);
        const filename = urlObj.searchParams.get("filename");
        if (!filename) {
          throw new Error("Image URL missing filename parameter");
        }
        return {
          filename,
          subfolder: urlObj.searchParams.get("subfolder") || void 0,
          type: urlObj.searchParams.get("type") || void 0
        };
      } catch (e2) {
        throw new Error(`Invalid image URL: ${url}`);
      }
    }
  }
  __name(parseImageRef, "parseImageRef");
  async function loadImageLayer(url, channel) {
    let urlObj;
    try {
      urlObj = new URL(url);
    } catch {
      urlObj = new URL(url, window.location.origin);
    }
    if (channel) {
      urlObj.searchParams.delete("channel");
      urlObj.searchParams.set("channel", channel);
    }
    const finalUrl = urlObj.toString();
    const image = await loadImage(finalUrl);
    return { image, url: finalUrl };
  }
  __name(loadImageLayer, "loadImageLayer");
  function loadImage(url) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.crossOrigin = "anonymous";
      img.onload = () => resolve(img);
      img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
      img.src = url;
    });
  }
  __name(loadImage, "loadImage");
  async function loadPaintLayer(url) {
    let urlObj;
    try {
      urlObj = new URL(url);
    } catch {
      urlObj = new URL(url, window.location.origin);
    }
    const finalUrl = urlObj.toString();
    const image = await loadImage(finalUrl);
    return { image, url: finalUrl };
  }
  __name(loadPaintLayer, "loadPaintLayer");
  return {
    loadFromNode
  };
}
__name(useMaskEditorLoader, "useMaskEditorLoader");
function usePanAndZoom() {
  const store = useMaskEditorStore();
  const DOUBLE_TAP_DELAY = 300;
  const lastTwoFingerTap = ref(0);
  const isTouchZooming = ref(false);
  const lastTouchZoomDistance = ref(0);
  const lastTouchMidPoint = ref({ x: 0, y: 0 });
  const lastTouchPoint = ref({ x: 0, y: 0 });
  const zoom_ratio = ref(1);
  const interpolatedZoomRatio = ref(1);
  const pan_offset = ref({ x: 0, y: 0 });
  const mouseDownPoint = ref(null);
  const initialPan = ref({ x: 0, y: 0 });
  const canvasContainer = ref(null);
  const maskCanvas = ref(null);
  const rgbCanvas = ref(null);
  const rootElement = ref(null);
  const toolPanelElement = ref(null);
  const sidePanelElement = ref(null);
  const image = ref(null);
  const imageRootWidth = ref(0);
  const imageRootHeight = ref(0);
  const cursorPoint = ref({ x: 0, y: 0 });
  const penPointerIdList = ref([]);
  const getTouchDistance = /* @__PURE__ */ __name((touches) => {
    const dx = touches[0].clientX - touches[1].clientX;
    const dy = touches[0].clientY - touches[1].clientY;
    return Math.sqrt(dx * dx + dy * dy);
  }, "getTouchDistance");
  const getTouchMidpoint = /* @__PURE__ */ __name((touches) => {
    return {
      x: (touches[0].clientX + touches[1].clientX) / 2,
      y: (touches[0].clientY + touches[1].clientY) / 2
    };
  }, "getTouchMidpoint");
  const updateCursorPosition = /* @__PURE__ */ __name((clientPoint) => {
    const cursorX = clientPoint.x - pan_offset.value.x;
    const cursorY = clientPoint.y - pan_offset.value.y;
    cursorPoint.value = { x: cursorX, y: cursorY };
    store.setCursorPoint({ x: cursorX, y: cursorY });
  }, "updateCursorPosition");
  const handleDoubleTap = /* @__PURE__ */ __name(() => {
    store.canvasHistory.undo();
  }, "handleDoubleTap");
  const invalidatePanZoom = /* @__PURE__ */ __name(async () => {
    if (!image.value?.width || !image.value?.height || !pan_offset.value || !zoom_ratio.value) {
      console.warn("Missing required properties for pan/zoom");
      return;
    }
    const raw_width = image.value.width * zoom_ratio.value;
    const raw_height = image.value.height * zoom_ratio.value;
    if (!canvasContainer.value) {
      canvasContainer.value = store.canvasContainer;
    }
    if (!canvasContainer.value) return;
    Object.assign(canvasContainer.value.style, {
      width: `${raw_width}px`,
      height: `${raw_height}px`,
      left: `${pan_offset.value.x}px`,
      top: `${pan_offset.value.y}px`
    });
    if (!rgbCanvas.value) {
      rgbCanvas.value = store.rgbCanvas;
    }
    if (rgbCanvas.value) {
      if (rgbCanvas.value.width !== image.value.width || rgbCanvas.value.height !== image.value.height) {
        rgbCanvas.value.width = image.value.width;
        rgbCanvas.value.height = image.value.height;
      }
      rgbCanvas.value.style.width = `${raw_width}px`;
      rgbCanvas.value.style.height = `${raw_height}px`;
    }
    store.setPanOffset(pan_offset.value);
    store.setZoomRatio(zoom_ratio.value);
  }, "invalidatePanZoom");
  const handlePanStart = /* @__PURE__ */ __name((event) => {
    mouseDownPoint.value = { x: event.clientX, y: event.clientY };
    store.isPanning = true;
    initialPan.value = { ...pan_offset.value };
  }, "handlePanStart");
  const handlePanMove = /* @__PURE__ */ __name(async (event) => {
    if (mouseDownPoint.value === null) {
      throw new Error("mouseDownPoint is null");
    }
    const deltaX = mouseDownPoint.value.x - event.clientX;
    const deltaY = mouseDownPoint.value.y - event.clientY;
    const pan_x = initialPan.value.x - deltaX;
    const pan_y = initialPan.value.y - deltaY;
    pan_offset.value = { x: pan_x, y: pan_y };
    await invalidatePanZoom();
  }, "handlePanMove");
  const handleSingleTouchPan = /* @__PURE__ */ __name(async (touch) => {
    if (lastTouchPoint.value === null) {
      lastTouchPoint.value = { x: touch.clientX, y: touch.clientY };
      return;
    }
    const deltaX = touch.clientX - lastTouchPoint.value.x;
    const deltaY = touch.clientY - lastTouchPoint.value.y;
    pan_offset.value.x += deltaX;
    pan_offset.value.y += deltaY;
    await invalidatePanZoom();
    lastTouchPoint.value = { x: touch.clientX, y: touch.clientY };
  }, "handleSingleTouchPan");
  const handleTouchStart = /* @__PURE__ */ __name((event) => {
    event.preventDefault();
    if (penPointerIdList.value.length > 0) return;
    store.brushVisible = false;
    if (event.touches.length === 2) {
      const currentTime = (/* @__PURE__ */ new Date()).getTime();
      const tapTimeDiff = currentTime - lastTwoFingerTap.value;
      if (tapTimeDiff < DOUBLE_TAP_DELAY) {
        handleDoubleTap();
        lastTwoFingerTap.value = 0;
      } else {
        lastTwoFingerTap.value = currentTime;
        isTouchZooming.value = true;
        lastTouchZoomDistance.value = getTouchDistance(event.touches);
        lastTouchMidPoint.value = getTouchMidpoint(event.touches);
      }
    } else if (event.touches.length === 1) {
      lastTouchPoint.value = {
        x: event.touches[0].clientX,
        y: event.touches[0].clientY
      };
    }
  }, "handleTouchStart");
  const handleTouchMove = /* @__PURE__ */ __name(async (event) => {
    event.preventDefault();
    if (penPointerIdList.value.length > 0) return;
    lastTwoFingerTap.value = 0;
    if (isTouchZooming.value && event.touches.length === 2) {
      const newDistance = getTouchDistance(event.touches);
      const zoomFactor = newDistance / lastTouchZoomDistance.value;
      const oldZoom = zoom_ratio.value;
      zoom_ratio.value = Math.max(
        0.2,
        Math.min(10, zoom_ratio.value * zoomFactor)
      );
      const newZoom = zoom_ratio.value;
      const midpoint = getTouchMidpoint(event.touches);
      if (lastTouchMidPoint.value) {
        const deltaX = midpoint.x - lastTouchMidPoint.value.x;
        const deltaY = midpoint.y - lastTouchMidPoint.value.y;
        pan_offset.value.x += deltaX;
        pan_offset.value.y += deltaY;
      }
      if (maskCanvas.value === null) {
        maskCanvas.value = store.maskCanvas;
      }
      if (!maskCanvas.value) return;
      const rect = maskCanvas.value.getBoundingClientRect();
      const touchX = midpoint.x - rect.left;
      const touchY = midpoint.y - rect.top;
      const scaleFactor2 = newZoom / oldZoom;
      pan_offset.value.x += touchX - touchX * scaleFactor2;
      pan_offset.value.y += touchY - touchY * scaleFactor2;
      await invalidatePanZoom();
      lastTouchZoomDistance.value = newDistance;
      lastTouchMidPoint.value = midpoint;
    } else if (event.touches.length === 1) {
      await handleSingleTouchPan(event.touches[0]);
    }
  }, "handleTouchMove");
  const handleTouchEnd = /* @__PURE__ */ __name((event) => {
    event.preventDefault();
    const lastTouch = event.touches[0];
    if (lastTouch) {
      lastTouchPoint.value = {
        x: lastTouch.clientX,
        y: lastTouch.clientY
      };
    } else {
      isTouchZooming.value = false;
      lastTouchMidPoint.value = { x: 0, y: 0 };
    }
  }, "handleTouchEnd");
  const zoom = /* @__PURE__ */ __name(async (event) => {
    const cursorPosition = { x: event.clientX, y: event.clientY };
    const oldZoom = zoom_ratio.value;
    const zoomFactor = event.deltaY < 0 ? 1.1 : 0.9;
    zoom_ratio.value = Math.max(
      0.2,
      Math.min(10, zoom_ratio.value * zoomFactor)
    );
    const newZoom = zoom_ratio.value;
    if (!maskCanvas.value) {
      maskCanvas.value = store.maskCanvas;
    }
    if (!maskCanvas.value) return;
    const rect = maskCanvas.value.getBoundingClientRect();
    const mouseX = cursorPosition.x - rect.left;
    const mouseY = cursorPosition.y - rect.top;
    const scaleFactor2 = newZoom / oldZoom;
    pan_offset.value.x += mouseX - mouseX * scaleFactor2;
    pan_offset.value.y += mouseY - mouseY * scaleFactor2;
    await invalidatePanZoom();
    const newImageWidth = maskCanvas.value.clientWidth;
    const zoomRatio = newImageWidth / imageRootWidth.value;
    interpolatedZoomRatio.value = zoomRatio;
    store.displayZoomRatio = zoomRatio;
    updateCursorPosition(cursorPosition);
  }, "zoom");
  const getPanelDimensions = /* @__PURE__ */ __name(() => {
    const toolPanelWidth = toolPanelElement.value?.getBoundingClientRect().width || 64;
    const sidePanelWidth = sidePanelElement.value?.getBoundingClientRect().width || 220;
    return { sidePanelWidth, toolPanelWidth };
  }, "getPanelDimensions");
  const smoothResetView = /* @__PURE__ */ __name(async (duration = 500) => {
    if (!image.value || !rootElement.value) return;
    const startZoom = zoom_ratio.value;
    const startPan = { ...pan_offset.value };
    const { sidePanelWidth, toolPanelWidth } = getPanelDimensions();
    const availableWidth = rootElement.value.clientWidth - sidePanelWidth - toolPanelWidth;
    const availableHeight = rootElement.value.clientHeight;
    const zoomRatioWidth = availableWidth / image.value.width;
    const zoomRatioHeight = availableHeight / image.value.height;
    const targetZoom = Math.min(zoomRatioWidth, zoomRatioHeight);
    const aspectRatio = image.value.width / image.value.height;
    let finalWidth;
    let finalHeight;
    const targetPan = { x: toolPanelWidth, y: 0 };
    if (zoomRatioHeight > zoomRatioWidth) {
      finalWidth = availableWidth;
      finalHeight = finalWidth / aspectRatio;
      targetPan.y = (availableHeight - finalHeight) / 2;
    } else {
      finalHeight = availableHeight;
      finalWidth = finalHeight * aspectRatio;
      targetPan.x = (availableWidth - finalWidth) / 2 + toolPanelWidth;
    }
    const startTime = performance.now();
    const animate = /* @__PURE__ */ __name(async (currentTime) => {
      const elapsed = currentTime - startTime;
      const progress = Math.min(elapsed / duration, 1);
      const eased = 1 - Math.pow(1 - progress, 3);
      zoom_ratio.value = startZoom + (targetZoom - startZoom) * eased;
      pan_offset.value.x = startPan.x + (targetPan.x - startPan.x) * eased;
      pan_offset.value.y = startPan.y + (targetPan.y - startPan.y) * eased;
      await invalidatePanZoom();
      const interpolatedRatio = startZoom + (1 - startZoom) * eased;
      store.displayZoomRatio = interpolatedRatio;
      if (progress < 1) {
        requestAnimationFrame(animate);
      }
    }, "animate");
    requestAnimationFrame(animate);
    interpolatedZoomRatio.value = 1;
  }, "smoothResetView");
  const initializeCanvasPanZoom = /* @__PURE__ */ __name(async (img, root, toolPanel, sidePanel) => {
    rootElement.value = root;
    toolPanelElement.value = toolPanel || null;
    sidePanelElement.value = sidePanel || null;
    const { sidePanelWidth, toolPanelWidth } = getPanelDimensions();
    const availableWidth = root.clientWidth - sidePanelWidth - toolPanelWidth;
    const availableHeight = root.clientHeight;
    const zoomRatioWidth = availableWidth / img.width;
    const zoomRatioHeight = availableHeight / img.height;
    const aspectRatio = img.width / img.height;
    let finalWidth;
    let finalHeight;
    const panOffset = { x: toolPanelWidth, y: 0 };
    if (zoomRatioHeight > zoomRatioWidth) {
      finalWidth = availableWidth;
      finalHeight = finalWidth / aspectRatio;
      panOffset.y = (availableHeight - finalHeight) / 2;
    } else {
      finalHeight = availableHeight;
      finalWidth = finalHeight * aspectRatio;
      panOffset.x = (availableWidth - finalWidth) / 2 + toolPanelWidth;
    }
    if (image.value === null) {
      image.value = img;
    }
    imageRootWidth.value = finalWidth;
    imageRootHeight.value = finalHeight;
    zoom_ratio.value = Math.min(zoomRatioWidth, zoomRatioHeight);
    pan_offset.value = panOffset;
    penPointerIdList.value = [];
    await invalidatePanZoom();
  }, "initializeCanvasPanZoom");
  watch(
    () => store.resetZoomTrigger,
    async () => {
      if (interpolatedZoomRatio.value === 1) return;
      await smoothResetView();
    }
  );
  const addPenPointerId = /* @__PURE__ */ __name((pointerId) => {
    if (!penPointerIdList.value.includes(pointerId)) {
      penPointerIdList.value.push(pointerId);
    }
  }, "addPenPointerId");
  const removePenPointerId = /* @__PURE__ */ __name((pointerId) => {
    const index2 = penPointerIdList.value.indexOf(pointerId);
    if (index2 !== -1) {
      penPointerIdList.value.splice(index2, 1);
    }
  }, "removePenPointerId");
  return {
    initializeCanvasPanZoom,
    handlePanStart,
    handlePanMove,
    handleTouchStart,
    handleTouchMove,
    handleTouchEnd,
    updateCursorPosition,
    zoom,
    invalidatePanZoom,
    addPenPointerId,
    removePenPointerId
  };
}
__name(usePanAndZoom, "usePanAndZoom");
function useCoordinateTransformInternal() {
  const store = useMaskEditorStore();
  const screenToCanvas = /* @__PURE__ */ __name((clientPoint) => {
    const pointerZoneEl = unref(store.pointerZone);
    const canvasContainerEl = unref(store.canvasContainer);
    const canvasEl = unref(store.maskCanvas);
    if (!pointerZoneEl || !canvasContainerEl || !canvasEl) {
      console.warn("screenToCanvas called before elements are available");
      return { x: 0, y: 0 };
    }
    const pointerZoneRect = pointerZoneEl.getBoundingClientRect();
    const canvasContainerRect = canvasContainerEl.getBoundingClientRect();
    const canvasRect = canvasEl.getBoundingClientRect();
    const absoluteX = pointerZoneRect.left + clientPoint.x;
    const absoluteY = pointerZoneRect.top + clientPoint.y;
    const canvasX = absoluteX - canvasContainerRect.left;
    const canvasY = absoluteY - canvasContainerRect.top;
    const scaleX = canvasEl.width / canvasRect.width;
    const scaleY = canvasEl.height / canvasRect.height;
    const x2 = canvasX * scaleX;
    const y = canvasY * scaleY;
    return { x: x2, y };
  }, "screenToCanvas");
  const canvasToScreen = /* @__PURE__ */ __name((canvasPoint) => {
    const pointerZoneEl = unref(store.pointerZone);
    const canvasContainerEl = unref(store.canvasContainer);
    const canvasEl = unref(store.maskCanvas);
    if (!pointerZoneEl || !canvasContainerEl || !canvasEl) {
      console.warn("canvasToScreen called before elements are available");
      return { x: 0, y: 0 };
    }
    const pointerZoneRect = pointerZoneEl.getBoundingClientRect();
    const canvasContainerRect = canvasContainerEl.getBoundingClientRect();
    const canvasRect = canvasEl.getBoundingClientRect();
    const scaleX = canvasRect.width / canvasEl.width;
    const scaleY = canvasRect.height / canvasEl.height;
    const displayX = canvasPoint.x * scaleX;
    const displayY = canvasPoint.y * scaleY;
    const absoluteX = canvasContainerRect.left + displayX;
    const absoluteY = canvasContainerRect.top + displayY;
    const x2 = absoluteX - pointerZoneRect.left;
    const y = absoluteY - pointerZoneRect.top;
    return { x: x2, y };
  }, "canvasToScreen");
  return {
    screenToCanvas,
    canvasToScreen
  };
}
__name(useCoordinateTransformInternal, "useCoordinateTransformInternal");
const useCoordinateTransform = createSharedComposable(
  useCoordinateTransformInternal
);
function catmullRomSpline(p0, p1, p2, p3, t2) {
  const alpha = 0.5;
  const getT = /* @__PURE__ */ __name((t4, p02, p12) => {
    const d2 = Math.hypot(p12.x - p02.x, p12.y - p02.y);
    return t4 + Math.pow(d2, alpha);
  }, "getT");
  const t0 = 0;
  const t1 = getT(t0, p0, p1);
  const t22 = getT(t1, p1, p2);
  const t3 = getT(t22, p2, p3);
  const tInterp = t1 + (t22 - t1) * t2;
  const interp = /* @__PURE__ */ __name((pA, pB, tA, tB, t4) => {
    if (Math.abs(tB - tA) < 1e-4) return pA;
    const k = (t4 - tA) / (tB - tA);
    return add(mul(pA, 1 - k), mul(pB, k));
  }, "interp");
  const A1 = interp(p0, p1, t0, t1, tInterp);
  const A2 = interp(p1, p2, t1, t22, tInterp);
  const A3 = interp(p2, p3, t22, t3, tInterp);
  const B1 = interp(A1, A2, t0, t22, tInterp);
  const B2 = interp(A2, A3, t1, t3, tInterp);
  const C = interp(B1, B2, t1, t22, tInterp);
  return C;
}
__name(catmullRomSpline, "catmullRomSpline");
function add(p1, p2) {
  return { x: p1.x + p2.x, y: p1.y + p2.y };
}
__name(add, "add");
function mul(p, s) {
  return { x: p.x * s, y: p.y * s };
}
__name(mul, "mul");
function resampleSegment(points, spacing, startOffset) {
  if (points.length === 0) return { points: [], remainder: startOffset };
  const result = [];
  let currentDist = 0;
  let nextSampleDist = startOffset;
  for (let i = 0; i < points.length - 1; i++) {
    const p1 = points[i];
    const p2 = points[i + 1];
    const dx = p2.x - p1.x;
    const dy = p2.y - p1.y;
    const segmentLen = Math.hypot(dx, dy);
    if (segmentLen < 1e-4) {
      while (nextSampleDist <= currentDist) {
        result.push(p1);
        nextSampleDist += spacing;
      }
      continue;
    }
    while (nextSampleDist <= currentDist + segmentLen) {
      const t2 = (nextSampleDist - currentDist) / segmentLen;
      const x2 = p1.x + t2 * dx;
      const y = p1.y + t2 * dy;
      result.push({ x: x2, y });
      nextSampleDist += spacing;
    }
    currentDist += segmentLen;
  }
  const remainder = nextSampleDist - currentDist;
  return { points: result, remainder };
}
__name(resampleSegment, "resampleSegment");
const BrushUniforms = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(qs({
  brushColor: Le,
  brushOpacity: h$1,
  hardness: h$1,
  screenSize: B,
  brushShape: c
  // 0: Circle, 1: Square
}), "BrushUniforms");
const StrokePoint = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(qs({
  pos: Ws(0, B),
  // Center position
  size: Ws(1, h$1),
  // Brush radius
  pressure: Ws(2, h$1)
  // Pressure value (0.0 - 1.0)
}), "StrokePoint");
const VertexOutput = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(qs({
  position: yo.position,
  localUV: Ws(0, B),
  color: Ws(1, Le),
  opacity: Ws(2, h$1),
  hardness: Ws(3, h$1)
}), "VertexOutput");
const brushVertexTemplate = `
@group(0) @binding(0) var<uniform> globals: BrushUniforms;

@vertex
fn vs(
  @location(0) quadPos: vec2<f32>,
  @location(1) pos: vec2<f32>,
  @location(2) size: f32,
  @location(3) pressure: f32
) -> VertexOutput {
  // Convert diameter to radius
  let radius = size * pressure;
  let pixelPos = pos + (quadPos * radius);
  
  // Convert pixel coordinates to Normalized Device Coordinates (NDC)
  let ndcX = (pixelPos.x / globals.screenSize.x) * 2.0 - 1.0;
  let ndcY = 1.0 - ((pixelPos.y / globals.screenSize.y) * 2.0);  // Flip Y axis for WebGPU coordinate system

  return VertexOutput(
    vec4<f32>(ndcX, ndcY, 0.0, 1.0),
    quadPos,
    globals.brushColor,
    pressure * globals.brushOpacity,
    globals.hardness
  );
}
`;
const brushVertex = Co.resolve({
  template: brushVertexTemplate,
  externals: {
    BrushUniforms,
    VertexOutput
  }
});
const brushFragmentTemplate = `
@group(0) @binding(0) var<uniform> globals: BrushUniforms;

@fragment
fn fs(v: VertexOutput) -> @location(0) vec4<f32> {
  var dist: f32;
  if (globals.brushShape == 1u) {
    // Calculate Chebyshev distance for square shape
    dist = max(abs(v.localUV.x), abs(v.localUV.y));
  } else {
    // Calculate Euclidean distance for circle shape
    dist = length(v.localUV);
  }

  if (dist > 1.0) { discard; }

  // Calculate alpha with hardness and anti-aliasing
  let edgeWidth = fwidth(dist);
  let startFade = min(v.hardness, 1.0 - edgeWidth * 2.0);
  let linearAlpha = 1.0 - smoothstep(startFade, 1.0, dist);
  // Apply quadratic falloff for smoother edges
  let alphaShape = pow(linearAlpha, 2.0);
  
  // Return premultiplied alpha color
  let alpha = alphaShape * v.opacity;
  return vec4<f32>(v.color * alpha, alpha);
}
`;
const brushFragment = Co.resolve({
  template: brushFragmentTemplate,
  externals: {
    VertexOutput,
    BrushUniforms
  }
});
const blitShaderTemplate = `
@vertex fn vs(@builtin(vertex_index) vIdx: u32) -> @builtin(position) vec4<f32> {
  var pos = array<vec2<f32>, 3>(
    vec2<f32>(-1.0, -1.0), vec2<f32>(3.0, -1.0), vec2<f32>(-1.0, 3.0)
  );
  return vec4<f32>(pos[vIdx], 0.0, 1.0);
}

@group(0) @binding(0) var myTexture: texture_2d<f32>;

@fragment fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
  let c = textureLoad(myTexture, vec2<i32>(pos.xy), 0);
  // Treat texture as premultiplied to prevent double-darkening on overlaps
  return c;
}
`;
const blitShader = Co.resolve({
  template: blitShaderTemplate,
  externals: {}
});
const compositeShaderTemplate = `
@vertex fn vs(@builtin(vertex_index) vIdx: u32) -> @builtin(position) vec4<f32> {
  var pos = array<vec2<f32>, 3>(
    vec2<f32>(-1.0, -1.0), vec2<f32>(3.0, -1.0), vec2<f32>(-1.0, 3.0)
  );
  return vec4<f32>(pos[vIdx], 0.0, 1.0);
}

@group(0) @binding(0) var myTexture: texture_2d<f32>;
@group(1) @binding(0) var<uniform> globals: BrushUniforms;

@fragment fn fs(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
  let sampled = textureLoad(myTexture, vec2<i32>(pos.xy), 0);
  // Apply global brush opacity to accumulated coverage
  return sampled * globals.brushOpacity;
}
`;
const compositeShader = Co.resolve({
  template: compositeShaderTemplate,
  externals: {
    BrushUniforms
  }
});
const readbackShaderTemplate = `
@group(0) @binding(0) var inputTex: texture_2d<f32>;
@group(0) @binding(1) var<storage, read_write> outputBuf: array<u32>;

@compute @workgroup_size(8, 8)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
  let dims = textureDimensions(inputTex);
  if (id.x >= dims.x || id.y >= dims.y) { return; }

  let color = textureLoad(inputTex, vec2<i32>(id.xy), 0);
  
  var r = color.r;
  var g = color.g;
  var b = color.b;
  let a = color.a;

  if (a > 0.0) {
    r = r / a;
    g = g / a;
    b = b / a;
  }

  let ir = u32(clamp(r * 255.0, 0.0, 255.0));
  let ig = u32(clamp(g * 255.0, 0.0, 255.0));
  let ib = u32(clamp(b * 255.0, 0.0, 255.0));
  let ia = u32(clamp(a * 255.0, 0.0, 255.0));

  // Pack RGBA channels into a single u32 (Little Endian)
  let packed = ir | (ig << 8u) | (ib << 16u) | (ia << 24u);

  let index = id.y * dims.x + id.x;
  outputBuf[index] = packed;
}
`;
const readbackShader = Co.resolve({
  template: readbackShaderTemplate,
  externals: {}
});
const QUAD_VERTS = new Float32Array([-1, -1, 1, -1, 1, 1, -1, 1]);
const QUAD_INDICES = new Uint16Array([0, 1, 2, 0, 2, 3]);
const UNIFORM_SIZE = 48;
const STROKE_STRIDE = $s(StrokePoint);
const MAX_STROKES = 1e4;
class GPUBrushRenderer {
  static {
    __name(this, "GPUBrushRenderer");
  }
  device;
  // Buffers
  quadVertexBuffer;
  indexBuffer;
  instanceBuffer;
  uniformBuffer;
  // Pipelines
  renderPipeline;
  // Standard alpha blending pipeline
  accumulatePipeline;
  // SourceOver blending pipeline for stroke accumulation
  blitPipeline;
  compositePipeline;
  // Composite pipeline that applies opacity
  compositePipelinePreview;
  // Pipeline for rendering to the preview canvas
  erasePipeline;
  // Pipeline for erasing (Destination Out)
  erasePipelinePreview;
  // Eraser pipeline for the preview canvas
  readbackPipeline;
  // Compute pipeline for texture readback
  // Bind Group Layouts
  uniformBindGroupLayout;
  textureBindGroupLayout;
  // Shared Bind Groups
  mainUniformBindGroup;
  // Textures
  currentStrokeTexture = null;
  currentStrokeView = null;
  // Cached Bind Groups
  compositeTextureBindGroup = null;
  previewTextureBindGroup = null;
  // Removed separate uniform bind groups as we will use mainUniformBindGroup
  lastReadbackTexture = null;
  lastReadbackBuffer = null;
  readbackBindGroup = null;
  lastBackgroundTexture = null;
  backgroundBindGroup = null;
  constructor(device, presentationFormat = "rgba8unorm") {
    this.device = device;
    this.quadVertexBuffer = device.createBuffer({
      size: QUAD_VERTS.byteLength,
      usage: GPUBufferUsage.VERTEX,
      mappedAtCreation: true
    });
    new Float32Array(this.quadVertexBuffer.getMappedRange()).set(QUAD_VERTS);
    this.quadVertexBuffer.unmap();
    this.indexBuffer = device.createBuffer({
      size: QUAD_INDICES.byteLength,
      usage: GPUBufferUsage.INDEX,
      mappedAtCreation: true
    });
    new Uint16Array(this.indexBuffer.getMappedRange()).set(QUAD_INDICES);
    this.indexBuffer.unmap();
    this.instanceBuffer = device.createBuffer({
      size: MAX_STROKES * STROKE_STRIDE,
      usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
    });
    this.uniformBuffer = device.createBuffer({
      size: UNIFORM_SIZE,
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    });
    const brushModuleV = device.createShaderModule({ code: brushVertex });
    const brushModuleF = device.createShaderModule({ code: brushFragment });
    this.uniformBindGroupLayout = device.createBindGroupLayout({
      entries: [
        {
          binding: 0,
          visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
          buffer: { type: "uniform" }
        }
      ]
    });
    this.textureBindGroupLayout = device.createBindGroupLayout({
      entries: [
        {
          binding: 0,
          visibility: GPUShaderStage.FRAGMENT,
          texture: {}
          // default is float, 2d
        }
      ]
    });
    this.mainUniformBindGroup = device.createBindGroup({
      layout: this.uniformBindGroupLayout,
      entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }]
    });
    const renderPipelineLayout = device.createPipelineLayout({
      bindGroupLayouts: [this.uniformBindGroupLayout]
    });
    this.renderPipeline = device.createRenderPipeline({
      layout: renderPipelineLayout,
      vertex: {
        module: brushModuleV,
        entryPoint: "vs",
        buffers: [
          {
            arrayStride: 8,
            stepMode: "vertex",
            attributes: [{ shaderLocation: 0, offset: 0, format: "float32x2" }]
            // Quad vertex attributes
          },
          {
            arrayStride: 16,
            stepMode: "instance",
            attributes: [
              { shaderLocation: 1, offset: 0, format: "float32x2" },
              // Instance attributes: position
              { shaderLocation: 2, offset: 8, format: "float32" },
              // size
              { shaderLocation: 3, offset: 12, format: "float32" }
              // pressure
            ]
          }
        ]
      },
      fragment: {
        module: brushModuleF,
        entryPoint: "fs",
        targets: [
          {
            format: "rgba8unorm",
            blend: {
              color: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              },
              alpha: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              }
            }
          }
        ]
      },
      primitive: { topology: "triangle-list" }
    });
    this.accumulatePipeline = device.createRenderPipeline({
      layout: renderPipelineLayout,
      vertex: {
        module: brushModuleV,
        entryPoint: "vs",
        buffers: [
          {
            arrayStride: 8,
            stepMode: "vertex",
            attributes: [{ shaderLocation: 0, offset: 0, format: "float32x2" }]
          },
          {
            arrayStride: 16,
            stepMode: "instance",
            attributes: [
              { shaderLocation: 1, offset: 0, format: "float32x2" },
              { shaderLocation: 2, offset: 8, format: "float32" },
              { shaderLocation: 3, offset: 12, format: "float32" }
            ]
          }
        ]
      },
      fragment: {
        module: brushModuleF,
        entryPoint: "fs",
        targets: [
          {
            format: "rgba8unorm",
            blend: {
              // Use SourceOver blending for smooth stroke intersections.
              color: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              },
              alpha: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              }
            }
          }
        ]
      },
      primitive: { topology: "triangle-list" }
    });
    const blitPipelineLayout = device.createPipelineLayout({
      bindGroupLayouts: [this.textureBindGroupLayout]
    });
    this.blitPipeline = device.createRenderPipeline({
      layout: blitPipelineLayout,
      vertex: {
        module: device.createShaderModule({ code: blitShader }),
        entryPoint: "vs"
      },
      fragment: {
        module: device.createShaderModule({ code: blitShader }),
        entryPoint: "fs",
        targets: [
          {
            format: presentationFormat,
            // Use the presentation format
            blend: {
              color: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              },
              alpha: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              }
            }
          }
        ]
      },
      primitive: { topology: "triangle-list" }
    });
    const compositePipelineLayout = device.createPipelineLayout({
      bindGroupLayouts: [
        this.textureBindGroupLayout,
        this.uniformBindGroupLayout
      ]
    });
    this.compositePipeline = device.createRenderPipeline({
      layout: compositePipelineLayout,
      vertex: {
        module: device.createShaderModule({ code: compositeShader }),
        entryPoint: "vs"
      },
      fragment: {
        module: device.createShaderModule({ code: compositeShader }),
        entryPoint: "fs",
        targets: [
          {
            format: "rgba8unorm",
            blend: {
              color: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              },
              alpha: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              }
            }
          }
        ]
      },
      primitive: { topology: "triangle-list" }
    });
    this.compositePipelinePreview = device.createRenderPipeline({
      layout: compositePipelineLayout,
      vertex: {
        module: device.createShaderModule({ code: compositeShader }),
        entryPoint: "vs"
      },
      fragment: {
        module: device.createShaderModule({ code: compositeShader }),
        entryPoint: "fs",
        targets: [
          {
            format: presentationFormat,
            blend: {
              color: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              },
              alpha: {
                srcFactor: "one",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              }
            }
          }
        ]
      },
      primitive: { topology: "triangle-list" }
    });
    this.erasePipeline = device.createRenderPipeline({
      layout: compositePipelineLayout,
      vertex: {
        module: device.createShaderModule({ code: compositeShader }),
        entryPoint: "vs"
      },
      fragment: {
        module: device.createShaderModule({ code: compositeShader }),
        entryPoint: "fs",
        targets: [
          {
            format: "rgba8unorm",
            blend: {
              color: {
                srcFactor: "zero",
                dstFactor: "one-minus-src-alpha",
                // dst * (1 - src_alpha)
                operation: "add"
              },
              alpha: {
                srcFactor: "zero",
                dstFactor: "one-minus-src-alpha",
                // dst_alpha * (1 - src_alpha)
                operation: "add"
              }
            }
          }
        ]
      },
      primitive: { topology: "triangle-list" }
    });
    this.erasePipelinePreview = device.createRenderPipeline({
      layout: compositePipelineLayout,
      vertex: {
        module: device.createShaderModule({ code: compositeShader }),
        entryPoint: "vs"
      },
      fragment: {
        module: device.createShaderModule({ code: compositeShader }),
        entryPoint: "fs",
        targets: [
          {
            format: presentationFormat,
            blend: {
              color: {
                srcFactor: "zero",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              },
              alpha: {
                srcFactor: "zero",
                dstFactor: "one-minus-src-alpha",
                operation: "add"
              }
            }
          }
        ]
      },
      primitive: { topology: "triangle-list" }
    });
    this.readbackPipeline = device.createComputePipeline({
      layout: "auto",
      compute: {
        module: device.createShaderModule({ code: readbackShader }),
        entryPoint: "main"
      }
    });
  }
  prepareStroke(width2, height) {
    if (!this.currentStrokeTexture || this.currentStrokeTexture.width !== width2 || this.currentStrokeTexture.height !== height) {
      if (this.currentStrokeTexture) this.currentStrokeTexture.destroy();
      this.currentStrokeTexture = this.device.createTexture({
        size: [width2, height],
        format: "rgba8unorm",
        usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC
      });
      this.currentStrokeView = this.currentStrokeTexture.createView();
      this.compositeTextureBindGroup = null;
      this.previewTextureBindGroup = null;
      if (this.lastReadbackTexture === this.currentStrokeTexture) {
        this.readbackBindGroup = null;
        this.lastReadbackTexture = null;
      }
    }
    const encoder = this.device.createCommandEncoder();
    const pass = encoder.beginRenderPass({
      colorAttachments: [
        {
          view: this.currentStrokeView,
          loadOp: "clear",
          clearValue: { r: 0, g: 0, b: 0, a: 0 },
          storeOp: "store"
        }
      ]
    });
    pass.end();
    this.device.queue.submit([encoder.finish()]);
  }
  renderStrokeToAccumulator(points, settings) {
    if (!this.currentStrokeView) return;
    this.renderStrokeInternal(
      this.currentStrokeView,
      this.accumulatePipeline,
      points,
      settings
    );
  }
  compositeStroke(targetView, settings) {
    if (!this.currentStrokeTexture) return;
    const buffer = new ArrayBuffer(UNIFORM_SIZE);
    const f32 = new Float32Array(buffer);
    const u32 = new Uint32Array(buffer);
    f32[0] = settings.color[0];
    f32[1] = settings.color[1];
    f32[2] = settings.color[2];
    f32[3] = settings.opacity;
    f32[4] = settings.hardness;
    f32[5] = 0;
    f32[6] = settings.screenSize[0];
    f32[7] = settings.screenSize[1];
    u32[8] = settings.brushShape;
    this.device.queue.writeBuffer(this.uniformBuffer, 0, buffer);
    const encoder = this.device.createCommandEncoder();
    const pipeline = settings.isErasing ? this.erasePipeline : this.compositePipeline;
    if (!this.compositeTextureBindGroup) {
      this.compositeTextureBindGroup = this.device.createBindGroup({
        layout: this.textureBindGroupLayout,
        entries: [
          { binding: 0, resource: this.currentStrokeTexture.createView() }
        ]
      });
    }
    const pass = encoder.beginRenderPass({
      colorAttachments: [
        {
          view: targetView,
          loadOp: "load",
          storeOp: "store"
        }
      ]
    });
    pass.setPipeline(pipeline);
    pass.setBindGroup(0, this.compositeTextureBindGroup);
    pass.setBindGroup(1, this.mainUniformBindGroup);
    pass.draw(3);
    pass.end();
    this.device.queue.submit([encoder.finish()]);
  }
  // Direct rendering method
  renderStroke(targetView, points, settings) {
    this.renderStrokeInternal(targetView, this.renderPipeline, points, settings);
  }
  renderStrokeInternal(targetView, pipeline, points, settings) {
    if (points.length === 0) return;
    const buffer = new ArrayBuffer(UNIFORM_SIZE);
    const f32 = new Float32Array(buffer);
    const u32 = new Uint32Array(buffer);
    f32[0] = settings.color[0];
    f32[1] = settings.color[1];
    f32[2] = settings.color[2];
    f32[3] = settings.opacity;
    f32[4] = settings.hardness;
    f32[5] = 0;
    f32[6] = settings.width;
    f32[7] = settings.height;
    u32[8] = settings.brushShape;
    this.device.queue.writeBuffer(this.uniformBuffer, 0, buffer);
    let processedPoints = 0;
    while (processedPoints < points.length) {
      const batchSize = Math.min(points.length - processedPoints, MAX_STROKES);
      const iData = new Float32Array(batchSize * 4);
      for (let i = 0; i < batchSize; i++) {
        const p = points[processedPoints + i];
        iData[i * 4 + 0] = p.x;
        iData[i * 4 + 1] = p.y;
        iData[i * 4 + 2] = settings.size;
        iData[i * 4 + 3] = p.pressure;
      }
      this.device.queue.writeBuffer(this.instanceBuffer, 0, iData);
      const encoder = this.device.createCommandEncoder();
      const pass = encoder.beginRenderPass({
        colorAttachments: [
          {
            view: targetView,
            loadOp: "load",
            storeOp: "store"
          }
        ]
      });
      pass.setPipeline(pipeline);
      pass.setBindGroup(0, this.mainUniformBindGroup);
      pass.setVertexBuffer(0, this.quadVertexBuffer);
      pass.setVertexBuffer(1, this.instanceBuffer);
      pass.setIndexBuffer(this.indexBuffer, "uint16");
      pass.drawIndexed(6, batchSize);
      pass.end();
      this.device.queue.submit([encoder.finish()]);
      processedPoints += batchSize;
    }
  }
  // Blit the accumulated stroke to the preview canvas
  blitToCanvas(destinationCtx, settings, backgroundTexture) {
    const encoder = this.device.createCommandEncoder();
    const destView = destinationCtx.getCurrentTexture().createView();
    if (backgroundTexture) {
      if (this.lastBackgroundTexture !== backgroundTexture || !this.backgroundBindGroup) {
        this.backgroundBindGroup = this.device.createBindGroup({
          layout: this.textureBindGroupLayout,
          entries: [{ binding: 0, resource: backgroundTexture.createView() }]
        });
        this.lastBackgroundTexture = backgroundTexture;
      }
      const pass = encoder.beginRenderPass({
        colorAttachments: [
          {
            view: destView,
            loadOp: "clear",
            // Clear attachment before drawing
            clearValue: { r: 0, g: 0, b: 0, a: 0 },
            storeOp: "store"
          }
        ]
      });
      pass.setPipeline(this.blitPipeline);
      pass.setBindGroup(0, this.backgroundBindGroup);
      pass.draw(3);
      pass.end();
    } else {
      const clearPass = encoder.beginRenderPass({
        colorAttachments: [
          {
            view: destView,
            loadOp: "clear",
            clearValue: { r: 0, g: 0, b: 0, a: 0 },
            storeOp: "store"
          }
        ]
      });
      clearPass.end();
    }
    if (this.currentStrokeTexture) {
      const buffer = new ArrayBuffer(UNIFORM_SIZE);
      const f32 = new Float32Array(buffer);
      const u32 = new Uint32Array(buffer);
      f32[0] = settings.color[0];
      f32[1] = settings.color[1];
      f32[2] = settings.color[2];
      f32[3] = settings.opacity;
      f32[4] = settings.hardness;
      f32[5] = 0;
      f32[6] = settings.screenSize[0];
      f32[7] = settings.screenSize[1];
      u32[8] = settings.brushShape;
      this.device.queue.writeBuffer(this.uniformBuffer, 0, buffer);
      const pipeline = settings.isErasing ? this.erasePipelinePreview : this.compositePipelinePreview;
      if (!this.previewTextureBindGroup) {
        this.previewTextureBindGroup = this.device.createBindGroup({
          layout: this.textureBindGroupLayout,
          entries: [
            { binding: 0, resource: this.currentStrokeTexture.createView() }
          ]
        });
      }
      const passStroke = encoder.beginRenderPass({
        colorAttachments: [
          {
            view: destView,
            loadOp: "load",
            // Load the previous pass result
            storeOp: "store"
          }
        ]
      });
      passStroke.setPipeline(pipeline);
      passStroke.setBindGroup(0, this.previewTextureBindGroup);
      passStroke.setBindGroup(1, this.mainUniformBindGroup);
      passStroke.draw(3);
      passStroke.end();
    }
    this.device.queue.submit([encoder.finish()]);
  }
  // Clear the preview canvas
  clearPreview(destinationCtx) {
    const encoder = this.device.createCommandEncoder();
    const pass = encoder.beginRenderPass({
      colorAttachments: [
        {
          view: destinationCtx.getCurrentTexture().createView(),
          loadOp: "clear",
          clearValue: { r: 0, g: 0, b: 0, a: 0 },
          storeOp: "store"
        }
      ]
    });
    pass.end();
    this.device.queue.submit([encoder.finish()]);
  }
  prepareReadback(texture, outputBuffer) {
    if (this.lastReadbackTexture !== texture || this.lastReadbackBuffer !== outputBuffer || !this.readbackBindGroup) {
      this.readbackBindGroup = this.device.createBindGroup({
        layout: this.readbackPipeline.getBindGroupLayout(0),
        entries: [
          { binding: 0, resource: texture.createView() },
          { binding: 1, resource: { buffer: outputBuffer } }
        ]
      });
      this.lastReadbackTexture = texture;
      this.lastReadbackBuffer = outputBuffer;
    }
    const encoder = this.device.createCommandEncoder();
    const pass = encoder.beginComputePass();
    pass.setPipeline(this.readbackPipeline);
    pass.setBindGroup(0, this.readbackBindGroup);
    const width2 = texture.width;
    const height = texture.height;
    pass.dispatchWorkgroups(Math.ceil(width2 / 8), Math.ceil(height / 8));
    pass.end();
    this.device.queue.submit([encoder.finish()]);
  }
  destroy() {
    this.quadVertexBuffer.destroy();
    this.indexBuffer.destroy();
    this.instanceBuffer.destroy();
    this.uniformBuffer.destroy();
    if (this.currentStrokeTexture) this.currentStrokeTexture.destroy();
    this.compositeTextureBindGroup = null;
    this.previewTextureBindGroup = null;
    this.readbackBindGroup = null;
    this.backgroundBindGroup = null;
    this.lastReadbackTexture = null;
    this.lastReadbackBuffer = null;
    this.lastBackgroundTexture = null;
  }
}
class StrokeProcessor {
  static {
    __name(this, "StrokeProcessor");
  }
  controlPoints = [];
  remainder = 0;
  spacing;
  isFirstPoint = true;
  hasProcessedSegment = false;
  constructor(spacing) {
    this.spacing = spacing;
  }
  /**
   * Adds a point to the stroke and returns any new equidistant points generated.
   * Maintain a sliding window of 4 control points for spline generation
   */
  addPoint(point) {
    if (this.isFirstPoint) {
      this.controlPoints.push(point);
      this.controlPoints.push(point);
      this.isFirstPoint = false;
      return [];
    }
    this.controlPoints.push(point);
    if (this.controlPoints.length < 4) {
      return [];
    }
    const p0 = this.controlPoints[0];
    const p1 = this.controlPoints[1];
    const p2 = this.controlPoints[2];
    const p3 = this.controlPoints[3];
    const newPoints = this.processSegment(p0, p1, p2, p3);
    this.controlPoints.shift();
    return newPoints;
  }
  /**
   * End stroke and flush remaining segments
   */
  endStroke() {
    if (this.controlPoints.length < 2) {
      return [];
    }
    const newPoints = [];
    while (this.controlPoints.length >= 3) {
      const p0 = this.controlPoints[0];
      const p1 = this.controlPoints[1];
      const p2 = this.controlPoints[2];
      const p3 = p2;
      const points = this.processSegment(p0, p1, p2, p3);
      newPoints.push(...points);
      this.controlPoints.shift();
    }
    if (!this.hasProcessedSegment && this.controlPoints.length >= 2) {
      const p = this.controlPoints[1];
      const points = this.processSegment(p, p, p, p);
      newPoints.push(...points);
    }
    return newPoints;
  }
  processSegment(p0, p1, p2, p3) {
    this.hasProcessedSegment = true;
    const densePoints = [];
    const dist = Math.hypot(p2.x - p1.x, p2.y - p1.y);
    const samples = Math.max(5, Math.ceil(dist));
    for (let i = 0; i < samples; i++) {
      const t2 = i / samples;
      densePoints.push(catmullRomSpline(p0, p1, p2, p3, t2));
    }
    densePoints.push(p2);
    const { points, remainder } = resampleSegment(
      densePoints,
      this.spacing,
      this.remainder
    );
    this.remainder = remainder;
    return points;
  }
}
function getEffectiveBrushSize(size, hardness) {
  const MAX_SCALE = 1.5;
  const scale = 1 + (1 - hardness) * (MAX_SCALE - 1);
  return size * scale;
}
__name(getEffectiveBrushSize, "getEffectiveBrushSize");
function getEffectiveHardness(size, hardness, effectiveSize) {
  if (effectiveSize <= 0) return 0;
  return size * hardness / effectiveSize;
}
__name(getEffectiveHardness, "getEffectiveHardness");
const saveBrushToCache = debounce(function(key, brush) {
  try {
    const brushString = JSON.stringify(brush);
    setStorageValue(key, brushString);
  } catch (error) {
    console.error("Failed to save brush to cache:", error);
  }
}, 300);
function loadBrushFromCache(key) {
  try {
    const brushString = getStorageValue(key);
    if (brushString) {
      return JSON.parse(brushString);
    } else {
      return null;
    }
  } catch (error) {
    console.error("Failed to load brush from cache:", error);
    return null;
  }
}
__name(loadBrushFromCache, "loadBrushFromCache");
function useBrushDrawing(initialSettings) {
  const store = useMaskEditorStore();
  const coordinateTransform = useCoordinateTransform();
  let maskTexture = null;
  let rgbTexture = null;
  let device = null;
  let renderer = null;
  let previewContext = null;
  let previewCanvas = null;
  let readbackStorageMask = null;
  let readbackStorageRgb = null;
  let readbackStagingMask = null;
  let readbackStagingRgb = null;
  let currentBufferSize = 0;
  const isSavingHistory = ref(false);
  const brushTextureCache = new QuickLRU({
    maxSize: 20
  });
  function getCachedBrushTexture(radius, hardness, color2, opacity) {
    const cacheKey = `${radius}_${hardness}_${color2}_${opacity}`;
    if (brushTextureCache.has(cacheKey)) {
      return brushTextureCache.get(cacheKey);
    }
    const size = Math.ceil(radius * 2);
    const tempCanvas = document.createElement("canvas");
    const tempCtx = tempCanvas.getContext("2d");
    tempCanvas.width = size;
    tempCanvas.height = size;
    const centerX = size / 2;
    const centerY = size / 2;
    const hardRadius = radius * hardness;
    const imageData = tempCtx.createImageData(size, size);
    const data = imageData.data;
    const { r, g: g2, b } = parseToRgb(color2);
    const fadeRange = radius - hardRadius;
    for (let y = 0; y < size; y++) {
      const dy = y + 0.5 - centerY;
      for (let x2 = 0; x2 < size; x2++) {
        const dx = x2 + 0.5 - centerX;
        const index2 = (y * size + x2) * 4;
        const distFromEdge = Math.max(Math.abs(dx), Math.abs(dy));
        let pixelOpacity = 0;
        if (distFromEdge <= hardRadius) {
          pixelOpacity = opacity;
        } else if (distFromEdge <= radius) {
          const fadeProgress = (distFromEdge - hardRadius) / fadeRange;
          pixelOpacity = opacity * Math.pow(1 - fadeProgress, 2);
        }
        data[index2] = r;
        data[index2 + 1] = g2;
        data[index2 + 2] = b;
        data[index2 + 3] = pixelOpacity * 255;
      }
    }
    tempCtx.putImageData(imageData, 0, 0);
    brushTextureCache.set(cacheKey, tempCanvas);
    return tempCanvas;
  }
  __name(getCachedBrushTexture, "getCachedBrushTexture");
  const isDrawing = ref(false);
  const isDrawingLine = ref(false);
  const lineStartPoint = ref(null);
  const lineRemainder = ref(0);
  const smoothingLastDrawTime = ref(/* @__PURE__ */ new Date());
  const initialDraw = ref(true);
  const dirtyRect = ref({
    minX: Infinity,
    minY: Infinity,
    maxX: -Infinity,
    maxY: -Infinity
  });
  function resetDirtyRect() {
    dirtyRect.value = {
      minX: Infinity,
      minY: Infinity,
      maxX: -Infinity,
      maxY: -Infinity
    };
  }
  __name(resetDirtyRect, "resetDirtyRect");
  function updateDirtyRect(x2, y, radius) {
    const padding = 2;
    dirtyRect.value.minX = Math.min(dirtyRect.value.minX, x2 - radius - padding);
    dirtyRect.value.minY = Math.min(dirtyRect.value.minY, y - radius - padding);
    dirtyRect.value.maxX = Math.max(dirtyRect.value.maxX, x2 + radius + padding);
    dirtyRect.value.maxY = Math.max(dirtyRect.value.maxY, y + radius + padding);
  }
  __name(updateDirtyRect, "updateDirtyRect");
  let strokeProcessor = null;
  const initialPoint = ref(null);
  const useDominantAxis = ref(initialSettings?.useDominantAxis ?? false);
  const brushAdjustmentSpeed = ref(initialSettings?.brushAdjustmentSpeed ?? 1);
  const cachedBrushSettings = loadBrushFromCache("maskeditor_brush_settings");
  if (cachedBrushSettings) {
    store.setBrushSize(cachedBrushSettings.size);
    store.setBrushOpacity(cachedBrushSettings.opacity);
    store.setBrushHardness(cachedBrushSettings.hardness);
    store.brushSettings.type = cachedBrushSettings.type;
    store.setBrushStepSize(cachedBrushSettings.stepSize ?? 5);
  }
  watch(
    () => store.clearTrigger,
    () => {
      clearGPU();
    }
  );
  watch(
    () => store.canvasHistory.currentStateIndex,
    async () => {
      if (isSavingHistory.value) return;
      await updateGPUFromCanvas();
      if (renderer && previewContext) {
        renderer.clearPreview(previewContext);
      }
    }
  );
  onUnmounted(() => {
    if (renderer) {
      renderer.destroy();
      renderer = null;
    }
    if (maskTexture) {
      maskTexture.destroy();
      maskTexture = null;
    }
    if (rgbTexture) {
      rgbTexture.destroy();
      rgbTexture = null;
    }
    if (readbackStorageMask) {
      readbackStorageMask.destroy();
      readbackStorageMask = null;
    }
    if (readbackStorageRgb) {
      readbackStorageRgb.destroy();
      readbackStorageRgb = null;
    }
    if (readbackStagingMask) {
      readbackStagingMask.destroy();
      readbackStagingMask = null;
    }
    if (readbackStagingRgb) {
      readbackStagingRgb.destroy();
      readbackStagingRgb = null;
    }
  });
  async function initTypeGPU() {
    if (store.tgpuRoot) {
      device = store.tgpuRoot.device;
      return;
    }
    try {
      const root = await Co.init();
      store.tgpuRoot = root;
      device = root.device;
      console.warn("✅ TypeGPU initialized! Root:", root);
      console.warn("Device info:", root.device.limits);
    } catch (error) {
      console.warn("Failed to initialize TypeGPU:", error.message);
    }
  }
  __name(initTypeGPU, "initTypeGPU");
  function premultiplyData(data) {
    for (let i = 0; i < data.length; i += 4) {
      const a = data[i + 3] / 255;
      data[i] = Math.round(data[i] * a);
      data[i + 1] = Math.round(data[i + 1] * a);
      data[i + 2] = Math.round(data[i + 2] * a);
    }
  }
  __name(premultiplyData, "premultiplyData");
  async function updateGPUFromCanvas() {
    if (!device || !maskTexture || !rgbTexture || !store.maskCanvas || !store.rgbCtx)
      return;
    const canvasWidth = store.maskCanvas.width;
    const canvasHeight = store.maskCanvas.height;
    const maskImageData = store.maskCtx.getImageData(
      0,
      0,
      canvasWidth,
      canvasHeight
    );
    premultiplyData(maskImageData.data);
    device.queue.writeTexture(
      { texture: maskTexture },
      maskImageData.data,
      { bytesPerRow: canvasWidth * 4 },
      { width: canvasWidth, height: canvasHeight }
    );
    const rgbImageData = store.rgbCtx.getImageData(
      0,
      0,
      canvasWidth,
      canvasHeight
    );
    premultiplyData(rgbImageData.data);
    device.queue.writeTexture(
      { texture: rgbTexture },
      rgbImageData.data,
      { bytesPerRow: canvasWidth * 4 },
      { width: canvasWidth, height: canvasHeight }
    );
  }
  __name(updateGPUFromCanvas, "updateGPUFromCanvas");
  async function initGPUResources() {
    await initTypeGPU();
    if (!store.tgpuRoot || !device) {
      console.warn("TypeGPU not initialized, skipping GPU resource setup");
      return;
    }
    if (!store.maskCanvas || !store.rgbCanvas || !store.maskCtx || !store.rgbCtx) {
      console.warn("Canvas contexts not ready, skipping GPU resource setup");
      return;
    }
    const canvasWidth = store.maskCanvas.width;
    const canvasHeight = store.maskCanvas.height;
    try {
      console.warn(
        `🎨 Initializing GPU resources for ${canvasWidth}x${canvasHeight} canvas`
      );
      maskTexture = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(device.createTexture({
        size: [canvasWidth, canvasHeight],
        format: "rgba8unorm",
        usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
      }), "maskTexture");
      rgbTexture = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(device.createTexture({
        size: [canvasWidth, canvasHeight],
        format: "rgba8unorm",
        usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
      }), "rgbTexture");
      await updateGPUFromCanvas();
      console.warn("✅ GPU resources initialized successfully");
      const preferredFormat = navigator.gpu.getPreferredCanvasFormat();
      renderer = new GPUBrushRenderer(device, preferredFormat);
      console.warn("✅ Brush renderer initialized");
    } catch (error) {
      console.error("Failed to initialize GPU resources:", error);
      maskTexture = null;
      rgbTexture = null;
    }
  }
  __name(initGPUResources, "initGPUResources");
  function drawShape(point, overrideOpacity) {
    const brush = store.brushSettings;
    const mask_ctx = store.maskCtx;
    const rgb_ctx = store.rgbCtx;
    if (!mask_ctx || !rgb_ctx) {
      throw new Error("Canvas contexts are required");
    }
    const brushType = brush.type;
    const brushRadius = brush.size;
    const hardness = brush.hardness;
    const opacity = overrideOpacity ?? brush.opacity;
    const isErasing = mask_ctx.globalCompositeOperation === "destination-out";
    const currentTool = store.currentTool;
    const isRgbLayer = store.activeLayer === "rgb";
    if (isRgbLayer && currentTool && (currentTool === Tools.Eraser || currentTool === Tools.PaintPen)) {
      const effectiveRadius2 = getEffectiveBrushSize(brushRadius, hardness);
      const effectiveHardness2 = getEffectiveHardness(
        brushRadius,
        hardness,
        effectiveRadius2
      );
      drawRgbShape(
        rgb_ctx,
        point,
        brushType,
        effectiveRadius2,
        effectiveHardness2,
        opacity
      );
      return;
    }
    const effectiveRadius = getEffectiveBrushSize(brushRadius, hardness);
    const effectiveHardness = getEffectiveHardness(
      brushRadius,
      hardness,
      effectiveRadius
    );
    drawMaskShape(
      mask_ctx,
      point,
      brushType,
      effectiveRadius,
      effectiveHardness,
      opacity,
      isErasing
    );
    updateDirtyRect(point.x, point.y, effectiveRadius);
  }
  __name(drawShape, "drawShape");
  function drawRgbShape(ctx, point, brushType, brushRadius, hardness, opacity) {
    const { x: x2, y } = point;
    const rgbColor = store.rgbColor;
    if (brushType === BrushShape.Rect && hardness < 1) {
      const rgbaColor = formatRgba(rgbColor, opacity);
      const brushTexture = getCachedBrushTexture(
        brushRadius,
        hardness,
        rgbaColor,
        opacity
      );
      ctx.drawImage(brushTexture, x2 - brushRadius, y - brushRadius);
      updateDirtyRect(x2, y, brushRadius);
      return;
    }
    if (hardness === 1) {
      const rgbaColor = formatRgba(rgbColor, opacity);
      ctx.fillStyle = rgbaColor;
      drawShapeOnContext(ctx, brushType, x2, y, brushRadius);
      updateDirtyRect(x2, y, brushRadius);
      return;
    }
    const gradient = createBrushGradient(
      ctx,
      x2,
      y,
      brushRadius,
      hardness,
      rgbColor,
      opacity,
      false
    );
    ctx.fillStyle = gradient;
    drawShapeOnContext(ctx, brushType, x2, y, brushRadius);
    updateDirtyRect(x2, y, brushRadius);
  }
  __name(drawRgbShape, "drawRgbShape");
  function drawMaskShape(ctx, point, brushType, brushRadius, hardness, opacity, isErasing) {
    const { x: x2, y } = point;
    const maskColor = store.maskColor;
    if (brushType === BrushShape.Rect && hardness < 1) {
      const baseColor = isErasing ? `rgba(255, 255, 255, ${opacity})` : `rgba(${maskColor.r}, ${maskColor.g}, ${maskColor.b}, ${opacity})`;
      const brushTexture = getCachedBrushTexture(
        brushRadius,
        hardness,
        baseColor,
        opacity
      );
      ctx.drawImage(brushTexture, x2 - brushRadius, y - brushRadius);
      updateDirtyRect(x2, y, brushRadius);
      return;
    }
    if (hardness === 1) {
      ctx.fillStyle = isErasing ? `rgba(255, 255, 255, ${opacity})` : `rgba(${maskColor.r}, ${maskColor.g}, ${maskColor.b}, ${opacity})`;
      drawShapeOnContext(ctx, brushType, x2, y, brushRadius);
      updateDirtyRect(x2, y, brushRadius);
      return;
    }
    const maskColorHex = `rgb(${maskColor.r}, ${maskColor.g}, ${maskColor.b})`;
    const gradient = createBrushGradient(
      ctx,
      x2,
      y,
      brushRadius,
      hardness,
      maskColorHex,
      opacity,
      isErasing
    );
    ctx.fillStyle = gradient;
    drawShapeOnContext(ctx, brushType, x2, y, brushRadius);
    updateDirtyRect(x2, y, brushRadius);
  }
  __name(drawMaskShape, "drawMaskShape");
  function drawShapeOnContext(ctx, brushType, x2, y, radius) {
    ctx.beginPath();
    if (brushType === BrushShape.Rect) {
      ctx.rect(x2 - radius, y - radius, radius * 2, radius * 2);
    } else {
      ctx.arc(x2, y, radius, 0, Math.PI * 2, false);
    }
    ctx.fill();
  }
  __name(drawShapeOnContext, "drawShapeOnContext");
  function formatRgba(hex, alpha) {
    const { r, g: g2, b } = hexToRgb(hex);
    return `rgba(${r}, ${g2}, ${b}, ${alpha})`;
  }
  __name(formatRgba, "formatRgba");
  function createBrushGradient(ctx, x2, y, radius, hardness, color2, opacity, isErasing) {
    if (!Number.isFinite(x2) || !Number.isFinite(y) || !Number.isFinite(radius)) {
      return ctx.createRadialGradient(0, 0, 0, 0, 0, 0);
    }
    const gradient = ctx.createRadialGradient(x2, y, 0, x2, y, radius);
    if (isErasing) {
      gradient.addColorStop(0, `rgba(255, 255, 255, ${opacity})`);
      gradient.addColorStop(hardness, `rgba(255, 255, 255, ${opacity})`);
      gradient.addColorStop(1, `rgba(255, 255, 255, 0)`);
    } else {
      const { r, g: g2, b } = parseToRgb(color2);
      gradient.addColorStop(0, `rgba(${r}, ${g2}, ${b}, ${opacity})`);
      gradient.addColorStop(hardness, `rgba(${r}, ${g2}, ${b}, ${opacity})`);
      gradient.addColorStop(1, `rgba(${r}, ${g2}, ${b}, 0)`);
    }
    return gradient;
  }
  __name(createBrushGradient, "createBrushGradient");
  async function drawWithBetterSmoothing(point) {
    if (!strokeProcessor) return;
    const newPoints = strokeProcessor.addPoint(point);
    if (newPoints.length === 0) {
      if (renderer && initialDraw.value) {
        gpuRender([], true);
      }
      return;
    }
    if (renderer) {
      gpuRender(newPoints, true);
    } else {
      for (const p of newPoints) {
        drawShape(p);
      }
    }
    initialDraw.value = false;
  }
  __name(drawWithBetterSmoothing, "drawWithBetterSmoothing");
  function initShape(compositionOperation) {
    const blendMode = store.maskBlendMode;
    const mask_ctx = store.maskCtx;
    const rgb_ctx = store.rgbCtx;
    if (!mask_ctx || !rgb_ctx) {
      throw new Error("Canvas contexts are required");
    }
    mask_ctx.beginPath();
    rgb_ctx.beginPath();
    if (compositionOperation === CompositionOperation.SourceOver) {
      mask_ctx.fillStyle = blendMode;
      mask_ctx.globalCompositeOperation = CompositionOperation.SourceOver;
      rgb_ctx.globalCompositeOperation = CompositionOperation.SourceOver;
    } else if (compositionOperation === CompositionOperation.DestinationOut) {
      mask_ctx.globalCompositeOperation = CompositionOperation.DestinationOut;
      rgb_ctx.globalCompositeOperation = CompositionOperation.DestinationOut;
    }
  }
  __name(initShape, "initShape");
  async function drawLine(p1, p2, compositionOp, spacing) {
    const { points, remainder } = resampleSegment(
      [p1, p2],
      spacing,
      lineRemainder.value
    );
    lineRemainder.value = remainder;
    initShape(compositionOp);
    if (renderer) {
      gpuRender(points);
    } else {
      for (const point of points) {
        drawShape(point, 1);
      }
    }
  }
  __name(drawLine, "drawLine");
  async function startDrawing(event) {
    isDrawing.value = true;
    resetDirtyRect();
    try {
      if (renderer && store.maskCanvas) {
        renderer.prepareStroke(store.maskCanvas.width, store.maskCanvas.height);
      }
      let compositionOp;
      const currentTool = store.currentTool;
      const coords = { x: event.offsetX, y: event.offsetY };
      const coords_canvas = coordinateTransform.screenToCanvas(coords);
      if (currentTool === "eraser" || event.buttons === 2) {
        compositionOp = CompositionOperation.DestinationOut;
      } else {
        compositionOp = CompositionOperation.SourceOver;
      }
      const stepPercentage = store.brushSettings.stepSize / 100;
      const targetSpacing = Math.max(
        1,
        store.brushSettings.size * stepPercentage
      );
      if (event.shiftKey && lineStartPoint.value) {
        isDrawingLine.value = true;
        await drawLine(
          lineStartPoint.value,
          coords_canvas,
          compositionOp,
          targetSpacing
        );
      } else {
        isDrawingLine.value = false;
        initShape(compositionOp);
        lineRemainder.value = targetSpacing;
      }
      lineStartPoint.value = coords_canvas;
      if (renderer) {
        const isRgb = store.activeLayer === "rgb";
        if (isRgb && store.rgbCanvas) {
          store.rgbCanvas.style.opacity = "0";
          if (previewCanvas) previewCanvas.style.opacity = "1";
        } else if (!isRgb && store.maskCanvas) {
          store.maskCanvas.style.opacity = "0";
          if (previewCanvas)
            previewCanvas.style.opacity = String(store.maskOpacity);
        }
      }
      strokeProcessor = new StrokeProcessor(targetSpacing);
      await drawWithBetterSmoothing(coords_canvas);
      smoothingLastDrawTime.value = /* @__PURE__ */ new Date();
    } catch (error) {
      console.error("[useBrushDrawing] Failed to start drawing:", error);
      isDrawing.value = false;
      isDrawingLine.value = false;
    }
  }
  __name(startDrawing, "startDrawing");
  async function handleDrawing(event) {
    const diff2 = performance.now() - smoothingLastDrawTime.value.getTime();
    const coords = { x: event.offsetX, y: event.offsetY };
    const coords_canvas = coordinateTransform.screenToCanvas(coords);
    const currentTool = store.currentTool;
    if (diff2 > 20 && !isDrawing.value) {
      requestAnimationFrame(async () => {
        if (!isDrawing.value) return;
        try {
          initShape(CompositionOperation.SourceOver);
          await gpuDrawPoint(coords_canvas);
        } catch (error) {
          console.error("[useBrushDrawing] Drawing error:", error);
        }
      });
    } else {
      requestAnimationFrame(async () => {
        if (!isDrawing.value) return;
        try {
          if (currentTool === "eraser" || event.buttons === 2) {
            initShape(CompositionOperation.DestinationOut);
          } else {
            initShape(CompositionOperation.SourceOver);
          }
          await drawWithBetterSmoothing(coords_canvas);
        } catch (error) {
          console.error("[useBrushDrawing] Drawing error:", error);
        }
      });
    }
    smoothingLastDrawTime.value = /* @__PURE__ */ new Date();
  }
  __name(handleDrawing, "handleDrawing");
  async function drawEnd(event) {
    const coords = { x: event.offsetX, y: event.offsetY };
    const coords_canvas = coordinateTransform.screenToCanvas(coords);
    if (isDrawing.value) {
      isDrawing.value = false;
      lineStartPoint.value = coords_canvas;
      initialDraw.value = true;
      if (strokeProcessor) {
        const finalPoints = strokeProcessor.endStroke();
        if (finalPoints.length > 0) {
          if (renderer) {
            gpuRender(finalPoints, true);
          } else {
            for (const p of finalPoints) {
              drawShape(p);
            }
          }
        }
        strokeProcessor = null;
      }
      if (renderer && maskTexture && rgbTexture) {
        const isRgb = store.activeLayer === "rgb";
        const targetTex = isRgb ? rgbTexture : maskTexture;
        const size = store.brushSettings.size;
        const hardness = store.brushSettings.hardness;
        const effectiveSize = getEffectiveBrushSize(size, hardness);
        const effectiveHardness = getEffectiveHardness(
          size,
          hardness,
          effectiveSize
        );
        const isErasing = store.currentTool === "eraser" || store.maskCtx?.globalCompositeOperation === "destination-out";
        const brushShape = store.brushSettings.type === BrushShape.Rect ? 1 : 0;
        renderer.compositeStroke(targetTex.createView(), {
          opacity: store.brushSettings.opacity,
          color: [0, 0, 0],
          // Color is handled by accumulator, this is just for uniforms if needed
          hardness: effectiveHardness,
          screenSize: [store.maskCanvas.width, store.maskCanvas.height],
          brushShape,
          isErasing
        });
      }
      let maskData;
      let rgbData;
      if (renderer && maskTexture && rgbTexture) {
        try {
          const result = await copyGpuToCanvas();
          maskData = result.maskData;
          rgbData = result.rgbData;
        } catch (error) {
          console.warn("GPU readback failed, falling back to CPU:", error);
        }
      }
      isSavingHistory.value = true;
      store.canvasHistory.saveState(maskData, rgbData);
      await nextTick();
      isSavingHistory.value = false;
      if (renderer && previewContext) {
        renderer.clearPreview(previewContext);
      }
      if (store.activeLayer === "rgb" && store.rgbCanvas) {
        store.rgbCanvas.style.opacity = "1";
      } else if (store.activeLayer === "mask" && store.maskCanvas) {
        store.maskCanvas.style.opacity = String(store.maskOpacity);
      }
      if (previewCanvas) {
        previewCanvas.style.opacity = "1";
      }
    }
  }
  __name(drawEnd, "drawEnd");
  async function startBrushAdjustment(event) {
    event.preventDefault();
    const coords = { x: event.offsetX, y: event.offsetY };
    const coords_canvas = coordinateTransform.screenToCanvas(coords);
    store.brushPreviewGradientVisible = true;
    initialPoint.value = coords_canvas;
  }
  __name(startBrushAdjustment, "startBrushAdjustment");
  async function handleBrushAdjustment(event) {
    if (!initialPoint.value) {
      return;
    }
    const coords = { x: event.offsetX, y: event.offsetY };
    const brushDeadZone = 5;
    const coords_canvas = coordinateTransform.screenToCanvas(coords);
    const delta_x = coords_canvas.x - initialPoint.value.x;
    const delta_y = coords_canvas.y - initialPoint.value.y;
    const effectiveDeltaX = Math.abs(delta_x) < brushDeadZone ? 0 : delta_x;
    const effectiveDeltaY = Math.abs(delta_y) < brushDeadZone ? 0 : delta_y;
    let finalDeltaX = effectiveDeltaX;
    let finalDeltaY = effectiveDeltaY;
    if (useDominantAxis.value) {
      const ratio = Math.abs(effectiveDeltaX) / Math.abs(effectiveDeltaY);
      const threshold = 2;
      if (ratio > threshold) {
        finalDeltaY = 0;
      } else if (ratio < 1 / threshold) {
        finalDeltaX = 0;
      }
    }
    const cappedDeltaX = Math.max(-100, Math.min(100, finalDeltaX));
    const cappedDeltaY = Math.max(-100, Math.min(100, finalDeltaY));
    const newSize = Math.max(
      1,
      Math.min(
        500,
        store.brushSettings.size + cappedDeltaX / 35 * brushAdjustmentSpeed.value
      )
    );
    const newHardness = Math.max(
      0,
      Math.min(
        1,
        store.brushSettings.hardness - cappedDeltaY / 4e3 * brushAdjustmentSpeed.value
      )
    );
    store.setBrushSize(newSize);
    store.setBrushHardness(newHardness);
  }
  __name(handleBrushAdjustment, "handleBrushAdjustment");
  function saveBrushSettings() {
    saveBrushToCache("maskeditor_brush_settings", store.brushSettings);
  }
  __name(saveBrushSettings, "saveBrushSettings");
  async function copyGpuToCanvas() {
    if (!device || !maskTexture || !rgbTexture || !store.maskCanvas || !store.rgbCanvas || !store.maskCtx || !store.rgbCtx || !renderer)
      throw new Error("GPU resources not ready");
    const width2 = store.maskCanvas.width;
    const height = store.maskCanvas.height;
    const bufferSize = width2 * height * 4;
    if (!readbackStorageMask || !readbackStorageRgb || !readbackStagingMask || !readbackStagingRgb || currentBufferSize !== bufferSize) {
      readbackStorageMask?.destroy();
      readbackStorageRgb?.destroy();
      readbackStagingMask?.destroy();
      readbackStagingRgb?.destroy();
      readbackStorageMask = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
      }), "readbackStorageMask");
      readbackStorageRgb = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
      }), "readbackStorageRgb");
      readbackStagingMask = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
      }), "readbackStagingMask");
      readbackStagingRgb = (globalThis.__TYPEGPU_AUTONAME__ ?? ((a) => a))(device.createBuffer({
        size: bufferSize,
        usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
      }), "readbackStagingRgb");
      currentBufferSize = bufferSize;
    }
    renderer.prepareReadback(maskTexture, readbackStorageMask);
    renderer.prepareReadback(rgbTexture, readbackStorageRgb);
    const encoder = device.createCommandEncoder();
    encoder.copyBufferToBuffer(
      readbackStorageMask,
      0,
      readbackStagingMask,
      0,
      bufferSize
    );
    encoder.copyBufferToBuffer(
      readbackStorageRgb,
      0,
      readbackStagingRgb,
      0,
      bufferSize
    );
    device.queue.submit([encoder.finish()]);
    await Promise.all([
      readbackStagingMask.mapAsync(GPUMapMode.READ),
      readbackStagingRgb.mapAsync(GPUMapMode.READ)
    ]);
    const maskDataArr = new Uint8ClampedArray(
      readbackStagingMask.getMappedRange().slice(0)
    );
    const rgbDataArr = new Uint8ClampedArray(
      readbackStagingRgb.getMappedRange().slice(0)
    );
    readbackStagingMask.unmap();
    readbackStagingRgb.unmap();
    const maskImageData = new ImageData(maskDataArr, width2, height);
    const rgbImageData = new ImageData(rgbDataArr, width2, height);
    let dx = 0;
    let dy = 0;
    let dw = width2;
    let dh = height;
    if (dirtyRect.value.minX !== Infinity && dirtyRect.value.maxX !== -Infinity) {
      const r = dirtyRect.value;
      dx = Math.floor(Math.max(0, r.minX));
      dy = Math.floor(Math.max(0, r.minY));
      const max_x = Math.ceil(Math.min(width2, r.maxX));
      const max_y = Math.ceil(Math.min(height, r.maxY));
      dw = max_x - dx;
      dh = max_y - dy;
    }
    if (dw > 0 && dh > 0) {
      store.maskCtx.putImageData(maskImageData, 0, 0, dx, dy, dw, dh);
      store.rgbCtx.putImageData(rgbImageData, 0, 0, dx, dy, dw, dh);
    } else {
      store.maskCtx.putImageData(maskImageData, 0, 0);
      store.rgbCtx.putImageData(rgbImageData, 0, 0);
    }
    return { maskData: maskImageData, rgbData: rgbImageData };
  }
  __name(copyGpuToCanvas, "copyGpuToCanvas");
  function destroy() {
    renderer?.destroy();
    if (maskTexture) {
      maskTexture.destroy();
      maskTexture = null;
    }
    if (rgbTexture) {
      rgbTexture.destroy();
      rgbTexture = null;
    }
    readbackStorageMask?.destroy();
    readbackStorageRgb?.destroy();
    readbackStagingMask?.destroy();
    readbackStagingRgb?.destroy();
    readbackStorageMask = null;
    readbackStorageRgb = null;
    readbackStagingMask = null;
    readbackStagingRgb = null;
    currentBufferSize = 0;
    if (store.tgpuRoot) {
      store.tgpuRoot.destroy();
      store.tgpuRoot = null;
    }
    device = null;
  }
  __name(destroy, "destroy");
  async function gpuDrawPoint(point, opacity = 1) {
    if (renderer) {
      const width2 = store.maskCanvas.width;
      const height = store.maskCanvas.height;
      const strokePoints = [{ x: point.x, y: point.y, pressure: opacity }];
      const size = store.brushSettings.size;
      const hardness = store.brushSettings.hardness;
      const effectiveSize = getEffectiveBrushSize(size, hardness);
      const effectiveHardness = getEffectiveHardness(
        size,
        hardness,
        effectiveSize
      );
      const brushShape = store.brushSettings.type === BrushShape.Rect ? 1 : 0;
      renderer.renderStrokeToAccumulator(strokePoints, {
        size: effectiveSize,
        opacity: 0.5,
        // Fixed flow for smooth accumulation
        hardness: effectiveHardness,
        color: [1, 1, 1],
        width: width2,
        height,
        brushShape
      });
      if (maskTexture && previewContext) {
        const isRgb = store.activeLayer === "rgb";
        let color2 = [1, 1, 1];
        if (isRgb) {
          const c2 = parseToRgb(store.rgbColor);
          color2 = [c2.r / 255, c2.g / 255, c2.b / 255];
        } else {
          const c2 = store.maskColor;
          color2 = [c2.r / 255, c2.g / 255, c2.b / 255];
        }
        const isErasing = store.currentTool === "eraser" || store.maskCtx?.globalCompositeOperation === "destination-out";
        renderer.blitToCanvas(
          previewContext,
          {
            opacity: store.brushSettings.opacity,
            color: color2,
            hardness: effectiveHardness,
            screenSize: [width2, height],
            brushShape,
            isErasing
          },
          void 0
          // Do not draw background texture for preview to avoid double rendering
        );
      }
    } else {
      drawShape(point, opacity);
    }
  }
  __name(gpuDrawPoint, "gpuDrawPoint");
  function initPreviewCanvas(canvas2) {
    if (!device) return;
    const ctx = canvas2.getContext("webgpu");
    if (!ctx) return;
    ctx.configure({
      device,
      format: navigator.gpu.getPreferredCanvasFormat(),
      alphaMode: "premultiplied"
    });
    previewContext = ctx;
    previewCanvas = canvas2;
    console.warn("✅ Preview Canvas Initialized");
  }
  __name(initPreviewCanvas, "initPreviewCanvas");
  function gpuRender(points, skipResampling = false) {
    if (!renderer || !maskTexture || !rgbTexture) return;
    const isRgb = store.activeLayer === "rgb";
    let color2 = [1, 1, 1];
    if (isRgb) {
      const c2 = parseToRgb(store.rgbColor);
      color2 = [c2.r / 255, c2.g / 255, c2.b / 255];
    } else {
      const c2 = store.maskColor;
      color2 = [c2.r / 255, c2.g / 255, c2.b / 255];
    }
    let strokePoints = [];
    if (skipResampling) {
      strokePoints = points.map((p) => ({ x: p.x, y: p.y, pressure: 1 }));
    } else {
      for (let i = 0; i < points.length - 1; i++) {
        const p1 = points[i];
        const p2 = points[i + 1];
        const dist = Math.hypot(p2.x - p1.x, p2.y - p1.y);
        const stepPercentage = store.brushSettings.stepSize / 100;
        const stepSize = Math.max(
          1,
          store.brushSettings.size * stepPercentage
        );
        const steps = Math.max(1, Math.ceil(dist / stepSize));
        for (let step = 0; step <= steps; step++) {
          const t2 = step / steps;
          const pressure = 1;
          const x2 = p1.x + (p2.x - p1.x) * t2;
          const y = p1.y + (p2.y - p1.y) * t2;
          strokePoints.push({ x: x2, y, pressure });
        }
      }
    }
    const size = store.brushSettings.size;
    const hardness = store.brushSettings.hardness;
    const effectiveSize = getEffectiveBrushSize(size, hardness);
    const effectiveHardness = getEffectiveHardness(
      size,
      hardness,
      effectiveSize
    );
    const brushShape = store.brushSettings.type === BrushShape.Rect ? 1 : 0;
    renderer.renderStrokeToAccumulator(strokePoints, {
      size: effectiveSize,
      opacity: 0.5,
      hardness: effectiveHardness,
      color: color2,
      width: store.maskCanvas.width,
      height: store.maskCanvas.height,
      brushShape
    });
    for (const p of strokePoints) {
      updateDirtyRect(p.x, p.y, effectiveSize);
    }
    if (previewContext) {
      const isErasing = store.currentTool === "eraser" || store.maskCtx?.globalCompositeOperation === "destination-out";
      const targetTex = isRgb ? rgbTexture : maskTexture;
      renderer.blitToCanvas(
        previewContext,
        {
          opacity: store.brushSettings.opacity,
          color: color2,
          hardness: effectiveHardness,
          screenSize: [store.maskCanvas.width, store.maskCanvas.height],
          brushShape,
          isErasing
        },
        targetTex ?? void 0
      );
    }
  }
  __name(gpuRender, "gpuRender");
  function clearGPU() {
    if (!device || !maskTexture || !rgbTexture || !store.maskCanvas) return;
    const width2 = store.maskCanvas.width;
    const height = store.maskCanvas.height;
    device.queue.writeTexture(
      { texture: maskTexture },
      new Uint8Array(width2 * height * 4),
      // Zeros
      { bytesPerRow: width2 * 4 },
      { width: width2, height }
    );
    device.queue.writeTexture(
      { texture: rgbTexture },
      new Uint8Array(width2 * height * 4),
      // Zeros
      { bytesPerRow: width2 * 4 },
      { width: width2, height }
    );
  }
  __name(clearGPU, "clearGPU");
  return {
    startDrawing,
    handleDrawing,
    drawEnd,
    startBrushAdjustment,
    handleBrushAdjustment,
    saveBrushSettings,
    destroy,
    initGPUResources,
    initPreviewCanvas,
    clearGPU
    // Export this
  };
}
__name(useBrushDrawing, "useBrushDrawing");
function useToolManager(keyboard, panZoom) {
  const store = useMaskEditorStore();
  const coordinateTransform = useCoordinateTransform();
  const brushDrawing = useBrushDrawing({
    useDominantAxis: app$1.extensionManager.setting.get(
      "Comfy.MaskEditor.UseDominantAxis"
    ),
    brushAdjustmentSpeed: app$1.extensionManager.setting.get(
      "Comfy.MaskEditor.BrushAdjustmentSpeed"
    )
  });
  const canvasTools = useCanvasTools();
  const mouseDownPoint = ref(null);
  const toolSettings = {
    [Tools.MaskPen]: {
      newActiveLayerOnSet: "mask"
    },
    [Tools.Eraser]: {},
    [Tools.PaintPen]: {
      newActiveLayerOnSet: "rgb"
    },
    [Tools.MaskBucket]: {
      cursor: "url('/cursor/paintBucket.png') 30 25, auto",
      newActiveLayerOnSet: "mask"
    },
    [Tools.MaskColorFill]: {
      cursor: "url('/cursor/colorSelect.png') 15 25, auto",
      newActiveLayerOnSet: "mask"
    }
  };
  const setActiveLayer = /* @__PURE__ */ __name((layer) => {
    store.activeLayer = layer;
    const currentTool = store.currentTool;
    const maskOnlyTools = [Tools.MaskPen, Tools.MaskBucket, Tools.MaskColorFill];
    if (maskOnlyTools.includes(currentTool) && layer === "rgb") {
      switchTool(Tools.PaintPen);
    }
    if (currentTool === Tools.PaintPen && layer === "mask") {
      switchTool(Tools.MaskPen);
    }
  }, "setActiveLayer");
  const switchTool = /* @__PURE__ */ __name((tool) => {
    store.currentTool = tool;
    const newActiveLayer = toolSettings[tool].newActiveLayerOnSet;
    if (newActiveLayer) {
      store.activeLayer = newActiveLayer;
    }
    const cursor = toolSettings[tool].cursor;
    const pointerZone = store.pointerZone;
    if (cursor && pointerZone) {
      store.brushVisible = false;
      pointerZone.style.cursor = cursor;
    } else if (pointerZone) {
      store.brushVisible = true;
      pointerZone.style.cursor = "none";
    }
  }, "switchTool");
  const updateCursor = /* @__PURE__ */ __name(() => {
    const currentTool = store.currentTool;
    const cursor = toolSettings[currentTool].cursor;
    const pointerZone = store.pointerZone;
    if (cursor && pointerZone) {
      store.brushVisible = false;
      pointerZone.style.cursor = cursor;
    } else if (pointerZone) {
      store.brushVisible = true;
      pointerZone.style.cursor = "none";
    }
    store.brushPreviewGradientVisible = false;
  }, "updateCursor");
  watch(
    () => store.currentTool,
    (newTool) => {
      if (newTool !== Tools.MaskColorFill) {
        canvasTools.clearLastColorSelectPoint();
      }
    }
  );
  const handlePointerDown = /* @__PURE__ */ __name(async (event) => {
    event.preventDefault();
    if (event.pointerType === "touch") return;
    if (event.pointerType === "pen") {
      panZoom.addPenPointerId(event.pointerId);
    }
    const isSpacePressed = keyboard.isKeyDown(" ");
    if (event.buttons === 4 || event.buttons === 1 && isSpacePressed) {
      panZoom.handlePanStart(event);
      store.brushVisible = false;
      return;
    }
    if (store.currentTool === Tools.PaintPen && event.button === 0) {
      await brushDrawing.startDrawing(event);
      return;
    }
    if (store.currentTool === Tools.PaintPen && event.buttons === 1) {
      await brushDrawing.handleDrawing(event);
      return;
    }
    if (store.currentTool === Tools.MaskBucket && event.button === 0) {
      const offset = { x: event.offsetX, y: event.offsetY };
      const coords_canvas = coordinateTransform.screenToCanvas(offset);
      canvasTools.paintBucketFill(coords_canvas);
      return;
    }
    if (store.currentTool === Tools.MaskColorFill && event.button === 0) {
      const offset = { x: event.offsetX, y: event.offsetY };
      const coords_canvas = coordinateTransform.screenToCanvas(offset);
      await canvasTools.colorSelectFill(coords_canvas);
      return;
    }
    if (event.altKey && event.button === 2) {
      store.isAdjustingBrush = true;
      await brushDrawing.startBrushAdjustment(event);
      return;
    }
    const isDrawingTool = [
      Tools.MaskPen,
      Tools.Eraser,
      Tools.PaintPen
    ].includes(store.currentTool);
    if ([0, 2].includes(event.button) && isDrawingTool) {
      await brushDrawing.startDrawing(event);
      return;
    }
  }, "handlePointerDown");
  const handlePointerMove = /* @__PURE__ */ __name(async (event) => {
    event.preventDefault();
    if (event.pointerType === "touch") return;
    const newCursorPoint = { x: event.clientX, y: event.clientY };
    panZoom.updateCursorPosition(newCursorPoint);
    const isSpacePressed = keyboard.isKeyDown(" ");
    if (event.buttons === 4 || event.buttons === 1 && isSpacePressed) {
      await panZoom.handlePanMove(event);
      return;
    }
    const isDrawingTool = [
      Tools.MaskPen,
      Tools.Eraser,
      Tools.PaintPen
    ].includes(store.currentTool);
    if (!isDrawingTool) return;
    if (store.isAdjustingBrush && (store.currentTool === Tools.MaskPen || store.currentTool === Tools.Eraser) && event.altKey && event.buttons === 2) {
      await brushDrawing.handleBrushAdjustment(event);
      return;
    }
    if (event.buttons === 1 || event.buttons === 2) {
      await brushDrawing.handleDrawing(event);
      return;
    }
  }, "handlePointerMove");
  const handlePointerUp2 = /* @__PURE__ */ __name(async (event) => {
    store.isPanning = false;
    store.brushVisible = true;
    if (event.pointerType === "pen") {
      panZoom.removePenPointerId(event.pointerId);
    }
    if (event.pointerType === "touch") return;
    updateCursor();
    store.isAdjustingBrush = false;
    await brushDrawing.drawEnd(event);
    mouseDownPoint.value = null;
  }, "handlePointerUp");
  return {
    switchTool,
    setActiveLayer,
    updateCursor,
    handlePointerDown,
    handlePointerMove,
    handlePointerUp: handlePointerUp2,
    brushDrawing
  };
}
__name(useToolManager, "useToolManager");
const _sfc_main$1q = /* @__PURE__ */ defineComponent({
  __name: "BrushCursor",
  props: {
    containerRef: {}
  },
  setup(__props) {
    const store = useMaskEditorStore();
    const brushOpacity = computed(() => {
      return store.brushVisible ? 1 : 0;
    });
    const brushRadius = computed(() => {
      const size = store.brushSettings.size;
      const hardness = store.brushSettings.hardness;
      const effectiveSize = getEffectiveBrushSize(size, hardness);
      return effectiveSize * store.zoomRatio;
    });
    const brushSize = computed(() => {
      return brushRadius.value * 2;
    });
    const brushLeft = computed(() => {
      const dialogRect = __props.containerRef?.getBoundingClientRect();
      const dialogOffsetLeft = dialogRect?.left || 0;
      return store.cursorPoint.x + store.panOffset.x - brushRadius.value - dialogOffsetLeft;
    });
    const brushTop = computed(() => {
      const dialogRect = __props.containerRef?.getBoundingClientRect();
      const dialogOffsetTop = dialogRect?.top || 0;
      return store.cursorPoint.y + store.panOffset.y - brushRadius.value - dialogOffsetTop;
    });
    const borderRadius = computed(() => {
      return store.brushSettings.type === BrushShape.Rect ? "0%" : "50%";
    });
    const gradientVisible = computed(() => {
      return store.brushPreviewGradientVisible;
    });
    const gradientBackground = computed(() => {
      const size = store.brushSettings.size;
      const hardness = store.brushSettings.hardness;
      const effectiveSize = getEffectiveBrushSize(size, hardness);
      const effectiveHardness = getEffectiveHardness(size, hardness, effectiveSize);
      if (effectiveHardness === 1) {
        return "rgba(255, 0, 0, 0.5)";
      }
      const midStop = effectiveHardness * 100;
      const outerStop = 100;
      const fadeMidStop = midStop + (outerStop - midStop) * 0.5;
      return `radial-gradient(
    circle,
    rgba(255, 0, 0, 0.5) 0%,
    rgba(255, 0, 0, 0.5) ${midStop}%,
    rgba(255, 0, 0, 0.125) ${fadeMidStop}%,
    rgba(255, 0, 0, 0) ${outerStop}%
  )`;
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        id: "maskEditor_brush",
        style: normalizeStyle({
          position: "absolute",
          opacity: brushOpacity.value,
          width: `${brushSize.value}px`,
          height: `${brushSize.value}px`,
          left: `${brushLeft.value}px`,
          top: `${brushTop.value}px`,
          borderRadius: borderRadius.value,
          pointerEvents: "none",
          zIndex: 1e3
        })
      }, [
        createBaseVNode("div", {
          id: "maskEditor_brushPreviewGradient",
          style: normalizeStyle({
            display: gradientVisible.value ? "block" : "none",
            background: gradientBackground.value
          })
        }, null, 4)
      ], 4);
    };
  }
});
const _sfc_main$1p = /* @__PURE__ */ defineComponent({
  __name: "PointerZone",
  props: {
    toolManager: {},
    panZoom: {}
  },
  setup(__props) {
    const store = useMaskEditorStore();
    const pointerZoneRef = ref();
    onMounted(() => {
      if (!pointerZoneRef.value) {
        console.error("[PointerZone] Pointer zone ref not initialized");
        return;
      }
      store.pointerZone = pointerZoneRef.value;
    });
    watch(
      () => store.isPanning,
      (isPanning) => {
        if (!pointerZoneRef.value) return;
        if (isPanning) {
          pointerZoneRef.value.style.cursor = "grabbing";
        } else {
          __props.toolManager.updateCursor();
        }
      }
    );
    const handlePointerDown = /* @__PURE__ */ __name(async (event) => {
      await __props.toolManager.handlePointerDown(event);
    }, "handlePointerDown");
    const handlePointerMove = /* @__PURE__ */ __name(async (event) => {
      await __props.toolManager.handlePointerMove(event);
    }, "handlePointerMove");
    const handlePointerUp2 = /* @__PURE__ */ __name((event) => {
      void __props.toolManager.handlePointerUp(event);
    }, "handlePointerUp");
    const handlePointerLeave = /* @__PURE__ */ __name(() => {
      store.brushVisible = false;
      if (pointerZoneRef.value) {
        pointerZoneRef.value.style.cursor = "";
      }
    }, "handlePointerLeave");
    const handlePointerEnter = /* @__PURE__ */ __name(() => {
      __props.toolManager.updateCursor();
    }, "handlePointerEnter");
    const handleTouchStart = /* @__PURE__ */ __name((event) => {
      __props.panZoom.handleTouchStart(event);
    }, "handleTouchStart");
    const handleTouchMove = /* @__PURE__ */ __name(async (event) => {
      await __props.panZoom.handleTouchMove(event);
    }, "handleTouchMove");
    const handleTouchEnd = /* @__PURE__ */ __name((event) => {
      __props.panZoom.handleTouchEnd(event);
    }, "handleTouchEnd");
    const handleWheel = /* @__PURE__ */ __name(async (event) => {
      await __props.panZoom.zoom(event);
      const newCursorPoint = { x: event.clientX, y: event.clientY };
      __props.panZoom.updateCursorPosition(newCursorPoint);
    }, "handleWheel");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        ref_key: "pointerZoneRef",
        ref: pointerZoneRef,
        class: "w-[calc(100%-4rem-220px)] h-full",
        onPointerdown: handlePointerDown,
        onPointermove: handlePointerMove,
        onPointerup: handlePointerUp2,
        onPointerleave: handlePointerLeave,
        onPointerenter: handlePointerEnter,
        onTouchstart: handleTouchStart,
        onTouchmove: handleTouchMove,
        onTouchend: handleTouchEnd,
        onWheel: handleWheel,
        onContextmenu: _cache[0] || (_cache[0] = withModifiers(() => {
        }, ["prevent"]))
      }, null, 544);
    };
  }
});
const _hoisted_1$1d = { class: "flex flex-col gap-3 pb-3" };
const _hoisted_2$Z = { class: "text-left text-xs font-sans text-[var(--descrip-text)]" };
const _hoisted_3$E = ["min", "max", "step", "value"];
const _sfc_main$1o = /* @__PURE__ */ defineComponent({
  __name: "SliderControl",
  props: {
    label: {},
    min: {},
    max: {},
    step: { default: 1 },
    modelValue: {}
  },
  emits: ["update:modelValue"],
  setup(__props, { emit: __emit }) {
    const emit = __emit;
    const onInput = /* @__PURE__ */ __name((event) => {
      const value = Number(event.target.value);
      emit("update:modelValue", value);
    }, "onInput");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1d, [
        createBaseVNode("span", _hoisted_2$Z, toDisplayString(_ctx.label), 1),
        createBaseVNode("input", {
          type: "range",
          class: "maskEditor_sidePanelBrushRange",
          min: _ctx.min,
          max: _ctx.max,
          step: _ctx.step,
          value: _ctx.modelValue,
          onInput
        }, null, 40, _hoisted_3$E)
      ]);
    };
  }
});
const _hoisted_1$1c = { class: "flex flex-col gap-3 pb-3" };
const _hoisted_2$Y = { class: "text-center text-[15px] font-sans text-[var(--descrip-text)] mt-2.5" };
const _hoisted_3$D = { class: "text-left text-xs font-sans text-[var(--descrip-text)]" };
const _hoisted_4$w = { class: "flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] -mt-2 -mb-1.5" };
const _hoisted_5$s = ["value"];
const _hoisted_6$k = { value: "black" };
const _hoisted_7$h = { value: "white" };
const _hoisted_8$d = { value: "negative" };
const _hoisted_9$b = { class: "text-left text-xs font-sans text-[var(--descrip-text)]" };
const _hoisted_10$9 = ["checked"];
const _hoisted_11$6 = { class: "maskEditor_sidePanelLayerPreviewContainer" };
const _hoisted_12$4 = {
  viewBox: "0 0 20 20",
  style: {}
};
const _hoisted_13$4 = ["disabled"];
const _hoisted_14$2 = { class: "text-left text-xs font-sans text-[var(--descrip-text)]" };
const _hoisted_15$2 = ["checked"];
const _hoisted_16$1 = ["disabled"];
const _hoisted_17$1 = { class: "text-left text-xs font-sans text-[var(--descrip-text)]" };
const _hoisted_18$1 = { class: "flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] bg-secondary-background-hover" };
const _hoisted_19$1 = ["checked"];
const _hoisted_20$1 = { class: "maskEditor_sidePanelLayerPreviewContainer" };
const _hoisted_21$1 = ["src", "alt"];
const _sfc_main$1n = /* @__PURE__ */ defineComponent({
  __name: "ImageLayerSettingsPanel",
  props: {
    toolManager: {}
  },
  setup(__props) {
    const store = useMaskEditorStore();
    const canvasManager = useCanvasManager();
    const maskLayerVisible = ref(true);
    const paintLayerVisible = ref(true);
    const baseImageLayerVisible = ref(true);
    const baseImageSrc = computed(() => {
      return store.image?.src ?? "";
    });
    const showLayerButtons = computed(() => {
      return store.currentTool === Tools.Eraser;
    });
    const onMaskLayerVisibilityChange = /* @__PURE__ */ __name((event) => {
      const checked = event.target.checked;
      maskLayerVisible.value = checked;
      const maskCanvas = store.maskCanvas;
      if (maskCanvas) {
        maskCanvas.style.opacity = checked ? String(store.maskOpacity) : "0";
      }
    }, "onMaskLayerVisibilityChange");
    const onPaintLayerVisibilityChange = /* @__PURE__ */ __name((event) => {
      const checked = event.target.checked;
      paintLayerVisible.value = checked;
      const rgbCanvas = store.rgbCanvas;
      if (rgbCanvas) {
        rgbCanvas.style.opacity = checked ? "1" : "0";
      }
    }, "onPaintLayerVisibilityChange");
    const onBaseImageLayerVisibilityChange = /* @__PURE__ */ __name((event) => {
      const checked = event.target.checked;
      baseImageLayerVisible.value = checked;
      const imgCanvas = store.imgCanvas;
      if (imgCanvas) {
        imgCanvas.style.opacity = checked ? "1" : "0";
      }
    }, "onBaseImageLayerVisibilityChange");
    const onMaskOpacityChange = /* @__PURE__ */ __name((value) => {
      store.setMaskOpacity(value);
      const maskCanvas = store.maskCanvas;
      if (maskCanvas) {
        maskCanvas.style.opacity = String(value);
      }
      maskLayerVisible.value = value !== 0;
    }, "onMaskOpacityChange");
    const onBlendModeChange = /* @__PURE__ */ __name(async (event) => {
      const value = event.target.value;
      let blendMode;
      switch (value) {
        case "white":
          blendMode = MaskBlendMode.White;
          break;
        case "negative":
          blendMode = MaskBlendMode.Negative;
          break;
        default:
          blendMode = MaskBlendMode.Black;
      }
      store.maskBlendMode = blendMode;
      await canvasManager.updateMaskColor();
    }, "onBlendModeChange");
    const setActiveLayer = /* @__PURE__ */ __name((layer) => {
      __props.toolManager?.setActiveLayer(layer);
    }, "setActiveLayer");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1c, [
        createBaseVNode("h3", _hoisted_2$Y, toDisplayString(unref(t)("maskEditor.layers")), 1),
        createVNode(_sfc_main$1o, {
          label: unref(t)("maskEditor.maskOpacity"),
          min: 0,
          max: 1,
          step: 0.01,
          "model-value": unref(store).maskOpacity,
          "onUpdate:modelValue": onMaskOpacityChange
        }, null, 8, ["label", "model-value"]),
        createBaseVNode("span", _hoisted_3$D, toDisplayString(unref(t)("maskEditor.maskBlendingOptions")), 1),
        createBaseVNode("div", _hoisted_4$w, [
          createBaseVNode("select", {
            class: "maskEditor_sidePanelDropdown",
            value: unref(store).maskBlendMode,
            onChange: onBlendModeChange
          }, [
            createBaseVNode("option", _hoisted_6$k, toDisplayString(unref(t)("maskEditor.black")), 1),
            createBaseVNode("option", _hoisted_7$h, toDisplayString(unref(t)("maskEditor.white")), 1),
            createBaseVNode("option", _hoisted_8$d, toDisplayString(unref(t)("maskEditor.negative")), 1)
          ], 40, _hoisted_5$s)
        ]),
        createBaseVNode("span", _hoisted_9$b, toDisplayString(unref(t)("maskEditor.maskLayer")), 1),
        createBaseVNode("div", {
          class: "flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] bg-secondary-background-hover",
          style: normalizeStyle({
            border: unref(store).activeLayer === "mask" ? "2px solid #007acc" : "none"
          })
        }, [
          createBaseVNode("input", {
            type: "checkbox",
            class: "maskEditor_sidePanelLayerCheckbox",
            checked: maskLayerVisible.value,
            onChange: onMaskLayerVisibilityChange
          }, null, 40, _hoisted_10$9),
          createBaseVNode("div", _hoisted_11$6, [
            (openBlock(), createElementBlock("svg", _hoisted_12$4, _cache[2] || (_cache[2] = [
              createBaseVNode("path", {
                class: "cls-1",
                d: "M1.31,5.32v9.36c0,.55.45,1,1,1h15.38c.55,0,1-.45,1-1V5.32c0-.55-.45-1-1-1H2.31c-.55,0-1,.45-1,1ZM11.19,13.44c-2.91.94-5.57-1.72-4.63-4.63.34-1.05,1.19-1.9,2.24-2.24,2.91-.94,5.57,1.72,4.63,4.63-.34,1.05-1.19-1.9-2.24,2.24Z"
              }, null, -1)
            ])))
          ]),
          createBaseVNode("button", {
            style: normalizeStyle([{ "font-size": "12px" }, { opacity: unref(store).activeLayer === "mask" ? "0.5" : "1" }]),
            disabled: unref(store).activeLayer === "mask",
            onClick: _cache[0] || (_cache[0] = ($event) => setActiveLayer("mask"))
          }, toDisplayString(unref(t)("maskEditor.activateLayer")), 13, _hoisted_13$4)
        ], 4),
        createBaseVNode("span", _hoisted_14$2, toDisplayString(unref(t)("maskEditor.paintLayer")), 1),
        createBaseVNode("div", {
          class: "flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] bg-secondary-background-hover",
          style: normalizeStyle({
            border: unref(store).activeLayer === "rgb" ? "2px solid #007acc" : "none"
          })
        }, [
          createBaseVNode("input", {
            type: "checkbox",
            class: "maskEditor_sidePanelLayerCheckbox",
            checked: paintLayerVisible.value,
            onChange: onPaintLayerVisibilityChange
          }, null, 40, _hoisted_15$2),
          _cache[3] || (_cache[3] = createBaseVNode("div", { class: "maskEditor_sidePanelLayerPreviewContainer" }, [
            createBaseVNode("svg", { viewBox: "0 0 20 20" }, [
              createBaseVNode("path", {
                class: "cls-1",
                d: "M 17 6.965 c 0 0.235 -0.095 0.47 -0.275 0.655 l -6.51 6.52 c -0.045 0.035 -0.09 0.075 -0.135 0.11 c -0.035 -0.695 -0.605 -1.24 -1.305 -1.245 c 0.035 -0.06 0.08 -0.12 0.135 -0.17 l 6.52 -6.52 c 0.36 -0.36 0.945 -0.36 1.3 0 c 0.175 0.175 0.275 0.415 0.275 0.65 Z"
              }),
              createBaseVNode("path", {
                class: "cls-1",
                d: "M 9.82 14.515 c 0 2.23 -3.23 1.59 -4.82 0 c 1.65 -0.235 2.375 -1.29 3.53 -1.29 c 0.715 0 1.29 0.58 1.29 1.29 Z"
              })
            ])
          ], -1)),
          createBaseVNode("button", {
            style: normalizeStyle([{ "font-size": "12px" }, {
              opacity: unref(store).activeLayer === "rgb" ? "0.5" : "1",
              display: showLayerButtons.value ? "block" : "none"
            }]),
            disabled: unref(store).activeLayer === "rgb",
            onClick: _cache[1] || (_cache[1] = ($event) => setActiveLayer("rgb"))
          }, toDisplayString(unref(t)("maskEditor.activateLayer")), 13, _hoisted_16$1)
        ], 4),
        createBaseVNode("span", _hoisted_17$1, toDisplayString(unref(t)("maskEditor.baseImageLayer")), 1),
        createBaseVNode("div", _hoisted_18$1, [
          createBaseVNode("input", {
            type: "checkbox",
            class: "maskEditor_sidePanelLayerCheckbox",
            checked: baseImageLayerVisible.value,
            onChange: onBaseImageLayerVisibilityChange
          }, null, 40, _hoisted_19$1),
          createBaseVNode("div", _hoisted_20$1, [
            createBaseVNode("img", {
              class: "maskEditor_sidePanelImageLayerImage",
              src: baseImageSrc.value,
              alt: unref(t)("maskEditor.baseLayerPreview")
            }, null, 8, _hoisted_21$1)
          ])
        ])
      ]);
    };
  }
});
const _hoisted_1$1b = { class: "flex flex-col gap-3 pb-3" };
const _hoisted_2$X = { class: "text-center text-[15px] font-sans text-[var(--descrip-text)] mt-2.5" };
const _hoisted_3$C = { class: "flex flex-col gap-3 pb-3" };
const _hoisted_4$v = { class: "text-left text-xs font-sans text-[var(--descrip-text)]" };
const _hoisted_5$r = { class: "flex flex-row gap-2.5 items-center min-h-6 relative h-[50px] w-full rounded-[10px] bg-secondary-background-hover" };
const _hoisted_6$j = { class: "flex flex-col gap-3 pb-3" };
const _hoisted_7$g = { class: "text-left text-xs font-sans text-[var(--descrip-text)]" };
const _hoisted_8$c = ["value"];
const textButtonClass = "h-7.5 w-32 rounded-[10px] border border-[var(--p-form-field-border-color)] text-[var(--input-text)] font-sans pointer-events-auto transition-colors duration-100 bg-[var(--comfy-menu-bg)] hover:bg-secondary-background-hover";
const _sfc_main$1m = /* @__PURE__ */ defineComponent({
  __name: "BrushSettingsPanel",
  setup(__props) {
    const store = useMaskEditorStore();
    const setBrushShape = /* @__PURE__ */ __name((shape2) => {
      store.brushSettings.type = shape2;
    }, "setBrushShape");
    const onColorChange = /* @__PURE__ */ __name((event) => {
      store.rgbColor = event.target.value;
    }, "onColorChange");
    const onThicknessChange = /* @__PURE__ */ __name((value) => {
      store.setBrushSize(value);
    }, "onThicknessChange");
    const onOpacityChange = /* @__PURE__ */ __name((value) => {
      store.setBrushOpacity(value);
    }, "onOpacityChange");
    const onHardnessChange = /* @__PURE__ */ __name((value) => {
      store.setBrushHardness(value);
    }, "onHardnessChange");
    const onStepSizeChange = /* @__PURE__ */ __name((value) => {
      store.setBrushStepSize(value);
    }, "onStepSizeChange");
    const resetToDefault = /* @__PURE__ */ __name(() => {
      store.resetBrushToDefault();
    }, "resetToDefault");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1b, [
        createBaseVNode("h3", _hoisted_2$X, toDisplayString(unref(t)("maskEditor.brushSettings")), 1),
        createBaseVNode("button", {
          class: normalizeClass(textButtonClass),
          onClick: resetToDefault
        }, toDisplayString(unref(t)("maskEditor.resetToDefault")), 1),
        createBaseVNode("div", _hoisted_3$C, [
          createBaseVNode("span", _hoisted_4$v, toDisplayString(unref(t)("maskEditor.brushShape")), 1),
          createBaseVNode("div", _hoisted_5$r, [
            createBaseVNode("div", {
              class: normalizeClass(["maskEditor_sidePanelBrushShapeCircle bg-transparent hover:bg-comfy-menu-bg", { active: unref(store).brushSettings.type === unref(BrushShape).Arc }]),
              style: normalizeStyle({
                background: unref(store).brushSettings.type === unref(BrushShape).Arc ? "var(--p-button-text-primary-color)" : ""
              }),
              onClick: _cache[0] || (_cache[0] = ($event) => setBrushShape(unref(BrushShape).Arc))
            }, null, 6),
            createBaseVNode("div", {
              class: normalizeClass(["maskEditor_sidePanelBrushShapeSquare bg-transparent hover:bg-comfy-menu-bg", { active: unref(store).brushSettings.type === unref(BrushShape).Rect }]),
              style: normalizeStyle({
                background: unref(store).brushSettings.type === unref(BrushShape).Rect ? "var(--p-button-text-primary-color)" : ""
              }),
              onClick: _cache[1] || (_cache[1] = ($event) => setBrushShape(unref(BrushShape).Rect))
            }, null, 6)
          ])
        ]),
        createBaseVNode("div", _hoisted_6$j, [
          createBaseVNode("span", _hoisted_7$g, toDisplayString(unref(t)("maskEditor.colorSelector")), 1),
          createBaseVNode("input", {
            type: "color",
            value: unref(store).rgbColor,
            onInput: onColorChange
          }, null, 40, _hoisted_8$c)
        ]),
        createVNode(_sfc_main$1o, {
          label: unref(t)("maskEditor.thickness"),
          min: 1,
          max: 500,
          step: 1,
          "model-value": unref(store).brushSettings.size,
          "onUpdate:modelValue": onThicknessChange
        }, null, 8, ["label", "model-value"]),
        createVNode(_sfc_main$1o, {
          label: unref(t)("maskEditor.opacity"),
          min: 0,
          max: 1,
          step: 0.01,
          "model-value": unref(store).brushSettings.opacity,
          "onUpdate:modelValue": onOpacityChange
        }, null, 8, ["label", "model-value"]),
        createVNode(_sfc_main$1o, {
          label: unref(t)("maskEditor.hardness"),
          min: 0,
          max: 1,
          step: 0.01,
          "model-value": unref(store).brushSettings.hardness,
          "onUpdate:modelValue": onHardnessChange
        }, null, 8, ["label", "model-value"]),
        createVNode(_sfc_main$1o, {
          label: _ctx.$t("maskEditor.stepSize"),
          min: 1,
          max: 100,
          step: 1,
          "model-value": unref(store).brushSettings.stepSize,
          "onUpdate:modelValue": onStepSizeChange
        }, null, 8, ["label", "model-value"])
      ]);
    };
  }
});
const _hoisted_1$1a = { class: "flex flex-row gap-2.5 items-center min-h-6 relative" };
const _hoisted_2$W = { class: "text-left text-xs font-sans text-[var(--descrip-text)]" };
const _hoisted_3$B = ["value"];
const _hoisted_4$u = ["value"];
const _sfc_main$1l = /* @__PURE__ */ defineComponent({
  __name: "DropdownControl",
  props: {
    label: {},
    options: {},
    modelValue: {}
  },
  emits: ["update:modelValue"],
  setup(__props, { emit: __emit }) {
    const props = __props;
    const emit = __emit;
    const normalizedOptions = computed(() => {
      return props.options.map((option) => {
        if (typeof option === "string") {
          return { label: option, value: option };
        }
        return option;
      });
    });
    const onChange2 = /* @__PURE__ */ __name((event) => {
      const value = event.target.value;
      emit("update:modelValue", value);
    }, "onChange");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$1a, [
        createBaseVNode("span", _hoisted_2$W, toDisplayString(_ctx.label), 1),
        createBaseVNode("select", {
          class: "absolute right-0 h-6 px-1.5 rounded-md border border-border-default transition-colors duration-100 bg-secondary-background focus:outline focus:outline-node-component-border",
          value: _ctx.modelValue,
          onChange: onChange2
        }, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(normalizedOptions.value, (option) => {
            return openBlock(), createElementBlock("option", {
              key: option.value,
              value: option.value
            }, toDisplayString(option.label), 9, _hoisted_4$u);
          }), 128))
        ], 40, _hoisted_3$B)
      ]);
    };
  }
});
const _hoisted_1$19 = { class: "flex flex-row gap-2.5 items-center min-h-6 relative" };
const _hoisted_2$V = { class: "text-left text-xs font-sans text-[var(--descrip-text)]" };
const _hoisted_3$A = { class: "maskEditor_sidePanelToggleContainer" };
const _hoisted_4$t = ["checked"];
const _sfc_main$1k = /* @__PURE__ */ defineComponent({
  __name: "ToggleControl",
  props: {
    label: {},
    modelValue: { type: Boolean }
  },
  emits: ["update:modelValue"],
  setup(__props, { emit: __emit }) {
    const emit = __emit;
    const onChange2 = /* @__PURE__ */ __name((event) => {
      const checked = event.target.checked;
      emit("update:modelValue", checked);
    }, "onChange");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$19, [
        createBaseVNode("span", _hoisted_2$V, toDisplayString(_ctx.label), 1),
        createBaseVNode("label", _hoisted_3$A, [
          createBaseVNode("input", {
            type: "checkbox",
            class: "maskEditor_sidePanelToggleCheckbox",
            checked: _ctx.modelValue,
            onChange: onChange2
          }, null, 40, _hoisted_4$t),
          _cache[0] || (_cache[0] = createBaseVNode("div", { class: "maskEditor_sidePanelToggleSwitch" }, null, -1))
        ])
      ]);
    };
  }
});
const _hoisted_1$18 = { class: "flex flex-col gap-3 pb-3" };
const _hoisted_2$U = { class: "text-center text-[15px] font-sans text-[var(--descrip-text)] mt-2.5" };
const _sfc_main$1j = /* @__PURE__ */ defineComponent({
  __name: "ColorSelectSettingsPanel",
  setup(__props) {
    const store = useMaskEditorStore();
    const methodOptions = Object.values(ColorComparisonMethod);
    const onToleranceChange = /* @__PURE__ */ __name((value) => {
      store.setColorSelectTolerance(value);
    }, "onToleranceChange");
    const onSelectionOpacityChange = /* @__PURE__ */ __name((value) => {
      store.setSelectionOpacity(value);
    }, "onSelectionOpacityChange");
    const onLivePreviewChange = /* @__PURE__ */ __name((value) => {
      store.colorSelectLivePreview = value;
    }, "onLivePreviewChange");
    const onWholeImageChange = /* @__PURE__ */ __name((value) => {
      store.applyWholeImage = value;
    }, "onWholeImageChange");
    const onMethodChange = /* @__PURE__ */ __name((value) => {
      store.colorComparisonMethod = value;
    }, "onMethodChange");
    const onMaskBoundaryChange = /* @__PURE__ */ __name((value) => {
      store.maskBoundary = value;
    }, "onMaskBoundaryChange");
    const onMaskToleranceChange = /* @__PURE__ */ __name((value) => {
      store.setMaskTolerance(value);
    }, "onMaskToleranceChange");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$18, [
        createBaseVNode("h3", _hoisted_2$U, toDisplayString(unref(t)("maskEditor.colorSelectSettings")), 1),
        createVNode(_sfc_main$1o, {
          label: unref(t)("maskEditor.tolerance"),
          min: 0,
          max: 255,
          step: 1,
          "model-value": unref(store).colorSelectTolerance,
          "onUpdate:modelValue": onToleranceChange
        }, null, 8, ["label", "model-value"]),
        createVNode(_sfc_main$1o, {
          label: unref(t)("maskEditor.selectionOpacity"),
          min: 0,
          max: 100,
          step: 1,
          "model-value": unref(store).selectionOpacity,
          "onUpdate:modelValue": onSelectionOpacityChange
        }, null, 8, ["label", "model-value"]),
        createVNode(_sfc_main$1k, {
          label: unref(t)("maskEditor.livePreview"),
          "model-value": unref(store).colorSelectLivePreview,
          "onUpdate:modelValue": onLivePreviewChange
        }, null, 8, ["label", "model-value"]),
        createVNode(_sfc_main$1k, {
          label: unref(t)("maskEditor.applyToWholeImage"),
          "model-value": unref(store).applyWholeImage,
          "onUpdate:modelValue": onWholeImageChange
        }, null, 8, ["label", "model-value"]),
        createVNode(_sfc_main$1l, {
          label: unref(t)("maskEditor.method"),
          options: unref(methodOptions),
          "model-value": unref(store).colorComparisonMethod,
          "onUpdate:modelValue": onMethodChange
        }, null, 8, ["label", "options", "model-value"]),
        createVNode(_sfc_main$1k, {
          label: unref(t)("maskEditor.stopAtMask"),
          "model-value": unref(store).maskBoundary,
          "onUpdate:modelValue": onMaskBoundaryChange
        }, null, 8, ["label", "model-value"]),
        createVNode(_sfc_main$1o, {
          label: unref(t)("maskEditor.maskTolerance"),
          min: 0,
          max: 255,
          step: 1,
          "model-value": unref(store).maskTolerance,
          "onUpdate:modelValue": onMaskToleranceChange
        }, null, 8, ["label", "model-value"])
      ]);
    };
  }
});
const _hoisted_1$17 = { class: "flex flex-col gap-3 pb-3" };
const _hoisted_2$T = { class: "text-center text-[15px] font-sans text-[var(--descrip-text)] mt-2.5" };
const _sfc_main$1i = /* @__PURE__ */ defineComponent({
  __name: "PaintBucketSettingsPanel",
  setup(__props) {
    const store = useMaskEditorStore();
    const onToleranceChange = /* @__PURE__ */ __name((value) => {
      store.setPaintBucketTolerance(value);
    }, "onToleranceChange");
    const onFillOpacityChange = /* @__PURE__ */ __name((value) => {
      store.setFillOpacity(value);
    }, "onFillOpacityChange");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$17, [
        createBaseVNode("h3", _hoisted_2$T, toDisplayString(unref(t)("maskEditor.paintBucketSettings")), 1),
        createVNode(_sfc_main$1o, {
          label: unref(t)("maskEditor.tolerance"),
          min: 0,
          max: 255,
          step: 1,
          "model-value": unref(store).paintBucketTolerance,
          "onUpdate:modelValue": onToleranceChange
        }, null, 8, ["label", "model-value"]),
        createVNode(_sfc_main$1o, {
          label: unref(t)("maskEditor.fillOpacity"),
          min: 0,
          max: 100,
          step: 1,
          "model-value": unref(store).fillOpacity,
          "onUpdate:modelValue": onFillOpacityChange
        }, null, 8, ["label", "model-value"])
      ]);
    };
  }
});
const _hoisted_1$16 = { class: "maskEditor_sidePanel" };
const _hoisted_2$S = { class: "maskEditor_sidePanelContainer" };
const _sfc_main$1h = /* @__PURE__ */ defineComponent({
  __name: "SettingsPanelContainer",
  setup(__props) {
    const currentPanelComponent = computed(() => {
      const tool = useMaskEditorStore().currentTool;
      if (tool === Tools.MaskBucket) {
        return _sfc_main$1i;
      } else if (tool === Tools.MaskColorFill) {
        return _sfc_main$1j;
      } else {
        return _sfc_main$1m;
      }
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$16, [
        createBaseVNode("div", _hoisted_2$S, [
          (openBlock(), createBlock(resolveDynamicComponent(currentPanelComponent.value)))
        ])
      ]);
    };
  }
});
const _hoisted_1$15 = { class: "flex flex-col gap-3 pb-3 h-full !items-stretch bg-[var(--comfy-menu-bg)] overflow-y-auto w-55 px-2.5" };
const _hoisted_2$R = { class: "w-full min-h-full" };
const _sfc_main$1g = /* @__PURE__ */ defineComponent({
  __name: "SidePanel",
  props: {
    toolManager: {}
  },
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$15, [
        createBaseVNode("div", _hoisted_2$R, [
          createVNode(_sfc_main$1h),
          _cache[0] || (_cache[0] = createBaseVNode("div", { class: "w-full h-0.5 bg-[var(--border-color)] mt-6 mb-1.5" }, null, -1)),
          createVNode(_sfc_main$1n, { "tool-manager": _ctx.toolManager }, null, 8, ["tool-manager"])
        ])
      ]);
    };
  }
});
const iconsHtml = {
  [Tools.MaskPen]: `
    <svg viewBox="0 0 44 44">
      <path class="cls-1" d="M10.97,15.98v14.04c0,.825.675,1.5,1.5,1.5h23.07c.825,0,1.5-.675,1.5-1.5V15.98c0-.825-.675-1.5-1.5-1.5H12.47c-.825,0-1.5.675-1.5,1.5ZM25.79,28.16c-4.365,1.41-8.355-2.58-6.945-6.945.51-1.575,1.785-2.85,3.36-3.36,4.365-1.41,8.355,2.58,6.945,6.945-.51,1.575-1.785,2.85-3.36,3.36Z"/>
    </svg>
  `,
  [Tools.Eraser]: `
    <svg viewBox="0 0 44 44">
      <g>
        <rect class="cls-2" x="16.68" y="10" width="10.63" height="24" rx="1.16" ry="1.16" transform="translate(22 -9.11) rotate(45)"/>
        <path class="cls-1" d="M17.27,34.27c-.42,0-.85-.16-1.17-.48l-5.88-5.88c-.31-.31-.48-.73-.48-1.17s.17-.86.48-1.17l15.34-15.34c.62-.62,1.72-.62,2.34,0l5.88,5.88c.65.65.65,1.7,0,2.34l-15.34,15.34c-.32.32-.75.48-1.17.48ZM26.73,10.73c-.18,0-.34.07-.46.19l-15.34,15.34c-.12.12-.19.29-.19.46s.07.34.19.46l5.88,5.88c.26.26.67.26.93,0l15.34-15.34c.26-.26.26-.67,0-.93l-5.88-5.88c-.12-.12-.29-.19-.46-.19Z"/>
      </g>
      <path class="cls-3" d="M20.33,11.03h8.32c.64,0,1.16.52,1.16,1.16v15.79h-10.63v-15.79c0-.64.52-1.16,1.16-1.16Z" transform="translate(20.97 -11.61) rotate(45)"/>
    </svg>
  `,
  [Tools.MaskBucket]: `
    <svg viewBox="0 0 44 44">
      <path class="cls-1" d="M33.4,21.76l-11.42,11.41-.04.05c-.61.61-1.6.61-2.21,0l-8.91-8.91c-.61-.61-.61-1.6,0-2.21l.04-.05.3-.29h22.24Z"/>
      <path class="cls-1" d="M20.83,34.17c-.55,0-1.07-.21-1.46-.6l-8.91-8.91c-.8-.8-.8-2.11,0-2.92l11.31-11.31c.8-.8,2.11-.8,2.92,0l8.91,8.91c.39.39.6.91.6,1.46s-.21,1.07-.6,1.46l-11.31,11.31c-.39.39-.91.6-1.46.6ZM23.24,10.83c-.27,0-.54.1-.75.31l-11.31,11.31c-.41.41-.41,1.09,0,1.5l8.91,8.91c.4.4,1.1.4,1.5,0l11.31-11.31c.2-.2.31-.47.31-.75s-.11-.55-.31-.75l-8.91-8.91c-.21-.21-.48-.31-.75-.31Z"/>
      <path class="cls-1" d="M34.28,26.85c0,.84-.68,1.52-1.52,1.52s-1.52-.68-1.52-1.52,1.52-2.86,1.52-2.86c0,0,1.52,2.02,1.52,2.86Z"/>
    </svg>
  `,
  [Tools.MaskColorFill]: `
    <svg viewBox="0 0 44 44">
      <path class="cls-1" d="M30.29,13.72c-1.09-1.1-2.85-1.09-3.94,0l-2.88,2.88-.75-.75c-.2-.19-.51-.19-.71,0-.19.2-.19.51,0,.71l1.4,1.4-9.59,9.59c-.35.36-.54.82-.54,1.32,0,.14,0,.28.05.41-.05.04-.1.08-.15.13-.39.39-.39,1.01,0,1.4.38.39,1.01.39,1.4,0,.04-.04.08-.09.11-.13.14.04.3.06.45.06.5,0,.97-.19,1.32-.55l9.59-9.59,1.38,1.38c.1.09.22.14.35.14s.26-.05.35-.14c.2-.2.2-.52,0-.71l-.71-.72,2.88-2.89c1.08-1.08,1.08-2.85-.01-3.94ZM19.43,25.82h-2.46l7.15-7.15,1.23,1.23-5.92,5.92Z"/>
    </svg>
  `,
  [Tools.PaintPen]: `
    <svg viewBox="0 0 44 44">
      <path class="cls-1" d="M34,13.93c0,.47-.19.94-.55,1.31l-13.02,13.04c-.09.07-.18.15-.27.22-.07-1.39-1.21-2.48-2.61-2.49.07-.12.16-.24.27-.34l13.04-13.04c.72-.72,1.89-.72,2.6,0,.35.35.55.83.55,1.3Z"/>
      <path class="cls-1" d="M19.64,29.03c0,4.46-6.46,3.18-9.64,0,3.3-.47,4.75-2.58,7.06-2.58,1.43,0,2.58,1.16,2.58,2.58Z"/>
    </svg>
  `
};
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.constants = window.comfyAPI.constants || {};
window.comfyAPI.constants.iconsHtml = iconsHtml;
const _hoisted_1$14 = { class: "h-full z-8888 flex flex-col justify-between bg-comfy-menu-bg" };
const _hoisted_2$Q = { class: "flex flex-col" };
const _hoisted_3$z = ["onClick"];
const _hoisted_4$s = ["innerHTML"];
const _hoisted_5$q = ["title"];
const _hoisted_6$i = { class: "text-sm text-text-secondary" };
const _hoisted_7$f = { class: "text-xs text-text-secondary" };
const _sfc_main$1f = /* @__PURE__ */ defineComponent({
  __name: "ToolPanel",
  props: {
    toolManager: {}
  },
  setup(__props) {
    const store = useMaskEditorStore();
    const onToolSelect = /* @__PURE__ */ __name((tool) => {
      __props.toolManager.switchTool(tool);
    }, "onToolSelect");
    const currentTool = computed(() => store.currentTool);
    const zoomText = computed(() => `${Math.round(store.displayZoomRatio * 100)}%`);
    const dimensionsText = computed(() => {
      const img = store.image;
      return img ? `${img.width}x${img.height}` : " ";
    });
    const onResetZoom = /* @__PURE__ */ __name(() => {
      store.resetZoom();
    }, "onResetZoom");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$14, [
        createBaseVNode("div", _hoisted_2$Q, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(unref(allTools), (tool) => {
            return openBlock(), createElementBlock("div", {
              key: tool,
              class: normalizeClass([
                "maskEditor_toolPanelContainer hover:bg-secondary-background-hover",
                { maskEditor_toolPanelContainerSelected: currentTool.value === tool }
              ]),
              onClick: /* @__PURE__ */ __name(($event) => onToolSelect(tool), "onClick")
            }, [
              createBaseVNode("div", {
                class: "flex items-center justify-center",
                innerHTML: unref(iconsHtml)[tool]
              }, null, 8, _hoisted_4$s),
              _cache[0] || (_cache[0] = createBaseVNode("div", { class: "maskEditor_toolPanelIndicator" }, null, -1))
            ], 10, _hoisted_3$z);
          }), 128))
        ]),
        createBaseVNode("div", {
          class: "flex flex-col items-center cursor-pointer rounded-md mb-2 transition-colors duration-200 hover:bg-secondary-background-hover",
          title: unref(t)("maskEditor.clickToResetZoom"),
          onClick: onResetZoom
        }, [
          createBaseVNode("span", _hoisted_6$i, toDisplayString(zoomText.value), 1),
          createBaseVNode("span", _hoisted_7$f, toDisplayString(dimensionsText.value), 1)
        ], 8, _hoisted_5$q)
      ]);
    };
  }
});
const _hoisted_1$13 = { class: "maskEditor-ui-container flex min-h-0 flex-1 flex-col" };
const _hoisted_2$P = { class: "flex min-h-0 flex-1 overflow-hidden" };
const _sfc_main$1e = /* @__PURE__ */ defineComponent({
  __name: "MaskEditorContent",
  props: {
    node: {}
  },
  setup(__props) {
    const store = useMaskEditorStore();
    const dataStore = useMaskEditorDataStore();
    const dialogStore = useDialogStore();
    const loader = useMaskEditorLoader();
    const containerRef = ref();
    const canvasContainerRef = ref();
    const imgCanvasRef = ref();
    const maskCanvasRef = ref();
    const rgbCanvasRef = ref();
    const gpuCanvasRef = ref();
    const canvasBackgroundRef = ref();
    const toolPanelRef = ref();
    const sidePanelRef = ref();
    const initialized = ref(false);
    const keyboard = useKeyboard();
    const panZoom = usePanAndZoom();
    const toolManager = useToolManager(keyboard, panZoom);
    let resizeObserver = null;
    const handleDragStart = /* @__PURE__ */ __name((event) => {
      if (event.ctrlKey) {
        event.preventDefault();
      }
    }, "handleDragStart");
    const initUI = /* @__PURE__ */ __name(async () => {
      if (!containerRef.value) {
        console.error(
          "[MaskEditorContent] Cannot initialize - missing required refs"
        );
        return;
      }
      if (!imgCanvasRef.value || !maskCanvasRef.value || !rgbCanvasRef.value || !canvasContainerRef.value || !canvasBackgroundRef.value) {
        console.error("[MaskEditorContent] Cannot initialize - missing canvas refs");
        return;
      }
      store.maskCanvas = maskCanvasRef.value;
      store.rgbCanvas = rgbCanvasRef.value;
      store.imgCanvas = imgCanvasRef.value;
      store.canvasContainer = canvasContainerRef.value;
      store.canvasBackground = canvasBackgroundRef.value;
      try {
        await loader.loadFromNode(__props.node);
        const imageLoader = useImageLoader();
        const image = await imageLoader.loadImages();
        await panZoom.initializeCanvasPanZoom(
          image,
          containerRef.value,
          toolPanelRef.value?.$el,
          sidePanelRef.value?.$el
        );
        store.canvasHistory.saveInitialState();
        if (toolManager.brushDrawing) {
          await toolManager.brushDrawing.initGPUResources();
          if (gpuCanvasRef.value && toolManager.brushDrawing.initPreviewCanvas) {
            gpuCanvasRef.value.width = maskCanvasRef.value.width;
            gpuCanvasRef.value.height = maskCanvasRef.value.height;
            toolManager.brushDrawing.initPreviewCanvas(gpuCanvasRef.value);
          }
        }
        initialized.value = true;
      } catch (error) {
        console.error("[MaskEditorContent] Initialization failed:", error);
        dialogStore.closeDialog();
      }
    }, "initUI");
    onMounted(() => {
      keyboard.addListeners();
      if (containerRef.value) {
        resizeObserver = new ResizeObserver(async () => {
          if (panZoom) {
            await panZoom.invalidatePanZoom();
          }
        });
        resizeObserver.observe(containerRef.value);
      }
      void initUI();
    });
    onBeforeUnmount(() => {
      toolManager.brushDrawing.saveBrushSettings();
      keyboard?.removeListeners();
      if (resizeObserver) {
        resizeObserver.disconnect();
        resizeObserver = null;
      }
      store.canvasHistory.clearStates();
      store.resetState();
      dataStore.reset();
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        ref_key: "containerRef",
        ref: containerRef,
        class: "maskEditor-dialog-root flex h-full w-full flex-col",
        onContextmenu: _cache[4] || (_cache[4] = withModifiers(() => {
        }, ["prevent"])),
        onDragstart: handleDragStart
      }, [
        createBaseVNode("div", {
          id: "maskEditorCanvasContainer",
          ref_key: "canvasContainerRef",
          ref: canvasContainerRef,
          onContextmenu: _cache[3] || (_cache[3] = withModifiers(() => {
          }, ["prevent"]))
        }, [
          createBaseVNode("canvas", {
            ref_key: "imgCanvasRef",
            ref: imgCanvasRef,
            class: "absolute top-0 left-0 w-full h-full z-0",
            onContextmenu: _cache[0] || (_cache[0] = withModifiers(() => {
            }, ["prevent"]))
          }, null, 544),
          createBaseVNode("canvas", {
            ref_key: "rgbCanvasRef",
            ref: rgbCanvasRef,
            class: "absolute top-0 left-0 w-full h-full z-10",
            onContextmenu: _cache[1] || (_cache[1] = withModifiers(() => {
            }, ["prevent"]))
          }, null, 544),
          createBaseVNode("canvas", {
            ref_key: "maskCanvasRef",
            ref: maskCanvasRef,
            class: "absolute top-0 left-0 w-full h-full z-30",
            onContextmenu: _cache[2] || (_cache[2] = withModifiers(() => {
            }, ["prevent"]))
          }, null, 544),
          createBaseVNode("canvas", {
            ref_key: "gpuCanvasRef",
            ref: gpuCanvasRef,
            class: normalizeClass(["absolute top-0 left-0 w-full h-full pointer-events-none", {
              "z-20": unref(store).activeLayer === "rgb",
              "z-40": unref(store).activeLayer === "mask"
            }])
          }, null, 2),
          createBaseVNode("div", {
            ref_key: "canvasBackgroundRef",
            ref: canvasBackgroundRef,
            class: "bg-white w-full h-full"
          }, null, 512)
        ], 544),
        createBaseVNode("div", _hoisted_1$13, [
          createBaseVNode("div", _hoisted_2$P, [
            initialized.value ? (openBlock(), createBlock(_sfc_main$1f, {
              key: 0,
              ref_key: "toolPanelRef",
              ref: toolPanelRef,
              "tool-manager": unref(toolManager)
            }, null, 8, ["tool-manager"])) : createCommentVNode("", true),
            initialized.value ? (openBlock(), createBlock(_sfc_main$1p, {
              key: 1,
              "tool-manager": unref(toolManager),
              "pan-zoom": unref(panZoom)
            }, null, 8, ["tool-manager", "pan-zoom"])) : createCommentVNode("", true),
            initialized.value ? (openBlock(), createBlock(_sfc_main$1g, {
              key: 2,
              ref_key: "sidePanelRef",
              ref: sidePanelRef,
              "tool-manager": unref(toolManager)
            }, null, 8, ["tool-manager"])) : createCommentVNode("", true)
          ])
        ]),
        initialized.value ? (openBlock(), createBlock(_sfc_main$1q, {
          key: 0,
          "container-ref": containerRef.value
        }, null, 8, ["container-ref"])) : createCommentVNode("", true)
      ], 544);
    };
  }
});
const MaskEditorContent = /* @__PURE__ */ _export_sfc(_sfc_main$1e, [["__scopeId", "data-v-6c02c403"]]);
function useMaskEditor() {
  const openMaskEditor = /* @__PURE__ */ __name((node2) => {
    if (!node2) {
      console.error("[MaskEditor] No node provided");
      return;
    }
    if (!node2.imgs?.length && node2.previewMediaType !== "image") {
      console.error("[MaskEditor] Node has no images");
      return;
    }
    useDialogStore().showDialog({
      key: "global-mask-editor",
      headerComponent: _sfc_main$1r,
      component: MaskEditorContent,
      props: {
        node: node2
      },
      dialogComponentProps: {
        style: "width: 90vw; height: 90vh;",
        modal: true,
        maximizable: true,
        closable: true,
        pt: {
          root: {
            class: "mask-editor-dialog flex flex-col"
          },
          content: {
            class: "flex flex-col min-h-0 flex-1 !p-0"
          },
          header: {
            class: "!p-2"
          }
        }
      }
    });
  }, "openMaskEditor");
  return {
    openMaskEditor
  };
}
__name(useMaskEditor, "useMaskEditor");
const CONFIG = /* @__PURE__ */ Symbol();
const GET_CONFIG = /* @__PURE__ */ Symbol();
const useLitegraphService = /* @__PURE__ */ __name(() => {
  const extensionService = useExtensionService();
  const toastStore = useToastStore();
  const widgetStore = useWidgetStore();
  const canvasStore = useCanvasStore();
  const { toggleSelectedNodesMode } = useSelectedLiteGraphItems();
  function nodeKey(node2) {
    return `nodeDefs.${normalizeI18nKey(node2.constructor.nodeData.name)}`;
  }
  __name(nodeKey, "nodeKey");
  function addInputSocket(node2, inputSpec) {
    const inputName = inputSpec.name;
    const nameKey = `${nodeKey(node2)}.inputs.${normalizeI18nKey(inputName)}.name`;
    const widgetConstructor = widgetStore.widgets.get(
      inputSpec.widgetType ?? inputSpec.type
    );
    if (widgetConstructor && !inputSpec.forceInput || applyDynamicInputs(node2, inputSpec))
      return;
    const input = node2.addInput(inputName, inputSpec.type, {
      shape: inputSpec.isOptional ? RenderShape.HollowCircle : void 0,
      localized_name: st(nameKey, inputName)
    });
    input.label ??= inputSpec.display_name;
  }
  __name(addInputSocket, "addInputSocket");
  function setupStrokeStyles(node2) {
    node2.strokeStyles["running"] = function() {
      const nodeId = String(this.id);
      const nodeLocatorId = useWorkflowStore().nodeIdToNodeLocatorId(nodeId);
      const state2 = useExecutionStore().nodeLocationProgressStates[nodeLocatorId]?.state;
      if (state2 === "running") {
        return { color: "#0f0" };
      }
    };
    node2.strokeStyles["dragOver"] = function() {
      if (app$1.dragOverNode?.id == this.id) {
        return { color: "dodgerblue" };
      }
    };
    node2.strokeStyles["executionError"] = function() {
      if (app$1.lastExecutionError?.node_id == this.id) {
        return { color: "#f0f", lineWidth: 2 };
      }
    };
  }
  __name(setupStrokeStyles, "setupStrokeStyles");
  function addNodeInput(node2, inputSpec) {
    addInputSocket(node2, inputSpec);
    addInputWidget(node2, inputSpec);
  }
  __name(addNodeInput, "addNodeInput");
  function addInputWidget(node2, inputSpec) {
    const widgetInputSpec = { ...inputSpec };
    if (inputSpec.widgetType) {
      widgetInputSpec.type = inputSpec.widgetType;
    }
    const inputName = inputSpec.name;
    const nameKey = `${nodeKey(node2)}.inputs.${normalizeI18nKey(inputName)}.name`;
    const widgetConstructor = widgetStore.widgets.get(widgetInputSpec.type);
    if (!widgetConstructor || inputSpec.forceInput) return;
    const {
      widget,
      minWidth = 1,
      minHeight = 1
    } = widgetConstructor(
      node2,
      inputName,
      transformInputSpecV2ToV1(widgetInputSpec),
      app$1
    ) ?? {};
    if (widget) {
      widget.label = st(
        nameKey,
        widget.label ?? widgetInputSpec.display_name ?? inputName
      );
      widget.options ??= {};
      Object.assign(widget.options, {
        advanced: inputSpec.advanced,
        hidden: inputSpec.hidden
      });
    }
    if (!widget?.options?.socketless) {
      const inputSpecV1 = transformInputSpecV2ToV1(widgetInputSpec);
      node2.addInput(inputName, inputSpec.type, {
        shape: inputSpec.isOptional ? RenderShape.HollowCircle : void 0,
        localized_name: st(nameKey, inputName),
        widget: { name: inputName, [GET_CONFIG]: () => inputSpecV1 }
      });
    }
    const castedNode = node2;
    castedNode._initialMinSize.width = Math.max(
      castedNode._initialMinSize.width,
      minWidth
    );
    castedNode._initialMinSize.height = Math.max(
      castedNode._initialMinSize.height,
      minHeight
    );
  }
  __name(addInputWidget, "addInputWidget");
  function addInputs(node2, inputs) {
    const nodeDefImpl = node2.constructor.nodeData;
    const orderedInputSpecs = getOrderedInputSpecs(nodeDefImpl, inputs);
    for (const inputSpec of orderedInputSpecs) addInputSocket(node2, inputSpec);
    for (const inputSpec of orderedInputSpecs) addInputWidget(node2, inputSpec);
  }
  __name(addInputs, "addInputs");
  function addOutputs(node2, outputs) {
    for (const output of outputs) {
      const { name: name2, is_list } = output;
      const type = output.type === "COMFY_MATCHTYPE_V3" ? "*" : output.type;
      const shapeOptions = is_list ? { shape: LiteGraph.GRID_SHAPE } : {};
      const nameKey = `${nodeKey(node2)}.outputs.${output.index}.name`;
      const typeKey = `dataTypes.${normalizeI18nKey(type)}`;
      const outputOptions = {
        ...shapeOptions,
        // If the output name is different from the output type, use the output name.
        // e.g.
        // - type ("INT"); name ("Positive") => translate name
        // - type ("FLOAT"); name ("FLOAT") => translate type
        localized_name: type !== name2 ? st(nameKey, name2) : st(typeKey, name2)
      };
      node2.addOutput(name2, type, outputOptions);
    }
  }
  __name(addOutputs, "addOutputs");
  function setInitialSize(node2) {
    const s = node2.computeSize();
    const pad = node2.widgets?.length && !useSettingStore().get("LiteGraph.Node.DefaultPadding");
    const castedNode = node2;
    s[0] = Math.max(castedNode._initialMinSize.width, s[0] + (pad ? 60 : 0));
    s[1] = Math.max(castedNode._initialMinSize.height, s[1]);
    node2.setSize(s);
  }
  __name(setInitialSize, "setInitialSize");
  function registerSubgraphNodeDef(nodeDefV1, subgraph, instanceData) {
    const node2 = class ComfyNode extends SubgraphNode {
      static {
        __name(this, "ComfyNode");
      }
      static comfyClass;
      static title;
      static category;
      static nodeData;
      _initialMinSize = { width: 1, height: 1 };
      constructor() {
        super(app$1.rootGraph, subgraph, instanceData);
        subgraph.events.addEventListener("widget-promoted", (event) => {
          const { widget } = event.detail;
          if (!isDOMWidget(widget) && !isComponentWidget(widget)) return;
          const domWidgetStore = useDomWidgetStore();
          if (!domWidgetStore.widgetStates.has(widget.id)) {
            domWidgetStore.registerWidget(widget);
            const widgetState = domWidgetStore.widgetStates.get(widget.id);
            if (widgetState) {
              const currentGraph = canvasStore.getCanvas().graph;
              widgetState.visible = currentGraph?.nodes.includes(widget.node) ?? false;
            }
          }
        });
        subgraph.events.addEventListener("widget-demoted", (event) => {
          const { widget } = event.detail;
          if (!isDOMWidget(widget) && !isComponentWidget(widget)) return;
          const domWidgetStore = useDomWidgetStore();
          if (domWidgetStore.widgetStates.has(widget.id)) {
            domWidgetStore.unregisterWidget(widget.id);
          }
        });
        setupStrokeStyles(this);
        addInputs(this, ComfyNode.nodeData.inputs);
        addOutputs(this, ComfyNode.nodeData.outputs);
        setInitialSize(this);
        this.serialize_widgets = true;
        void extensionService.invokeExtensionsAsync("nodeCreated", this);
      }
      /**
       * Configure the node from a serialised node. Keep 'name', 'type', 'shape',
       * and 'localized_name' information from the original node definition.
       */
      configure(data) {
        const RESERVED_KEYS = ["name", "type", "shape", "localized_name"];
        const inputByName = new Map(
          data.inputs?.map((input) => [input.name, input]) ?? []
        );
        const definedInputNames = new Set(
          this.inputs.map((input) => input.name)
        );
        const definedInputs = this.inputs.map((input) => {
          const inputData = inputByName.get(input.name);
          return inputData ? {
            ...inputData,
            // Whether the input has associated widget follows the
            // original node definition.
            ...toolkit.pick(input, RESERVED_KEYS.concat("widget"))
          } : input;
        });
        const extraInputs = data.inputs?.filter(
          (input) => !definedInputNames.has(input.name)
        );
        data.inputs = [...definedInputs, ...extraInputs ?? []];
        data.outputs = toolkit.zip(this.outputs, data.outputs).map(
          ([output, outputData]) => {
            if (!output) return outputData;
            return outputData ? {
              ...outputData,
              ...toolkit.pick(output, RESERVED_KEYS)
            } : output;
          }
        );
        data.widgets_values = migrateWidgetsValues(
          ComfyNode.nodeData.inputs,
          this.widgets ?? [],
          data.widgets_values ?? []
        );
        super.configure(data);
      }
    };
    addNodeContextMenuHandler(node2);
    addDrawBackgroundHandler(node2);
    addNodeKeyHandler(node2);
    node2.prototype.comfyClass = nodeDefV1.name;
    node2.comfyClass = nodeDefV1.name;
    const nodeDef = new ComfyNodeDefImpl(nodeDefV1);
    node2.nodeData = nodeDef;
    LiteGraph.registerNodeType(subgraph.id, node2);
    node2.category = nodeDef.category;
    node2.skip_list = true;
    node2.title = nodeDef.display_name || nodeDef.name;
  }
  __name(registerSubgraphNodeDef, "registerSubgraphNodeDef");
  async function registerNodeDef(nodeId, nodeDefV1) {
    const node2 = class ComfyNode extends LGraphNode {
      static {
        __name(this, "ComfyNode");
      }
      static comfyClass;
      static title;
      static category;
      static nodeData;
      _initialMinSize = { width: 1, height: 1 };
      constructor(title) {
        super(title);
        setupStrokeStyles(this);
        addInputs(this, ComfyNode.nodeData.inputs);
        addOutputs(this, ComfyNode.nodeData.outputs);
        setInitialSize(this);
        this.serialize_widgets = true;
        if (ComfyNode.nodeData.api_node) {
          this.color = LGraphCanvas.node_colors.yellow.color;
          this.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor;
        }
        void extensionService.invokeExtensionsAsync("nodeCreated", this);
      }
      /**
       * Configure the node from a serialised node. Keep 'name', 'type', 'shape',
       * and 'localized_name' information from the original node definition.
       */
      configure(data) {
        const RESERVED_KEYS = ["name", "type", "shape", "localized_name"];
        const inputByName = new Map(
          data.inputs?.map((input) => [input.name, input]) ?? []
        );
        const definedInputNames = new Set(
          this.inputs.map((input) => input.name)
        );
        const definedInputs = this.inputs.map((input) => {
          const inputData = inputByName.get(input.name);
          return inputData ? {
            ...inputData,
            // Whether the input has associated widget follows the
            // original node definition.
            ...toolkit.pick(input, RESERVED_KEYS.concat("widget"))
          } : input;
        });
        const extraInputs = data.inputs?.filter(
          (input) => !definedInputNames.has(input.name)
        );
        data.inputs = [...definedInputs, ...extraInputs ?? []];
        data.outputs = toolkit.zip(this.outputs, data.outputs).map(
          ([output, outputData]) => {
            if (!output) return outputData;
            return outputData ? {
              ...outputData,
              ...toolkit.pick(output, RESERVED_KEYS)
            } : output;
          }
        );
        data.widgets_values = migrateWidgetsValues(
          ComfyNode.nodeData.inputs,
          this.widgets ?? [],
          data.widgets_values ?? []
        );
        super.configure(data);
      }
    };
    addNodeContextMenuHandler(node2);
    addDrawBackgroundHandler(node2);
    addNodeKeyHandler(node2);
    node2.prototype.comfyClass = nodeDefV1.name;
    node2.comfyClass = nodeDefV1.name;
    await extensionService.invokeExtensionsAsync(
      "beforeRegisterNodeDef",
      node2,
      nodeDefV1
      // Receives V1 NodeDef, and potentially make modifications to it
    );
    const nodeDef = new ComfyNodeDefImpl(nodeDefV1);
    node2.nodeData = nodeDef;
    LiteGraph.registerNodeType(nodeId, node2);
    node2.category = nodeDef.category;
    node2.title = nodeDef.display_name || nodeDef.name;
  }
  __name(registerNodeDef, "registerNodeDef");
  function addNodeContextMenuHandler(node2) {
    function getCopyImageOption(img) {
      if (typeof window.ClipboardItem === "undefined") return [];
      return [
        {
          content: "Copy Image",
          callback: /* @__PURE__ */ __name(async () => {
            const url = new URL(img.src);
            url.searchParams.delete("preview");
            const writeImage = /* @__PURE__ */ __name(async (blob) => {
              await navigator.clipboard.write([
                new ClipboardItem({
                  [blob.type]: blob
                })
              ]);
            }, "writeImage");
            try {
              const data = await fetch(url);
              const blob = await data.blob();
              try {
                await writeImage(blob);
              } catch (error) {
                if (blob.type !== "image/png") {
                  const canvas2 = $el("canvas", {
                    width: img.naturalWidth,
                    height: img.naturalHeight
                  });
                  const ctx = canvas2.getContext("2d");
                  let image;
                  if (typeof window.createImageBitmap === "undefined") {
                    image = new Image();
                    const p = new Promise((resolve, reject) => {
                      image.onload = resolve;
                      image.onerror = reject;
                    }).finally(() => {
                      URL.revokeObjectURL(image.src);
                    });
                    image.src = URL.createObjectURL(blob);
                    await p;
                  } else {
                    image = await createImageBitmap(blob);
                  }
                  try {
                    ctx.drawImage(image, 0, 0);
                    canvas2.toBlob(writeImage, "image/png");
                  } finally {
                    if (typeof image.close === "function") {
                      image.close();
                    }
                  }
                  return;
                }
                throw error;
              }
            } catch (error) {
              toastStore.addAlert(
                t("toastMessages.errorCopyImage", {
                  // @ts-expect-error fixme ts strict error
                  error: error.message ?? error
                })
              );
            }
          }, "callback")
        }
      ];
    }
    __name(getCopyImageOption, "getCopyImageOption");
    node2.prototype.getExtraMenuOptions = function(canvas2, options) {
      if (this.imgs) {
        let img;
        if (this.imageIndex != null) {
          img = this.imgs[this.imageIndex];
        } else if (this.overIndex != null) {
          img = this.imgs[this.overIndex];
        }
        if (img) {
          options.unshift(
            {
              content: "Open Image",
              callback: /* @__PURE__ */ __name(() => {
                const url = new URL(img.src);
                url.searchParams.delete("preview");
                window.open(url, "_blank");
              }, "callback")
            },
            ...getCopyImageOption(img),
            {
              content: "Save Image",
              callback: /* @__PURE__ */ __name(() => {
                const url = new URL(img.src);
                url.searchParams.delete("preview");
                const filename = new URLSearchParams(url.search).get("filename");
                downloadFile(url.toString(), filename ?? void 0);
              }, "callback")
            }
          );
        }
      }
      options.push({
        content: "Bypass",
        callback: /* @__PURE__ */ __name(() => {
          toggleSelectedNodesMode(LGraphEventMode.BYPASS);
          canvas2.setDirty(true, true);
        }, "callback")
      });
      if (!ComfyApp.clipspace_return_node) {
        options.push({
          content: "Copy (Clipspace)",
          callback: /* @__PURE__ */ __name(() => {
            ComfyApp.copyToClipspace(this);
          }, "callback")
        });
        if (ComfyApp.clipspace != null) {
          options.push({
            content: "Paste (Clipspace)",
            callback: /* @__PURE__ */ __name(() => {
              ComfyApp.pasteFromClipspace(this);
            }, "callback")
          });
        }
        if (isImageNode(this)) {
          options.push({
            content: "Open in MaskEditor | Image Canvas",
            callback: /* @__PURE__ */ __name(() => {
              useMaskEditor().openMaskEditor(this);
            }, "callback")
          });
        }
      }
      if (this instanceof SubgraphNode) {
        options.unshift(
          {
            content: "Edit Subgraph Widgets",
            callback: /* @__PURE__ */ __name(() => {
              useRightSidePanelStore().openPanel("subgraph");
            }, "callback")
          },
          {
            content: "Unpack Subgraph",
            callback: /* @__PURE__ */ __name(() => {
              const { unpackSubgraph } = useSubgraphOperations();
              unpackSubgraph();
            }, "callback")
          }
        );
      }
      const [x2, y] = canvas2.graph_mouse;
      const overWidget = this.getWidgetOnPos(x2, y, true);
      if (overWidget) {
        const input = this.inputs.find(
          (inp) => inp.widget?.name === overWidget.name
        );
        if (input)
          options.unshift({
            content: `${t("contextMenu.RenameWidget")}: ${overWidget.label ?? overWidget.name}`,
            callback: /* @__PURE__ */ __name(async () => {
              const newLabel = await useDialogService().prompt({
                title: t("g.rename"),
                message: t("g.enterNewName") + ":",
                defaultValue: overWidget.label,
                placeholder: overWidget.name
              });
              if (newLabel === null) return;
              overWidget.label = newLabel || void 0;
              input.label = newLabel || void 0;
              useCanvasStore().canvas?.setDirty(true);
            }, "callback")
          });
        if (this.graph && !this.graph.isRootGraph) {
          addWidgetPromotionOptions(options, overWidget, this);
        }
      }
      return [];
    };
  }
  __name(addNodeContextMenuHandler, "addNodeContextMenuHandler");
  function updatePreviews(node2, callback) {
    try {
      unsafeUpdatePreviews.call(node2, callback);
    } catch (error) {
      console.error("Error drawing node background", error);
    }
  }
  __name(updatePreviews, "updatePreviews");
  function unsafeUpdatePreviews(callback) {
    if (this.flags.collapsed) return;
    const nodeOutputStore = useNodeOutputStore();
    const { showAnimatedPreview, removeAnimatedPreview } = useNodeAnimatedImage();
    const { showCanvasImagePreview, removeCanvasImagePreview } = useNodeCanvasImagePreview();
    const output = nodeOutputStore.getNodeOutputs(this);
    const preview = nodeOutputStore.getNodePreviews(this);
    const isNewOutput = output && this.images !== output.images;
    const isNewPreview = preview && this.preview !== preview;
    if (isNewPreview) this.preview = preview;
    if (isNewOutput) this.images = output.images;
    if (isNewOutput || isNewPreview) {
      this.animatedImages = output?.animated?.find(Boolean);
      const isAnimatedWebp = this.animatedImages && output?.images?.some((img) => img.filename?.includes("webp"));
      const isAnimatedPng = this.animatedImages && output?.images?.some((img) => img.filename?.includes("png"));
      const isVideo = this.animatedImages && !isAnimatedWebp && !isAnimatedPng || isVideoNode(this);
      if (isVideo) {
        useNodeVideo(this, callback).showPreview();
      } else {
        useNodeImage(this, callback).showPreview();
      }
    }
    if (!this.imgs?.length) return;
    if (this.animatedImages) {
      removeCanvasImagePreview(this);
      showAnimatedPreview(this);
    } else {
      removeAnimatedPreview(this);
      showCanvasImagePreview(this);
    }
  }
  __name(unsafeUpdatePreviews, "unsafeUpdatePreviews");
  function addDrawBackgroundHandler(node2) {
    node2.prototype.setSizeForImage = function() {
      console.warn(
        "node.setSizeForImage is deprecated. Now it has no effect. Please remove the call to it."
      );
    };
    node2.prototype.onDrawBackground = function() {
      updatePreviews(this);
    };
  }
  __name(addDrawBackgroundHandler, "addDrawBackgroundHandler");
  function addNodeKeyHandler(node2) {
    const origNodeOnKeyDown = node2.prototype.onKeyDown;
    node2.prototype.onKeyDown = function(e2) {
      if (origNodeOnKeyDown && origNodeOnKeyDown.apply(this, e2) === false) {
        return false;
      }
      if (this.flags.collapsed || !this.imgs || this.imageIndex === null) {
        return;
      }
      let handled = false;
      if (e2.key === "ArrowLeft" || e2.key === "ArrowRight") {
        if (e2.key === "ArrowLeft") {
          this.imageIndex -= 1;
        } else if (e2.key === "ArrowRight") {
          this.imageIndex += 1;
        }
        this.imageIndex %= this.imgs.length;
        if (this.imageIndex < 0) {
          this.imageIndex = this.imgs.length + this.imageIndex;
        }
        handled = true;
      } else if (e2.key === "Escape") {
        this.imageIndex = null;
        handled = true;
      }
      if (handled === true) {
        e2.preventDefault();
        e2.stopImmediatePropagation();
        return false;
      }
    };
  }
  __name(addNodeKeyHandler, "addNodeKeyHandler");
  function addNodeOnGraph(nodeDef, options = {}) {
    options.pos ??= getCanvasCenter();
    if (nodeDef.name.startsWith(useSubgraphStore().typePrefix)) {
      const canvas2 = canvasStore.getCanvas();
      const bp = useSubgraphStore().getBlueprint(nodeDef.name);
      const items = {
        nodes: bp.nodes,
        subgraphs: bp.definitions?.subgraphs
      };
      const results = canvas2._deserializeItems(items, {
        position: options.pos
      });
      if (!results) throw new Error("Failed to add subgraph blueprint");
      const node22 = results.nodes.values().next().value;
      if (!node22)
        throw new Error(
          "Subgraph blueprint was added, but failed to resolve a subgraph Node"
        );
      return node22;
    }
    const node2 = LiteGraph.createNode(
      nodeDef.name,
      nodeDef.display_name,
      options
    );
    const graph = useWorkflowStore().activeSubgraph ?? app$1.graph;
    graph.add(node2);
    return node2;
  }
  __name(addNodeOnGraph, "addNodeOnGraph");
  function getCanvasCenter() {
    const dpi = Math.max(window.devicePixelRatio ?? 1, 1);
    const [x2, y, w, h2] = app$1.canvas.ds.visible_area;
    return [x2 + w / dpi / 2, y + h2 / dpi / 2];
  }
  __name(getCanvasCenter, "getCanvasCenter");
  function goToNode(nodeId) {
    const graphNode = app$1.canvas.graph?.getNodeById(nodeId);
    if (!graphNode) return;
    app$1.canvas.animateToBounds(graphNode.boundingRect);
  }
  __name(goToNode, "goToNode");
  function ensureBounds(nodes) {
    for (const node2 of nodes) {
      if (!node2.boundingRect.every((i) => i === 0)) continue;
      node2.updateArea();
    }
  }
  __name(ensureBounds, "ensureBounds");
  function resetView() {
    const canvas2 = canvasStore.canvas;
    if (!canvas2) return;
    canvas2.ds.scale = 1;
    canvas2.ds.offset = [0, 0];
    canvas2.setDirty(true, true);
  }
  __name(resetView, "resetView");
  function fitView() {
    const canvas2 = canvasStore.getCanvas();
    const nodes = canvas2.graph?.nodes;
    if (!nodes) return;
    ensureBounds(nodes);
    const bounds = createBounds(nodes);
    if (!bounds) return;
    canvas2.ds.fitToBounds(bounds);
    canvas2.setDirty(true, true);
  }
  __name(fitView, "fitView");
  return {
    registerNodeDef,
    registerSubgraphNodeDef,
    addNodeOnGraph,
    addNodeInput,
    getCanvasCenter,
    goToNode,
    resetView,
    fitView,
    updatePreviews
  };
}, "useLitegraphService");
const useSubgraphNavigationStore = defineStore(
  "subgraphNavigation",
  () => {
    const workflowStore = useWorkflowStore();
    const canvasStore = useCanvasStore();
    const activeSubgraph = shallowRef();
    const idStack = ref([]);
    const viewportCache = new QuickLRU({
      maxSize: 32
    });
    const getCurrentRootGraphId = /* @__PURE__ */ __name(() => {
      const canvas2 = canvasStore.getCanvas();
      if (!canvas2) return "root";
      return canvas2.graph?.rootGraph?.id ?? "root";
    }, "getCurrentRootGraphId");
    const navigationStack = computed(
      () => idStack.value.map((id2) => app$1.rootGraph.subgraphs.get(id2)).filter(isNonNullish)
    );
    const restoreState = /* @__PURE__ */ __name((subgraphIds) => {
      idStack.value.length = 0;
      for (const id2 of subgraphIds) idStack.value.push(id2);
    }, "restoreState");
    const exportState = /* @__PURE__ */ __name(() => [...idStack.value], "exportState");
    const getCurrentViewport = /* @__PURE__ */ __name(() => {
      const canvas2 = canvasStore.getCanvas();
      if (!canvas2) return null;
      return {
        scale: canvas2.ds.state.scale,
        offset: [...canvas2.ds.state.offset]
      };
    }, "getCurrentViewport");
    const saveViewport = /* @__PURE__ */ __name((graphId) => {
      const viewport = getCurrentViewport();
      if (!viewport) return;
      viewportCache.set(graphId, viewport);
    }, "saveViewport");
    const restoreViewport = /* @__PURE__ */ __name((graphId) => {
      const viewport = viewportCache.get(graphId);
      if (!viewport) return;
      const canvas2 = app$1.canvas;
      if (!canvas2) return;
      canvas2.ds.scale = viewport.scale;
      canvas2.ds.offset[0] = viewport.offset[0];
      canvas2.ds.offset[1] = viewport.offset[1];
      canvas2.setDirty(true, true);
    }, "restoreViewport");
    const onNavigated = /* @__PURE__ */ __name((subgraph, prevSubgraph) => {
      if (prevSubgraph) {
        saveViewport(prevSubgraph.id);
      } else if (!prevSubgraph && subgraph) {
        saveViewport(getCurrentRootGraphId());
      }
      const isInRootGraph = !subgraph;
      if (isInRootGraph) {
        idStack.value.length = 0;
        restoreViewport(getCurrentRootGraphId());
        return;
      }
      const path = findSubgraphPathById(subgraph.rootGraph, subgraph.id);
      const isInReachableSubgraph = !!path;
      if (isInReachableSubgraph) {
        idStack.value = [...path];
      } else {
        idStack.value = [subgraph.id];
      }
      restoreViewport(subgraph.id);
    }, "onNavigated");
    watch(
      () => workflowStore.activeSubgraph,
      (newValue2, oldValue) => {
        onNavigated(newValue2, oldValue);
      }
    );
    return {
      activeSubgraph,
      navigationStack,
      restoreState,
      exportState,
      saveViewport,
      restoreViewport,
      viewportCache
    };
  }
);
function getProxyWidgets(node2) {
  return parseProxyWidgets(node2.properties.proxyWidgets);
}
__name(getProxyWidgets, "getProxyWidgets");
function promoteWidget(node2, widget, parents) {
  for (const parent of parents) {
    const proxyWidgets = [
      ...getProxyWidgets(parent),
      widgetItemToProperty([node2, widget])
    ];
    parent.properties.proxyWidgets = proxyWidgets;
  }
  widget.promoted = true;
}
__name(promoteWidget, "promoteWidget");
function demoteWidget(node2, widget, parents) {
  for (const parent of parents) {
    const proxyWidgets = getProxyWidgets(parent).filter(
      (widgetItem) => !matchesPropertyItem([node2, widget])(widgetItem)
    );
    parent.properties.proxyWidgets = proxyWidgets;
  }
  widget.promoted = false;
}
__name(demoteWidget, "demoteWidget");
function matchesWidgetItem([nodeId, widgetName]) {
  return ([n, w]) => n.id == nodeId && w.name === widgetName;
}
__name(matchesWidgetItem, "matchesWidgetItem");
function matchesPropertyItem([n, w]) {
  return ([nodeId, widgetName]) => n.id == nodeId && w.name === widgetName;
}
__name(matchesPropertyItem, "matchesPropertyItem");
function widgetItemToProperty([n, w]) {
  return [`${n.id}`, w.name];
}
__name(widgetItemToProperty, "widgetItemToProperty");
function getParentNodes() {
  const { navigationStack } = useSubgraphNavigationStore();
  const subgraph = navigationStack.at(-1);
  if (!subgraph) {
    useToastStore().add({
      severity: "error",
      summary: t("g.error"),
      detail: t("subgraphStore.promoteOutsideSubgraph"),
      life: 2e3
    });
    return [];
  }
  const parentGraph = navigationStack.at(-2) ?? subgraph.rootGraph;
  return parentGraph.nodes.filter(
    (node2) => node2.type === subgraph.id && node2.isSubgraphNode()
  );
}
__name(getParentNodes, "getParentNodes");
function addWidgetPromotionOptions(options, widget, node2) {
  const parents = getParentNodes();
  const promotableParents = parents.filter(
    (s) => !getProxyWidgets(s).some(matchesPropertyItem([node2, widget]))
  );
  if (promotableParents.length > 0)
    options.unshift({
      content: `Promote Widget: ${widget.label ?? widget.name}`,
      callback: /* @__PURE__ */ __name(() => {
        promoteWidget(node2, widget, promotableParents);
      }, "callback")
    });
  else {
    options.unshift({
      content: `Un-Promote Widget: ${widget.label ?? widget.name}`,
      callback: /* @__PURE__ */ __name(() => {
        demoteWidget(node2, widget, parents);
      }, "callback")
    });
  }
}
__name(addWidgetPromotionOptions, "addWidgetPromotionOptions");
function tryToggleWidgetPromotion() {
  const canvas2 = useCanvasStore().getCanvas();
  const [x2, y] = canvas2.graph_mouse;
  const node2 = canvas2.graph?.getNodeOnPos(x2, y, canvas2.visible_nodes);
  if (!node2) return;
  const widget = node2.getWidgetOnPos(x2, y, true);
  const parents = getParentNodes();
  if (!parents.length || !widget) return;
  const promotableParents = parents.filter(
    (s) => !getProxyWidgets(s).some(matchesPropertyItem([node2, widget]))
  );
  if (promotableParents.length > 0)
    promoteWidget(node2, widget, promotableParents);
  else demoteWidget(node2, widget, parents);
}
__name(tryToggleWidgetPromotion, "tryToggleWidgetPromotion");
const recommendedNodes = [
  "CLIPTextEncode",
  "LoadImage",
  "SaveImage",
  "PreviewImage"
];
const recommendedWidgetNames = ["seed"];
function isRecommendedWidget([node2, widget]) {
  return !widget.computedDisabled && (recommendedNodes.includes(node2.type) || recommendedWidgetNames.includes(widget.name));
}
__name(isRecommendedWidget, "isRecommendedWidget");
function nodeWidgets(n) {
  return n.widgets?.map((w) => [n, w]) ?? [];
}
__name(nodeWidgets, "nodeWidgets");
function promoteRecommendedWidgets(subgraphNode) {
  const { updatePreviews } = useLitegraphService();
  const interiorNodes = subgraphNode.subgraph.nodes;
  for (const node2 of interiorNodes) {
    let checkWidgets = /* @__PURE__ */ __name(function() {
      updatePreviews(node2);
      const widget = node2.widgets?.find((w) => w.name.startsWith("$$"));
      if (!widget) return;
      const pw = getProxyWidgets(subgraphNode);
      if (pw.some(matchesPropertyItem([node2, widget]))) return;
      promoteWidget(node2, widget, [subgraphNode]);
    }, "checkWidgets");
    node2.updateComputedDisabled();
    requestAnimationFrame(() => updatePreviews(node2, checkWidgets));
  }
  const filteredWidgets = interiorNodes.flatMap(nodeWidgets).filter(isRecommendedWidget);
  const proxyWidgets = filteredWidgets.map(widgetItemToProperty);
  subgraphNode.properties.proxyWidgets = proxyWidgets;
  subgraphNode.computeSize(subgraphNode.size);
}
__name(promoteRecommendedWidgets, "promoteRecommendedWidgets");
function pruneDisconnected(subgraphNode) {
  subgraphNode.properties.proxyWidgets = subgraphNode.widgets.filter(isProxyWidget).filter((w) => !isDisconnectedWidget(w)).map((w) => [w._overlay.nodeId, w._overlay.widgetName]);
}
__name(pruneDisconnected, "pruneDisconnected");
class DisconnectedWidget extends BaseWidget {
  static {
    __name(this, "DisconnectedWidget");
  }
  constructor(widget) {
    super(widget, new LGraphNode("DisconnectedPlaceholder"));
    this.disabled = true;
  }
  drawWidget(ctx, { width: width2, showText = true }) {
    ctx.save();
    this.drawWidgetShape(ctx, { width: width2, showText });
    if (showText) {
      this.drawTruncatingText({ ctx, width: width2, leftPadding: 0, rightPadding: 0 });
    }
    ctx.restore();
  }
  onClick() {
  }
  get _displayValue() {
    return "Disconnected";
  }
}
const conf = {
  type: "button",
  value: void 0,
  name: "Disconnected",
  options: {},
  y: 0,
  clicked: false
};
const disconnectedWidget = new DisconnectedWidget(conf);
function isProxyWidget(w) {
  return w?._overlay?.isProxyWidget ?? false;
}
__name(isProxyWidget, "isProxyWidget");
function isDisconnectedWidget(w) {
  return w instanceof disconnectedWidget.constructor;
}
__name(isDisconnectedWidget, "isDisconnectedWidget");
function registerProxyWidgets(canvas2) {
  canvas2.canvas.addEventListener("subgraph-opened", (e2) => {
    const { subgraph, fromNode } = e2.detail;
    const proxyWidgets = parseProxyWidgets(fromNode.properties.proxyWidgets);
    for (const node2 of subgraph.nodes) {
      for (const widget of node2.widgets ?? []) {
        widget.promoted = proxyWidgets.some(
          ([n, w]) => node2.id == n && widget.name == w
        );
      }
    }
  });
  canvas2.canvas.addEventListener(
    "subgraph-converted",
    (e2) => promoteRecommendedWidgets(e2.detail.subgraphNode)
  );
  SubgraphNode.prototype.onConfigure = onConfigure;
}
__name(registerProxyWidgets, "registerProxyWidgets");
const originalOnConfigure = SubgraphNode.prototype.onConfigure;
const onConfigure = /* @__PURE__ */ __name(function(serialisedNode) {
  if (!this.isSubgraphNode())
    throw new Error("Can't add proxyWidgets to non-subgraphNode");
  const canvasStore = useCanvasStore();
  this.properties.proxyWidgets ??= [];
  originalOnConfigure?.call(this, serialisedNode);
  Object.defineProperty(this.properties, "proxyWidgets", {
    get: /* @__PURE__ */ __name(() => this.widgets.map(
      (w) => isProxyWidget(w) ? [w._overlay.nodeId, w._overlay.widgetName] : ["-1", w.name]
    ), "get"),
    set: /* @__PURE__ */ __name((property) => {
      const parsed = parseProxyWidgets(property);
      const { deactivateWidget, setWidget } = useDomWidgetStore();
      const isActiveGraph = useCanvasStore().canvas?.graph === this.graph;
      if (isActiveGraph) {
        for (const w of this.widgets.filter((w2) => isProxyWidget(w2))) {
          if (w instanceof DOMWidgetImpl) deactivateWidget(w.id);
        }
      }
      const newWidgets = parsed.flatMap(([nodeId, widgetName]) => {
        if (nodeId === "-1") {
          const widget = this.widgets.find((w2) => w2.name === widgetName);
          return widget ? [widget] : [];
        }
        const w = newProxyWidget(this, nodeId, widgetName);
        if (isActiveGraph && w instanceof DOMWidgetImpl) setWidget(w);
        return [w];
      });
      this.widgets = this.widgets.filter(
        (w) => !isProxyWidget(w) && !parsed.some(([, name2]) => w.name === name2)
      );
      this.widgets.push(...newWidgets);
      canvasStore.canvas?.setDirty(true, true);
      this._setConcreteSlots();
      this.arrange();
    }, "set")
  });
  if (serialisedNode.properties?.proxyWidgets) {
    this.properties.proxyWidgets = serialisedNode.properties.proxyWidgets;
    const parsed = parseProxyWidgets(serialisedNode.properties.proxyWidgets);
    serialisedNode.widgets_values?.forEach((v2, index2) => {
      if (parsed[index2]?.[0] !== "-1") return;
      const widget = this.widgets.find((w) => w.name == parsed[index2][1]);
      if (v2 !== null && widget) widget.value = v2;
    });
  }
}, "onConfigure");
function newProxyWidget(subgraphNode, nodeId, widgetName) {
  const name2 = `${nodeId}: ${widgetName}`;
  const overlay = {
    //items specific for proxy management
    nodeId,
    graph: subgraphNode.subgraph,
    widgetName,
    //Items which normally exist on widgets
    afterQueued: void 0,
    computedHeight: void 0,
    isProxyWidget: true,
    last_y: void 0,
    name: name2,
    node: subgraphNode,
    onRemove: void 0,
    promoted: void 0,
    serialize: false,
    width: void 0,
    y: 0
  };
  return newProxyFromOverlay(subgraphNode, overlay);
}
__name(newProxyWidget, "newProxyWidget");
function resolveLinkedWidget(overlay) {
  const { graph, nodeId, widgetName } = overlay;
  const n = getNodeByExecutionId(graph, nodeId);
  if (!n) return [void 0, void 0];
  const widget = n.widgets?.find((w) => w.name === widgetName);
  if (widget && isProxyWidget(widget) && isDisconnectedWidget(widget))
    widget.computedHeight = 20;
  return [n, widget];
}
__name(resolveLinkedWidget, "resolveLinkedWidget");
function newProxyFromOverlay(subgraphNode, overlay) {
  const { updatePreviews } = useLitegraphService();
  let [linkedNode, linkedWidget] = resolveLinkedWidget(overlay);
  let backingWidget = linkedWidget ?? disconnectedWidget;
  if (overlay.widgetName.startsWith("$$")) {
    overlay.node = new Proxy(subgraphNode, {
      get(_t, p) {
        if (p !== "imgs") return Reflect.get(subgraphNode, p);
        if (!linkedNode) return [];
        return linkedNode.imgs;
      }
    });
  }
  const handler = {
    get(_t, property, receiver) {
      let redirectedTarget = backingWidget;
      let redirectedReceiver = receiver;
      if (property == "_overlay") return overlay;
      else if (property == "value") redirectedReceiver = backingWidget;
      if (Object.prototype.hasOwnProperty.call(overlay, property)) {
        redirectedTarget = overlay;
        redirectedReceiver = overlay;
      }
      return Reflect.get(redirectedTarget, property, redirectedReceiver);
    },
    set(_t, property, value) {
      let redirectedTarget = backingWidget;
      if (property == "computedHeight") {
        if (overlay.widgetName.startsWith("$$") && linkedNode) {
          updatePreviews(linkedNode);
        }
        if (linkedNode && linkedWidget?.computedDisabled) {
          demoteWidget(linkedNode, linkedWidget, [subgraphNode]);
        }
        [linkedNode, linkedWidget] = resolveLinkedWidget(overlay);
        backingWidget = linkedWidget ?? disconnectedWidget;
      }
      if (Object.prototype.hasOwnProperty.call(overlay, property)) {
        redirectedTarget = overlay;
      }
      return Reflect.set(redirectedTarget, property, value, redirectedTarget);
    },
    getPrototypeOf() {
      return Reflect.getPrototypeOf(backingWidget);
    },
    ownKeys() {
      return Reflect.ownKeys(backingWidget);
    },
    has(_t, property) {
      let redirectedTarget = backingWidget;
      if (Object.prototype.hasOwnProperty.call(overlay, property)) {
        redirectedTarget = overlay;
      }
      return Reflect.has(redirectedTarget, property);
    }
  };
  const w = new Proxy(disconnectedWidget, handler);
  return w;
}
__name(newProxyFromOverlay, "newProxyFromOverlay");
function getNodeById(graph, id2) {
  if (graph.getNodeById) {
    return graph.getNodeById(id2);
  }
  graph = graph;
  return graph.nodes.find((node2) => node2.id == id2);
}
__name(getNodeById, "getNodeById");
function extendLink(link) {
  return {
    link,
    id: link[0],
    origin_id: link[1],
    origin_slot: link[2],
    target_id: link[3],
    target_slot: link[4],
    type: link[5]
  };
}
__name(extendLink, "extendLink");
function fixBadLinks(graph, options = {}) {
  const { fix = false, silent = false, logger: _logger = console } = options;
  const logger2 = {
    log: /* @__PURE__ */ __name((...args) => {
      if (!silent) {
        _logger.log(...args);
      }
    }, "log")
  };
  const patchedNodeSlots = {};
  const data = {
    patchedNodes: [],
    deletedLinks: []
  };
  function patchNodeSlot(node2, ioDir, slot, linkId, op) {
    patchedNodeSlots[node2.id] = patchedNodeSlots[node2.id] || {};
    const patchedNode = patchedNodeSlots[node2.id];
    if (ioDir == 0) {
      patchedNode["inputs"] = patchedNode["inputs"] || {};
      if (patchedNode["inputs"][slot] !== void 0) {
        logger2.log(
          ` > Already set ${node2.id}.inputs[${slot}] to ${patchedNode["inputs"][slot]} Skipping.`
        );
        return false;
      }
      const linkIdToSet = op === "REMOVE" ? null : linkId;
      patchedNode["inputs"][slot] = linkIdToSet;
    } else {
      patchedNode["outputs"] = patchedNode["outputs"] || {};
      patchedNode["outputs"][slot] = patchedNode["outputs"][slot] || {
        links: [...node2.outputs?.[slot]?.links || []],
        changes: {}
      };
      if (patchedNode["outputs"][slot]["changes"][linkId] !== void 0) {
        logger2.log(
          ` > Already set ${node2.id}.outputs[${slot}] to ${patchedNode["inputs"][slot]}! Skipping.`
        );
        return false;
      }
      patchedNode["outputs"][slot]["changes"][linkId] = op;
      if (op === "ADD") {
        const linkIdIndex = patchedNode["outputs"][slot]["links"].indexOf(linkId);
        if (linkIdIndex !== -1) {
          logger2.log(
            ` > Hmmm.. asked to add ${linkId} but it is already in list...`
          );
          return false;
        }
        patchedNode["outputs"][slot]["links"].push(linkId);
        if (fix) {
          node2.outputs = node2.outputs || [];
          node2.outputs[slot] = node2.outputs[slot] || {};
          node2.outputs[slot].links = node2.outputs[slot].links || [];
          node2.outputs[slot].links.push(linkId);
        }
      } else {
        const linkIdIndex = patchedNode["outputs"][slot]["links"].indexOf(linkId);
        if (linkIdIndex === -1) {
          logger2.log(
            ` > Hmmm.. asked to remove ${linkId} but it doesn't exist...`
          );
          return false;
        }
        patchedNode["outputs"][slot]["links"].splice(linkIdIndex, 1);
        if (fix) {
          node2.outputs?.[slot].links.splice(linkIdIndex, 1);
        }
      }
    }
    data.patchedNodes.push(node2);
    return true;
  }
  __name(patchNodeSlot, "patchNodeSlot");
  function nodeHasLinkId(node2, ioDir, slot, linkId) {
    let has = false;
    if (ioDir === 0) {
      const nodeHasIt = node2.inputs?.[slot]?.link === linkId;
      if (patchedNodeSlots[node2.id]?.["inputs"]) {
        const patchedHasIt = patchedNodeSlots[node2.id]["inputs"][slot] === linkId;
        if (fix && nodeHasIt !== patchedHasIt) {
          throw Error("Error. Expected node to match patched data.");
        }
        has = patchedHasIt;
      } else {
        has = !!nodeHasIt;
      }
    } else {
      const nodeHasIt = node2.outputs?.[slot]?.links?.includes(linkId);
      if (patchedNodeSlots[node2.id]?.["outputs"]?.[slot]?.["changes"][linkId]) {
        const patchedHasIt = patchedNodeSlots[node2.id]["outputs"][slot]?.links.includes(linkId);
        if (fix && nodeHasIt !== patchedHasIt) {
          throw Error("Error. Expected node to match patched data.");
        }
        has = !!patchedHasIt;
      } else {
        has = !!nodeHasIt;
      }
    }
    return has;
  }
  __name(nodeHasLinkId, "nodeHasLinkId");
  function nodeHasAnyLink(node2, ioDir, slot) {
    let hasAny = false;
    {
      const nodeHasAny = node2.inputs?.[slot]?.link != null;
      if (patchedNodeSlots[node2.id]?.["inputs"]) {
        const patchedHasAny = patchedNodeSlots[node2.id]["inputs"][slot] != null;
        if (fix && nodeHasAny !== patchedHasAny) {
          throw Error("Error. Expected node to match patched data.");
        }
        hasAny = patchedHasAny;
      } else {
        hasAny = !!nodeHasAny;
      }
    }
    return hasAny;
  }
  __name(nodeHasAnyLink, "nodeHasAnyLink");
  let links = [];
  if (!Array.isArray(graph.links)) {
    links = Object.values(graph.links).reduce((acc, v2) => {
      acc[v2.id] = v2;
      return acc;
    }, links);
  } else {
    links = graph.links;
  }
  const linksReverse = [...links];
  linksReverse.reverse();
  for (const l of linksReverse) {
    if (!l) continue;
    const link = l.origin_slot != null ? l : extendLink(l);
    const originNode = getNodeById(graph, link.origin_id);
    const originHasLink = /* @__PURE__ */ __name(() => nodeHasLinkId(originNode, 1, link.origin_slot, link.id), "originHasLink");
    const patchOrigin = /* @__PURE__ */ __name((op, id2 = link.id) => patchNodeSlot(originNode, 1, link.origin_slot, id2, op), "patchOrigin");
    const targetNode = getNodeById(graph, link.target_id);
    const targetHasLink = /* @__PURE__ */ __name(() => nodeHasLinkId(targetNode, 0, link.target_slot, link.id), "targetHasLink");
    const targetHasAnyLink = /* @__PURE__ */ __name(() => nodeHasAnyLink(targetNode, 0, link.target_slot), "targetHasAnyLink");
    const patchTarget = /* @__PURE__ */ __name((op, id2 = link.id) => patchNodeSlot(targetNode, 0, link.target_slot, id2, op), "patchTarget");
    const originLog = `origin(${link.origin_id}).outputs[${link.origin_slot}].links`;
    const targetLog = `target(${link.target_id}).inputs[${link.target_slot}].link`;
    if (!originNode || !targetNode) {
      if (!originNode && !targetNode) {
        logger2.log(
          `Link ${link.id} is invalid, both origin ${link.origin_id} and target ${link.target_id} do not exist`
        );
      } else if (!originNode) {
        logger2.log(
          `Link ${link.id} is funky... origin ${link.origin_id} does not exist, but target ${link.target_id} does.`
        );
        if (targetHasLink()) {
          logger2.log(
            ` > [PATCH] ${targetLog} does have link, will remove the inputs' link first.`
          );
          patchTarget("REMOVE", -1);
        }
      } else if (!targetNode) {
        logger2.log(
          `Link ${link.id} is funky... target ${link.target_id} does not exist, but origin ${link.origin_id} does.`
        );
        if (originHasLink()) {
          logger2.log(
            ` > [PATCH] Origin's links' has ${link.id}; will remove the link first.`
          );
          patchOrigin("REMOVE");
        }
      }
      continue;
    }
    if (targetHasLink() || originHasLink()) {
      if (!originHasLink()) {
        logger2.log(
          `${link.id} is funky... ${originLog} does NOT contain it, but ${targetLog} does.`
        );
        logger2.log(
          ` > [PATCH] Attempt a fix by adding this ${link.id} to ${originLog}.`
        );
        patchOrigin("ADD");
      } else if (!targetHasLink()) {
        logger2.log(
          `${link.id} is funky... ${targetLog} is NOT correct (is ${targetNode.inputs?.[link.target_slot]?.link}), but ${originLog} contains it`
        );
        if (!targetHasAnyLink()) {
          logger2.log(
            ` > [PATCH] ${targetLog} is not defined, will set to ${link.id}.`
          );
          let patched = patchTarget("ADD");
          if (!patched) {
            logger2.log(
              ` > [PATCH] Nvm, ${targetLog} already patched. Removing ${link.id} from ${originLog}.`
            );
            patched = patchOrigin("REMOVE");
          }
        } else {
          logger2.log(
            ` > [PATCH] ${targetLog} is defined, removing ${link.id} from ${originLog}.`
          );
          patchOrigin("REMOVE");
        }
      }
    }
  }
  for (const l of linksReverse) {
    if (!l) continue;
    const link = l.origin_slot != null ? l : extendLink(l);
    const originNode = getNodeById(graph, link.origin_id);
    const targetNode = getNodeById(graph, link.target_id);
    if ((!originNode || !nodeHasLinkId(
      originNode,
      1,
      link.origin_slot,
      link.id
    )) && (!targetNode || !nodeHasLinkId(
      targetNode,
      0,
      link.target_slot,
      link.id
    ))) {
      logger2.log(
        `${link.id} is def invalid; BOTH origin node ${link.origin_id} ${!originNode ? "is removed" : `doesn't have ${link.id}`} and ${link.origin_id} target node ${!targetNode ? "is removed" : `doesn't have ${link.id}`}.`
      );
      data.deletedLinks.push(link.id);
      continue;
    }
  }
  if (fix) {
    for (let i = data.deletedLinks.length - 1; i >= 0; i--) {
      logger2.log(`Deleting link #${data.deletedLinks[i]}.`);
      if (graph.getNodeById) {
        delete graph.links[data.deletedLinks[i]];
      } else {
        graph = graph;
        const idx = graph.links.findIndex(
          (l) => l && (l[0] === data.deletedLinks[i] || l.id === data.deletedLinks[i])
        );
        if (idx === -1) {
          logger2.log(`INDEX NOT FOUND for #${data.deletedLinks[i]}`);
        }
        logger2.log(`splicing ${idx} from links`);
        graph.links.splice(idx, 1);
      }
    }
    if (!graph.getNodeById) {
      graph.links = graph.links.filter((l) => !!l);
    }
  }
  if (!data.patchedNodes.length && !data.deletedLinks.length) {
    return {
      hasBadLinks: false,
      fixed: false,
      graph,
      patched: data.patchedNodes.length,
      deleted: data.deletedLinks.length
    };
  }
  logger2.log(
    `${fix ? "Made" : "Would make"} ${data.patchedNodes.length || "no"} node link patches, and ${data.deletedLinks.length || "no"} stale link removals.`
  );
  let hasBadLinks = !!(data.patchedNodes.length || data.deletedLinks.length);
  if (fix && !silent) {
    const rerun = fixBadLinks(graph, { fix: false, silent: true });
    hasBadLinks = rerun.hasBadLinks;
  }
  return {
    hasBadLinks,
    fixed: !!hasBadLinks && fix,
    graph,
    patched: data.patchedNodes.length,
    deleted: data.deletedLinks.length
  };
}
__name(fixBadLinks, "fixBadLinks");
function useWorkflowValidation() {
  const toastStore = useToastStore();
  function tryFixLinks(graphData, options = {}) {
    const { silent = false } = options;
    const logs = [];
    const linkValidation = fixBadLinks(
      graphData,
      {
        fix: true,
        silent,
        logger: {
          log: /* @__PURE__ */ __name((message) => {
            logs.push(message);
          }, "log")
        }
      }
    );
    if (!silent && logs.length > 0) {
      toastStore.add({
        severity: "warn",
        summary: "Workflow Validation",
        detail: logs.join("\n")
      });
    }
    if (linkValidation.fixed) {
      if (!silent) {
        toastStore.add({
          severity: "success",
          summary: "Workflow Links Fixed",
          detail: `Fixed ${linkValidation.patched} node connections and removed ${linkValidation.deleted} invalid links.`
        });
      }
    }
    return linkValidation.graph;
  }
  __name(tryFixLinks, "tryFixLinks");
  async function validateWorkflow(graphData, options = {}) {
    const { silent = false } = options;
    let validatedData = null;
    const validatedGraphData = await validateComfyWorkflow(
      graphData,
      /* onError=*/
      (err) => {
        if (!silent) {
          toastStore.addAlert(err);
        }
      }
    );
    if (validatedGraphData) {
      try {
        validatedData = tryFixLinks(validatedGraphData, { silent });
      } catch (err) {
        console.error(err);
      }
    }
    return {
      graphData: validatedData
    };
  }
  __name(validateWorkflow, "validateWorkflow");
  return {
    validateWorkflow
  };
}
__name(useWorkflowValidation, "useWorkflowValidation");
const useSubgraphService = /* @__PURE__ */ __name(() => {
  const nodeDefStore = useNodeDefStore();
  function registerLitegraphNode(nodeDef, subgraph, exportedSubgraph) {
    const instanceData = {
      id: -1,
      type: exportedSubgraph.id,
      pos: [0, 0],
      size: [100, 100],
      inputs: [],
      outputs: [],
      flags: {},
      order: 0,
      mode: 0
    };
    useLitegraphService().registerSubgraphNodeDef(
      nodeDef,
      subgraph,
      instanceData
    );
  }
  __name(registerLitegraphNode, "registerLitegraphNode");
  function createNodeDef(exportedSubgraph) {
    const { id: id2, name: name2 } = exportedSubgraph;
    const nodeDef = {
      input: { required: {} },
      output: [],
      output_is_list: [],
      output_name: [],
      output_tooltips: [],
      name: id2,
      display_name: name2,
      description: `Subgraph node for ${name2}`,
      category: "subgraph",
      output_node: false,
      python_module: "nodes"
    };
    nodeDefStore.addNodeDef(nodeDef);
    return nodeDef;
  }
  __name(createNodeDef, "createNodeDef");
  function loadSubgraphs(graphData) {
    const subgraphs = graphData.definitions?.subgraphs;
    if (!subgraphs) return;
    for (const subgraphData of subgraphs) {
      const subgraph = app$1.rootGraph.subgraphs.get(subgraphData.id) ?? app$1.rootGraph.createSubgraph(subgraphData);
      registerNewSubgraph(subgraph, subgraphData);
    }
  }
  __name(loadSubgraphs, "loadSubgraphs");
  function registerNewSubgraph(subgraph, exportedSubgraph) {
    const nodeDef = createNodeDef(exportedSubgraph);
    registerLitegraphNode(nodeDef, subgraph, exportedSubgraph);
  }
  __name(registerNewSubgraph, "registerNewSubgraph");
  return {
    loadSubgraphs,
    registerNewSubgraph
  };
}, "useSubgraphService");
const GROUP = /* @__PURE__ */ Symbol();
function isGroupNode(node2) {
  return node2.constructor?.nodeData?.[GROUP] !== void 0;
}
__name(isGroupNode, "isGroupNode");
class ExecutableGroupNodeDTO extends ExecutableNodeDTO {
  static {
    __name(this, "ExecutableGroupNodeDTO");
  }
  get isVirtualNode() {
    return true;
  }
  getInnerNodes() {
    return this.node.getInnerNodes?.(this.nodesByExecutionId) ?? [];
  }
  resolveOutput(slot, type, visited) {
    if (this.mode === LGraphEventMode.BYPASS) {
      const { inputs } = this;
      const parentInputIndexes = Object.keys(inputs).map(Number);
      const indexes = [slot, ...parentInputIndexes];
      const matchingIndex = indexes.find((i) => inputs[i]?.type === type);
      if (matchingIndex === void 0) return;
      return this.resolveInput(matchingIndex, visited);
    }
    const linkId = this.node.outputs[slot]?.links?.at(0);
    const link = this.node.graph?.getLink(linkId);
    if (!link) {
      throw new Error(
        `Failed to get link for group node ${this.node.id} with link ${linkId}`
      );
    }
    const updated = this.node.updateLink?.(link);
    if (!updated) {
      throw new Error(
        `Failed to update link for group node ${this.node.id} with link ${linkId}`
      );
    }
    const node2 = this.node.getInnerNodes?.(this.nodesByExecutionId).find((node22) => node22.id === updated.origin_id);
    if (!node2) {
      throw new Error(
        `Failed to get node for group node ${this.node.id} with link ${linkId}`
      );
    }
    return {
      node: node2,
      origin_id: `${this.id}:${updated.origin_id.split(":").at(-1)}`,
      origin_slot: updated.origin_slot
    };
  }
}
const graphToPrompt = /* @__PURE__ */ __name(async (graph, options = {}) => {
  const { sortNodes = false } = options;
  for (const node2 of graph.computeExecutionOrder(false)) {
    const innerNodes = node2.getInnerNodes ? node2.getInnerNodes(/* @__PURE__ */ new Map()) : [node2];
    for (const innerNode of innerNodes) {
      if (innerNode.isVirtualNode) {
        innerNode.applyToGraph?.();
      }
    }
  }
  const workflow = graph.serialize({ sortNodes });
  for (const node2 of workflow.nodes) {
    for (const slot of node2.inputs ?? []) {
      delete slot.localized_name;
    }
    for (const slot of node2.outputs ?? []) {
      delete slot.localized_name;
    }
  }
  compressWidgetInputSlots(workflow);
  workflow.extra ??= {};
  workflow.extra.frontendVersion = "1.36.14";
  const nodeDtoMap = /* @__PURE__ */ new Map();
  for (const node2 of graph.computeExecutionOrder(false)) {
    const dto = isGroupNode(node2) ? new ExecutableGroupNodeDTO(node2, [], nodeDtoMap) : new ExecutableNodeDTO(node2, [], nodeDtoMap);
    for (const innerNode of dto.getInnerNodes()) {
      nodeDtoMap.set(innerNode.id, innerNode);
    }
    nodeDtoMap.set(dto.id, dto);
  }
  const output = {};
  for (const node2 of nodeDtoMap.values()) {
    if (node2.isVirtualNode || node2.mode === LGraphEventMode.NEVER || node2.mode === LGraphEventMode.BYPASS) {
      continue;
    }
    const inputs = {};
    const { widgets: widgets2 } = node2;
    if (widgets2) {
      for (const [i, widget] of widgets2.entries()) {
        if (!widget.name || widget.options?.serialize === false) continue;
        const widgetValue = widget.serializeValue ? await widget.serializeValue(node2, i) : widget.value;
        inputs[widget.name] = Array.isArray(widgetValue) ? {
          __value__: widgetValue
        } : widgetValue;
      }
    }
    for (const [i, input] of node2.inputs.entries()) {
      const resolvedInput = node2.resolveInput(i);
      if (!resolvedInput) continue;
      if (resolvedInput.widgetInfo) {
        const { value } = resolvedInput.widgetInfo;
        inputs[input.name] = Array.isArray(value) ? { __value__: value } : value;
        continue;
      }
      inputs[input.name] = [
        String(resolvedInput.origin_id),
        // @ts-expect-error link.origin_slot is already number.
        parseInt(resolvedInput.origin_slot)
      ];
    }
    output[String(node2.id)] = {
      inputs,
      // TODO(huchenlei): Filter out all nodes that cannot be mapped to a
      // comfyClass.
      class_type: node2.comfyClass,
      // Ignored by the backend.
      _meta: {
        title: node2.title
      }
    };
  }
  for (const { inputs } of Object.values(output)) {
    for (const [i, input] of Object.entries(inputs)) {
      if (Array.isArray(input) && input.length === 2 && !output[input[0]]) {
        delete inputs[i];
      }
    }
  }
  return { workflow, output };
}, "graphToPrompt");
const gcd = /* @__PURE__ */ __name((a, b) => {
  let x2 = Math.abs(a);
  let y = Math.abs(b);
  if (x2 === 0) return y;
  if (y === 0) return x2;
  const epsilon = 1e-10;
  const maxIterations = 100;
  let iterations = 0;
  while (y > epsilon && iterations < maxIterations) {
    [x2, y] = [y, x2 % y];
    iterations++;
  }
  return x2;
}, "gcd");
const lcm = /* @__PURE__ */ __name((a, b) => {
  return Math.abs(a * b) / gcd(a, b);
}, "lcm");
function computeUnionBounds(rectangles) {
  const n = rectangles.length;
  if (n === 0) {
    return null;
  }
  const r0 = rectangles[0];
  let minX = r0[0];
  let minY = r0[1];
  let maxX = minX + r0[2];
  let maxY = minY + r0[3];
  for (let i = 1; i < n; i++) {
    const r = rectangles[i];
    const x1 = r[0];
    const y1 = r[1];
    const x2 = x1 + r[2];
    const y2 = y1 + r[3];
    if (x1 < minX) minX = x1;
    if (y1 < minY) minY = y1;
    if (x2 > maxX) maxX = x2;
    if (y2 > maxY) maxY = y2;
  }
  return {
    x: minX,
    y: minY,
    width: maxX - minX,
    height: maxY - minY
  };
}
__name(computeUnionBounds, "computeUnionBounds");
function anyItemOverlapsRect(items, rect) {
  const rectRight = rect[0] + rect[2];
  const rectBottom = rect[1] + rect[3];
  for (const item of items) {
    const overlaps = item.pos[0] < rectRight && item.pos[0] + item.size[0] > rect[0] && item.pos[1] < rectBottom && item.pos[1] + item.size[1] > rect[1];
    if (overlaps) return true;
  }
  return false;
}
__name(anyItemOverlapsRect, "anyItemOverlapsRect");
function findLegacyRerouteNodes(workflow) {
  return workflow.nodes.filter(
    (node2) => node2.type === "Reroute"
  );
}
__name(findLegacyRerouteNodes, "findLegacyRerouteNodes");
function noNativeReroutes(workflow) {
  return !workflow.extra?.reroutes?.length && !workflow.extra?.linkExtensions?.length;
}
__name(noNativeReroutes, "noNativeReroutes");
function getNodeCenter(node2) {
  return [node2.pos[0] + node2.size[0] / 2, node2.pos[1] + node2.size[1] / 2];
}
__name(getNodeCenter, "getNodeCenter");
class ConversionContext {
  static {
    __name(this, "ConversionContext");
  }
  constructor(workflow) {
    this.workflow = workflow;
    this.nodeById = toolkit.keyBy(workflow.nodes.map(toolkit.cloneDeep), "id");
    this.linkById = toolkit.keyBy(
      workflow.links.map((l) => ({
        id: l[0],
        origin_id: l[1],
        origin_slot: l[2],
        target_id: l[3],
        target_slot: l[4],
        type: l[5]
      })),
      "id"
    );
    const reroutes = findLegacyRerouteNodes(workflow).map((node2, index2) => ({
      nodeId: node2.id,
      id: index2 + 1,
      pos: getNodeCenter(node2),
      linkIds: []
    }));
    this.#rerouteIdCounter = reroutes.length + 1;
    this.rerouteByNodeId = toolkit.keyBy(reroutes, "nodeId");
    this.rerouteById = toolkit.keyBy(reroutes, "id");
    this.linkExtensions = [];
    this.validReroutes = /* @__PURE__ */ new Set();
  }
  nodeById;
  linkById;
  rerouteById;
  rerouteByNodeId;
  linkExtensions;
  /** Reroutes that has at least a valid link pass through it */
  validReroutes;
  #rerouteIdCounter = 0;
  /**
   * Gets the chain of reroute nodes leading to the given node
   */
  #getRerouteChain(node2) {
    const nodes = [];
    let currentNode = node2;
    while (currentNode?.type === "Reroute") {
      nodes.push(currentNode);
      const inputLink = this.linkById[currentNode.inputs?.[0]?.link ?? 0];
      if (!inputLink) {
        break;
      }
      currentNode = this.nodeById[inputLink.origin_id];
    }
    return nodes;
  }
  #connectRerouteChain(rerouteNodes) {
    const reroutes = rerouteNodes.map((node2) => this.rerouteByNodeId[node2.id]);
    for (const reroute of reroutes) {
      this.validReroutes.add(reroute);
    }
    for (let i = 0; i < rerouteNodes.length - 1; i++) {
      const to = reroutes[i];
      const from = reroutes[i + 1];
      to.parentId = from.id;
    }
    return reroutes;
  }
  #createNewLink(startingLink, endingLink, rerouteNodes) {
    if (rerouteNodes.length === 0) {
      throw new Error("No reroute nodes found");
    }
    const reroute = this.rerouteByNodeId[rerouteNodes[0].id];
    this.linkExtensions.push({
      id: endingLink.id,
      parentId: reroute.id
    });
    const reroutes = this.#connectRerouteChain(rerouteNodes);
    for (const reroute2 of reroutes) {
      reroute2.linkIds ??= [];
      reroute2.linkIds.push(endingLink.id);
      delete reroute2.floating;
    }
    return {
      id: endingLink.id,
      origin_id: startingLink.origin_id,
      origin_slot: startingLink.origin_slot,
      target_id: endingLink.target_id,
      target_slot: endingLink.target_slot,
      type: endingLink.type
    };
  }
  #createNewInputFloatingLink(endingLink, rerouteNodes) {
    const reroutes = this.#connectRerouteChain(rerouteNodes);
    for (const reroute of reroutes) {
      if (!reroute.linkIds?.length) {
        reroute.floating = {
          slotType: "input"
        };
      }
    }
    return {
      id: this.#rerouteIdCounter++,
      origin_id: -1,
      origin_slot: -1,
      target_id: endingLink.target_id,
      target_slot: endingLink.target_slot,
      type: endingLink.type,
      parentId: reroutes[0].id
    };
  }
  #createNewOutputFloatingLink(startingLink, rerouteNodes) {
    const reroutes = this.#connectRerouteChain(rerouteNodes);
    for (const reroute of reroutes) {
      if (!reroute.linkIds?.length) {
        reroute.floating = {
          slotType: "output"
        };
      }
    }
    return {
      id: this.#rerouteIdCounter++,
      origin_id: startingLink.origin_id,
      origin_slot: startingLink.origin_slot,
      target_id: -1,
      target_slot: -1,
      type: startingLink.type,
      parentId: reroutes[0].id
    };
  }
  #reconnectLinks(nodes, links) {
    for (const node2 of nodes) {
      for (const input of node2.inputs ?? []) {
        input.link = null;
      }
      for (const output of node2.outputs ?? []) {
        output.links = [];
      }
    }
    const nodesById = toolkit.keyBy(nodes, "id");
    for (const link of links) {
      const sourceNode = nodesById[link.origin_id];
      sourceNode.outputs[link.origin_slot].links.push(link.id);
      const targetNode = nodesById[link.target_id];
      targetNode.inputs[link.target_slot].link = link.id;
    }
  }
  migrateReroutes() {
    const links = [];
    const floatingLinks = [];
    const endingLinks = [];
    for (const link of Object.values(this.linkById)) {
      const sourceIsReroute = !!this.rerouteByNodeId[link.origin_id];
      const targetIsReroute = !!this.rerouteByNodeId[link.target_id];
      if (!sourceIsReroute && !targetIsReroute) {
        links.push(link);
      } else if (sourceIsReroute && !targetIsReroute) {
        endingLinks.push(link);
      }
    }
    for (const endingLink of endingLinks) {
      const endingRerouteNode = this.nodeById[endingLink.origin_id];
      const rerouteNodes = this.#getRerouteChain(endingRerouteNode);
      const startingLink = this.linkById[rerouteNodes[rerouteNodes.length - 1]?.inputs?.[0]?.link ?? -1];
      if (startingLink) {
        links.push(this.#createNewLink(startingLink, endingLink, rerouteNodes));
      } else {
        floatingLinks.push(
          this.#createNewInputFloatingLink(endingLink, rerouteNodes)
        );
      }
    }
    const floatingEndingRerouteNodes = Object.keys(this.rerouteByNodeId).map((nodeId) => this.nodeById[nodeId]).filter((rerouteNode) => {
      const output = rerouteNode.outputs?.[0];
      if (!output) return false;
      return !output.links?.length;
    });
    for (const rerouteNode of floatingEndingRerouteNodes) {
      const rerouteNodes = this.#getRerouteChain(rerouteNode);
      const startingLink = this.linkById[rerouteNodes[rerouteNodes.length - 1]?.inputs?.[0]?.link ?? -1];
      if (startingLink) {
        floatingLinks.push(
          this.#createNewOutputFloatingLink(startingLink, rerouteNodes)
        );
      }
    }
    const nodes = Object.values(this.nodeById).filter(
      (node2) => node2.type !== "Reroute"
    );
    this.#reconnectLinks(nodes, links);
    return {
      ...this.workflow,
      nodes,
      links: links.map((link) => [
        link.id,
        link.origin_id,
        link.origin_slot,
        link.target_id,
        link.target_slot,
        link.type
      ]),
      floatingLinks: floatingLinks.length > 0 ? floatingLinks : void 0,
      extra: {
        ...this.workflow.extra,
        reroutes: Array.from(this.validReroutes).map(
          (reroute) => toolkit.omit(reroute, "nodeId")
        ),
        linkExtensions: this.linkExtensions
      }
    };
  }
}
const migrateLegacyRerouteNodes = /* @__PURE__ */ __name((workflow) => {
  const legacyRerouteNodes = findLegacyRerouteNodes(workflow);
  if (legacyRerouteNodes.length === 0) {
    return workflow;
  }
  const newWorkflow = JSON.parse(JSON.stringify(workflow));
  if (!newWorkflow.extra) {
    newWorkflow.extra = {};
  }
  const context = new ConversionContext(newWorkflow);
  return context.migrateReroutes();
}, "migrateLegacyRerouteNodes");
function getSelectedModelsMetadata(node2) {
  try {
    if (!node2.properties?.models?.length) return;
    if (!node2.widgets_values) return;
    const widgetValues = Array.isArray(node2.widgets_values) ? node2.widgets_values : Object.values(node2.widgets_values);
    if (!widgetValues.length) return;
    const stringWidgetValues = /* @__PURE__ */ new Set();
    for (const widgetValue of widgetValues) {
      if (typeof widgetValue === "string" && widgetValue.trim()) {
        stringWidgetValues.add(widgetValue);
      }
    }
    return node2.properties.models.filter(
      (model) => stringWidgetValues.has(model.name)
    );
  } catch (error) {
    console.error("Error filtering models by current selection:", error);
  }
}
__name(getSelectedModelsMetadata, "getSelectedModelsMetadata");
function serialise(nodes, graph) {
  const serialisable = {
    nodes: [],
    links: []
  };
  let index2 = 0;
  const cloneable = [];
  for (const node2 of nodes) {
    if (node2.clonable === false) continue;
    node2._relative_id = index2++;
    cloneable.push(node2);
  }
  for (const node2 of cloneable) {
    const cloned = node2.clone();
    if (!cloned) {
      console.warn("node type not found: " + node2.type);
      continue;
    }
    serialisable.nodes.push(cloned.serialize());
    if (!node2.inputs?.length) continue;
    for (const input of node2.inputs) {
      if (!input || input.link == null) continue;
      const link = graph.links.get(input.link);
      if (!link) continue;
      const outNode = graph.getNodeById(link.origin_id);
      if (!outNode) continue;
      serialisable.links.push([
        outNode._relative_id,
        link.origin_slot,
        node2._relative_id,
        link.target_slot,
        outNode.id
      ]);
    }
  }
  return JSON.stringify(serialisable);
}
__name(serialise, "serialise");
function deserialiseAndCreate(data, canvas2) {
  if (!data) return;
  const { graph, graph_mouse } = canvas2;
  canvas2.emitBeforeChange();
  try {
    graph.beforeChange();
    const deserialised = JSON.parse(data);
    const topLeft = [Infinity, Infinity];
    for (const { pos } of deserialised.nodes) {
      if (topLeft[0] > pos[0]) topLeft[0] = pos[0];
      if (topLeft[1] > pos[1]) topLeft[1] = pos[1];
    }
    if (!Number.isFinite(topLeft[0]) || !Number.isFinite(topLeft[1])) {
      topLeft[0] = graph_mouse[0];
      topLeft[1] = graph_mouse[1];
    }
    const nodes = [];
    for (const info of deserialised.nodes) {
      const node2 = LiteGraph.createNode(info.type);
      if (!node2) continue;
      node2.configure(info);
      node2.pos[0] += graph_mouse[0] - topLeft[0];
      node2.pos[1] += graph_mouse[1] - topLeft[1];
      graph.add(node2, true);
      nodes.push(node2);
    }
    for (const info of deserialised.links) {
      const relativeId = info[0];
      const outNode = relativeId != null ? nodes[relativeId] : void 0;
      const inNode = nodes[info[2]];
      if (outNode && inNode) outNode.connect(info[1], inNode, info[3]);
      else console.warn("Warning, nodes missing on pasting");
    }
    canvas2.selectNodes(nodes);
    graph.afterChange();
  } finally {
    canvas2.emitAfterChange();
  }
}
__name(deserialiseAndCreate, "deserialiseAndCreate");
function applyClasses(element, classList, ...requiredClasses) {
  classList ??= "";
  let str;
  if (typeof classList === "string") {
    str = classList;
  } else if (classList instanceof Array) {
    str = classList.join(" ");
  } else {
    str = Object.entries(classList).reduce((p, c2) => {
      if (c2[1]) {
        p += (p.length ? " " : "") + c2[0];
      }
      return p;
    }, "");
  }
  element.className = str;
  if (requiredClasses) {
    element.classList.add(...requiredClasses);
  }
}
__name(applyClasses, "applyClasses");
function toggleElement(element, {
  onHide,
  onShow
} = {}) {
  let placeholder;
  let hidden;
  return (value) => {
    if (value) {
      if (hidden) {
        hidden = false;
        placeholder.replaceWith(element);
      }
      onShow?.(element, value);
    } else {
      if (!placeholder) {
        placeholder = document.createComment("");
      }
      hidden = true;
      element.replaceWith(placeholder);
      onHide?.(element);
    }
  };
}
__name(toggleElement, "toggleElement");
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.utils = window.comfyAPI.utils || {};
window.comfyAPI.utils.applyClasses = applyClasses;
window.comfyAPI.utils.toggleElement = toggleElement;
class ComfyButton {
  static {
    __name(this, "ComfyButton");
  }
  #over = 0;
  #popupOpen = false;
  isOver = false;
  iconElement = $el("i.mdi");
  contentElement = $el("span");
  // @ts-expect-error fixme ts strict error
  popup;
  element;
  overIcon;
  iconSize;
  content;
  icon;
  tooltip;
  classList;
  hidden;
  enabled;
  action;
  constructor({
    icon: icon2,
    overIcon,
    iconSize,
    content,
    tooltip,
    action,
    classList = "comfyui-button",
    visibilitySetting,
    app: app2,
    enabled = true
  }) {
    this.element = $el(
      "button",
      {
        onmouseenter: /* @__PURE__ */ __name(() => {
          this.isOver = true;
          if (this.overIcon) {
            this.updateIcon();
          }
        }, "onmouseenter"),
        onmouseleave: /* @__PURE__ */ __name(() => {
          this.isOver = false;
          if (this.overIcon) {
            this.updateIcon();
          }
        }, "onmouseleave")
      },
      [this.iconElement, this.contentElement]
    );
    this.icon = prop(
      this,
      "icon",
      icon2,
      toggleElement(this.iconElement, { onShow: this.updateIcon })
    );
    this.overIcon = prop(this, "overIcon", overIcon, () => {
      if (this.isOver) {
        this.updateIcon();
      }
    });
    this.iconSize = prop(this, "iconSize", iconSize, this.updateIcon);
    this.content = prop(
      this,
      "content",
      content,
      toggleElement(this.contentElement, {
        onShow: /* @__PURE__ */ __name((el, v2) => {
          if (typeof v2 === "string") {
            el.textContent = v2;
          } else {
            el.replaceChildren(v2);
          }
        }, "onShow")
      })
    );
    this.tooltip = prop(this, "tooltip", tooltip, (v2) => {
      if (v2) {
        this.element.title = v2;
      } else {
        this.element.removeAttribute("title");
      }
    });
    if (tooltip !== void 0) {
      this.element.setAttribute("aria-label", tooltip);
    }
    this.classList = prop(this, "classList", classList, this.updateClasses);
    this.hidden = prop(this, "hidden", false, this.updateClasses);
    this.enabled = prop(this, "enabled", enabled, () => {
      this.updateClasses();
      this.element.disabled = !this.enabled;
    });
    this.action = prop(this, "action", action);
    this.element.addEventListener("click", (e2) => {
      if (this.popup) {
        if (!this.#over) {
          this.popup.toggle();
        }
      }
      this.action?.(e2, this);
    });
    if (visibilitySetting?.id) {
      const settingUpdated = /* @__PURE__ */ __name(() => {
        this.hidden = // @ts-expect-error fixme ts strict error
        app2.ui.settings.getSettingValue(visibilitySetting.id) !== visibilitySetting.showValue;
      }, "settingUpdated");
      app2.ui.settings.addEventListener(
        visibilitySetting.id + ".change",
        settingUpdated
      );
      settingUpdated();
    }
  }
  updateIcon = /* @__PURE__ */ __name(() => this.iconElement.className = `mdi mdi-${this.isOver && this.overIcon || this.icon}${this.iconSize ? " mdi-" + this.iconSize + "px" : ""}`, "updateIcon");
  updateClasses = /* @__PURE__ */ __name(() => {
    const internalClasses = [];
    if (this.hidden) {
      internalClasses.push("hidden");
    }
    if (!this.enabled) {
      internalClasses.push("disabled");
    }
    if (this.popup) {
      if (this.#popupOpen) {
        internalClasses.push("popup-open");
      } else {
        internalClasses.push("popup-closed");
      }
    }
    applyClasses(this.element, this.classList, ...internalClasses);
  }, "updateClasses");
  withPopup(popup, mode = "click") {
    this.popup = popup;
    if (mode === "hover") {
      for (const el of [this.element, this.popup.element]) {
        el.addEventListener("mouseenter", () => {
          this.popup.open = !!++this.#over;
        });
        el.addEventListener("mouseleave", () => {
          this.popup.open = !!--this.#over;
        });
      }
    }
    popup.addEventListener("change", () => {
      this.#popupOpen = popup.open;
      this.updateClasses();
    });
    return this;
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.button = window.comfyAPI.button || {};
window.comfyAPI.button.ComfyButton = ComfyButton;
class ComfyButtonGroup {
  static {
    __name(this, "ComfyButtonGroup");
  }
  element = $el("div.comfyui-button-group");
  buttons;
  constructor(...buttons) {
    this.buttons = prop(this, "buttons", buttons, () => this.update());
  }
  insert(button, index2) {
    this.buttons.splice(index2, 0, button);
    this.update();
  }
  append(button) {
    this.buttons.push(button);
    this.update();
  }
  remove(indexOrButton) {
    if (typeof indexOrButton !== "number") {
      indexOrButton = this.buttons.indexOf(indexOrButton);
    }
    if (indexOrButton > -1) {
      const r = this.buttons.splice(indexOrButton, 1);
      this.update();
      return r;
    }
  }
  update() {
    this.element.replaceChildren(...this.buttons.map((b) => b["element"] ?? b));
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.buttonGroup = window.comfyAPI.buttonGroup || {};
window.comfyAPI.buttonGroup.ComfyButtonGroup = ComfyButtonGroup;
class ComfyPopup extends EventTarget {
  static {
    __name(this, "ComfyPopup");
  }
  element = $el("div.comfyui-popup");
  open;
  children;
  target;
  ignoreTarget;
  container;
  position;
  closeOnEscape;
  horizontal;
  classList;
  constructor({
    target,
    container = document.body,
    classList = "",
    ignoreTarget = true,
    closeOnEscape = true,
    position = "absolute",
    horizontal = "left"
  }, ...children) {
    super();
    this.target = target;
    this.ignoreTarget = ignoreTarget;
    this.container = container;
    this.position = position;
    this.closeOnEscape = closeOnEscape;
    this.horizontal = horizontal;
    container.append(this.element);
    this.children = prop(this, "children", children, () => {
      this.element.replaceChildren(...this.children);
      this.update();
    });
    this.classList = prop(
      this,
      "classList",
      classList,
      () => applyClasses(this.element, this.classList, "comfyui-popup", horizontal)
    );
    this.open = prop(this, "open", false, (v2, o) => {
      if (v2 === o) return;
      if (v2) {
        this.#show();
      } else {
        this.#hide();
      }
    });
  }
  toggle() {
    this.open = !this.open;
  }
  #hide() {
    this.element.classList.remove("open");
    window.removeEventListener("resize", this.update);
    window.removeEventListener("click", this.#clickHandler, { capture: true });
    window.removeEventListener("keydown", this.#escHandler, { capture: true });
    this.dispatchEvent(new CustomEvent("close"));
    this.dispatchEvent(new CustomEvent("change"));
  }
  #show() {
    this.element.classList.add("open");
    this.update();
    window.addEventListener("resize", this.update);
    window.addEventListener("click", this.#clickHandler, { capture: true });
    if (this.closeOnEscape) {
      window.addEventListener("keydown", this.#escHandler, { capture: true });
    }
    this.dispatchEvent(new CustomEvent("open"));
    this.dispatchEvent(new CustomEvent("change"));
  }
  // @ts-expect-error fixme ts strict error
  #escHandler = /* @__PURE__ */ __name((e2) => {
    if (e2.key === "Escape") {
      this.open = false;
      e2.preventDefault();
      e2.stopImmediatePropagation();
    }
  }, "#escHandler");
  // @ts-expect-error fixme ts strict error
  #clickHandler = /* @__PURE__ */ __name((e2) => {
    const target = e2.target;
    if (!this.element.contains(target) && this.ignoreTarget && !this.target.contains(target)) {
      this.open = false;
    }
  }, "#clickHandler");
  update = /* @__PURE__ */ __name(() => {
    const rect = this.target.getBoundingClientRect();
    this.element.style.setProperty("--bottom", "unset");
    if (this.position === "absolute") {
      if (this.horizontal === "left") {
        this.element.style.setProperty("--left", rect.left + "px");
      } else {
        this.element.style.setProperty(
          "--left",
          rect.right - this.element.clientWidth + "px"
        );
      }
      this.element.style.setProperty("--top", rect.bottom + "px");
      this.element.style.setProperty("--limit", rect.bottom + "px");
    } else {
      this.element.style.setProperty("--left", "0px");
      this.element.style.setProperty("--top", rect.height + "px");
      this.element.style.setProperty("--limit", rect.height + "px");
    }
    const thisRect = this.element.getBoundingClientRect();
    if (thisRect.height < 30) {
      this.element.style.setProperty("--top", "unset");
      this.element.style.setProperty("--bottom", rect.height + 5 + "px");
      this.element.style.setProperty("--limit", rect.height + 5 + "px");
    }
  }, "update");
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.popup = window.comfyAPI.popup || {};
window.comfyAPI.popup.ComfyPopup = ComfyPopup;
class ComfySplitButton {
  static {
    __name(this, "ComfySplitButton");
  }
  arrow;
  element;
  popup;
  items;
  constructor({
    primary,
    mode,
    horizontal = "left",
    position = "relative"
  }, ...items) {
    this.arrow = new ComfyButton({
      icon: "chevron-down"
    });
    this.element = $el(
      "div.comfyui-split-button" + (mode === "hover" ? ".hover" : ""),
      [
        $el(
          "div.comfyui-split-primary",
          {
            ariaLabel: "Queue current workflow"
          },
          primary.element
        ),
        $el(
          "div.comfyui-split-arrow",
          {
            ariaLabel: "Open extra opens",
            ariaHasPopup: "true"
          },
          this.arrow.element
        )
      ]
    );
    this.popup = new ComfyPopup({
      target: this.element,
      container: position === "relative" ? this.element : document.body,
      classList: "comfyui-split-button-popup" + (mode === "hover" ? " hover" : ""),
      closeOnEscape: mode === "click",
      position,
      horizontal
    });
    this.arrow.withPopup(this.popup, mode);
    this.items = prop(this, "items", items, () => this.update());
  }
  update() {
    this.popup.element.replaceChildren(
      ...this.items.map((b) => "element" in b ? b.element : b)
    );
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.splitButton = window.comfyAPI.splitButton || {};
window.comfyAPI.splitButton.ComfySplitButton = ComfySplitButton;
class ComfyAsyncDialog extends ComfyDialog$1 {
  static {
    __name(this, "ComfyAsyncDialog");
  }
  // @ts-expect-error fixme ts strict error
  #resolve;
  constructor(actions) {
    super(
      "dialog.comfy-dialog.comfyui-dialog",
      // @ts-expect-error fixme ts strict error
      actions?.map((opt) => {
        if (typeof opt === "string") {
          opt = { text: opt };
        }
        return $el("button.comfyui-button", {
          type: "button",
          textContent: opt.text,
          onclick: /* @__PURE__ */ __name(() => this.close(opt.value ?? opt.text), "onclick")
        });
      })
    );
  }
  show(html) {
    this.element.addEventListener("close", () => {
      this.close();
    });
    super.show(html);
    return new Promise((resolve) => {
      this.#resolve = resolve;
    });
  }
  showModal(html) {
    this.element.addEventListener("close", () => {
      this.close();
    });
    super.show(html);
    this.element.showModal();
    return new Promise((resolve) => {
      this.#resolve = resolve;
    });
  }
  close(result = null) {
    this.#resolve(result);
    this.element.close();
    super.close();
  }
  static async prompt({
    title = null,
    message,
    actions
  }) {
    const dialog = new ComfyAsyncDialog(actions);
    const content = [$el("span", message)];
    if (title) {
      content.unshift($el("h3", title));
    }
    const res = await dialog.showModal(content);
    dialog.element.remove();
    return res;
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.asyncDialog = window.comfyAPI.asyncDialog || {};
window.comfyAPI.asyncDialog.ComfyAsyncDialog = ComfyAsyncDialog;
const styleElement = document.createElement("style");
styleElement.textContent = `
        .draggable-item {
            position: relative;
            will-change: transform;
            user-select: none;
        }
        .draggable-item.is-idle {
            transition: 0.25s ease transform;
        }
        .draggable-item.is-draggable {
            z-index: 10;
        }
    `;
document.head.append(styleElement);
class DraggableList extends EventTarget {
  static {
    __name(this, "DraggableList");
  }
  listContainer;
  // @ts-expect-error fixme ts strict error
  draggableItem;
  // @ts-expect-error fixme ts strict error
  pointerStartX;
  // @ts-expect-error fixme ts strict error
  pointerStartY;
  // @ts-expect-error fixme ts strict error
  scrollYMax;
  itemsGap = 0;
  items = [];
  itemSelector;
  handleClass = "drag-handle";
  off = [];
  offDrag = [];
  // @ts-expect-error fixme ts strict error
  constructor(element, itemSelector) {
    super();
    this.listContainer = element;
    this.itemSelector = itemSelector;
    if (!this.listContainer) return;
    this.off.push(this.on(this.listContainer, "mousedown", this.dragStart));
    this.off.push(this.on(this.listContainer, "touchstart", this.dragStart));
    this.off.push(this.on(document, "mouseup", this.dragEnd));
    this.off.push(this.on(document, "touchend", this.dragEnd));
  }
  getAllItems() {
    if (!this.items?.length) {
      this.items = Array.from(
        this.listContainer.querySelectorAll(this.itemSelector)
      );
      this.items.forEach((element) => {
        element.classList.add("is-idle");
      });
    }
    return this.items;
  }
  getIdleItems() {
    return this.getAllItems().filter(
      (item) => (
        // @ts-expect-error fixme ts strict error
        item.classList.contains("is-idle")
      )
    );
  }
  // @ts-expect-error fixme ts strict error
  isItemAbove(item) {
    return item.hasAttribute("data-is-above");
  }
  // @ts-expect-error fixme ts strict error
  isItemToggled(item) {
    return item.hasAttribute("data-is-toggled");
  }
  // @ts-expect-error fixme ts strict error
  on(source, event, listener, options) {
    listener = listener.bind(this);
    source.addEventListener(event, listener, options);
    return () => source.removeEventListener(event, listener);
  }
  // @ts-expect-error fixme ts strict error
  dragStart(e2) {
    if (e2.target.classList.contains(this.handleClass)) {
      this.draggableItem = e2.target.closest(this.itemSelector);
    }
    if (!this.draggableItem) return;
    this.pointerStartX = e2.clientX || e2.touches[0].clientX;
    this.pointerStartY = e2.clientY || e2.touches[0].clientY;
    this.scrollYMax = this.listContainer.scrollHeight - this.listContainer.clientHeight;
    this.setItemsGap();
    this.initDraggableItem();
    this.initItemsState();
    this.offDrag.push(this.on(document, "mousemove", this.drag));
    this.offDrag.push(
      // @ts-expect-error fixme ts strict error
      this.on(document, "touchmove", this.drag, { passive: false })
    );
    this.dispatchEvent(
      new CustomEvent("dragstart", {
        detail: {
          element: this.draggableItem,
          // @ts-expect-error fixme ts strict error
          position: this.getAllItems().indexOf(this.draggableItem)
        }
      })
    );
  }
  setItemsGap() {
    if (this.getIdleItems().length <= 1) {
      this.itemsGap = 0;
      return;
    }
    const item1 = this.getIdleItems()[0];
    const item2 = this.getIdleItems()[1];
    const item1Rect = item1.getBoundingClientRect();
    const item2Rect = item2.getBoundingClientRect();
    this.itemsGap = Math.abs(item1Rect.bottom - item2Rect.top);
  }
  initItemsState() {
    this.getIdleItems().forEach((item, i) => {
      if (this.getAllItems().indexOf(this.draggableItem) > i) {
        item.dataset.isAbove = "";
      }
    });
  }
  initDraggableItem() {
    this.draggableItem.classList.remove("is-idle");
    this.draggableItem.classList.add("is-draggable");
  }
  // @ts-expect-error fixme ts strict error
  drag(e2) {
    if (!this.draggableItem) return;
    e2.preventDefault();
    const clientX = e2.clientX || e2.touches[0].clientX;
    const clientY = e2.clientY || e2.touches[0].clientY;
    const listRect = this.listContainer.getBoundingClientRect();
    if (clientY > listRect.bottom) {
      if (this.listContainer.scrollTop < this.scrollYMax) {
        this.listContainer.scrollBy(0, 10);
        this.pointerStartY -= 10;
      }
    } else if (clientY < listRect.top && this.listContainer.scrollTop > 0) {
      this.pointerStartY += 10;
      this.listContainer.scrollBy(0, -10);
    }
    const pointerOffsetX = clientX - this.pointerStartX;
    const pointerOffsetY = clientY - this.pointerStartY;
    this.updateIdleItemsStateAndPosition();
    this.draggableItem.style.transform = `translate(${pointerOffsetX}px, ${pointerOffsetY}px)`;
  }
  updateIdleItemsStateAndPosition() {
    const draggableItemRect = this.draggableItem.getBoundingClientRect();
    const draggableItemY = draggableItemRect.top + draggableItemRect.height / 2;
    this.getIdleItems().forEach((item) => {
      const itemRect = item.getBoundingClientRect();
      const itemY = itemRect.top + itemRect.height / 2;
      if (this.isItemAbove(item)) {
        if (draggableItemY <= itemY) {
          item.dataset.isToggled = "";
        } else {
          delete item.dataset.isToggled;
        }
      } else {
        if (draggableItemY >= itemY) {
          item.dataset.isToggled = "";
        } else {
          delete item.dataset.isToggled;
        }
      }
    });
    this.getIdleItems().forEach((item) => {
      if (this.isItemToggled(item)) {
        const direction = this.isItemAbove(item) ? 1 : -1;
        item.style.transform = `translateY(${direction * (draggableItemRect.height + this.itemsGap)}px)`;
      } else {
        item.style.transform = "";
      }
    });
  }
  dragEnd() {
    if (!this.draggableItem) return;
    this.applyNewItemsOrder();
    this.cleanup();
  }
  applyNewItemsOrder() {
    const reorderedItems = [];
    let oldPosition = -1;
    this.getAllItems().forEach((item, index2) => {
      if (item === this.draggableItem) {
        oldPosition = index2;
        return;
      }
      if (!this.isItemToggled(item)) {
        reorderedItems[index2] = item;
        return;
      }
      const newIndex = this.isItemAbove(item) ? index2 + 1 : index2 - 1;
      reorderedItems[newIndex] = item;
    });
    for (let index2 = 0; index2 < this.getAllItems().length; index2++) {
      const item = reorderedItems[index2];
      if (typeof item === "undefined") {
        reorderedItems[index2] = this.draggableItem;
      }
    }
    reorderedItems.forEach((item) => {
      this.listContainer.appendChild(item);
    });
    this.items = reorderedItems;
    this.dispatchEvent(
      new CustomEvent("dragend", {
        detail: {
          element: this.draggableItem,
          oldPosition,
          newPosition: reorderedItems.indexOf(this.draggableItem)
        }
      })
    );
  }
  cleanup() {
    this.itemsGap = 0;
    this.items = [];
    this.unsetDraggableItem();
    this.unsetItemState();
    this.offDrag.forEach((f) => f());
    this.offDrag = [];
  }
  unsetDraggableItem() {
    this.draggableItem.style = null;
    this.draggableItem.classList.remove("is-draggable");
    this.draggableItem.classList.add("is-idle");
    this.draggableItem = null;
  }
  unsetItemState() {
    this.getIdleItems().forEach((item) => {
      delete item.dataset.isAbove;
      delete item.dataset.isToggled;
      item.style.transform = "";
    });
  }
  dispose() {
    this.off.forEach((f) => f());
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.draggableList = window.comfyAPI.draggableList || {};
window.comfyAPI.draggableList.DraggableList = DraggableList;
class ComfyAppMenu {
  static {
    __name(this, "ComfyAppMenu");
  }
  app;
  actionsGroup;
  settingsGroup;
  viewGroup;
  element;
  constructor(app2) {
    this.app = app2;
    this.actionsGroup = new ComfyButtonGroup();
    this.settingsGroup = new ComfyButtonGroup();
    this.viewGroup = new ComfyButtonGroup();
    this.element = $el("div.flex.gap-2.mx-2", [
      this.actionsGroup.element,
      this.settingsGroup.element,
      this.viewGroup.element
    ]);
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.index = window.comfyAPI.index || {};
window.comfyAPI.index.ComfyAppMenu = ComfyAppMenu;
const SCALE_FACTOR = 1.2;
function ensureCorrectLayoutScale(renderer = "LG", targetGraph) {
  const autoScaleLayoutSetting = useSettingStore().get(
    "Comfy.VueNodes.AutoScaleLayout"
  );
  if (!autoScaleLayoutSetting) return;
  const canvas2 = app$1.canvas;
  const graph = targetGraph ?? canvas2?.graph;
  if (!graph?.nodes) return;
  const { shouldRenderVueNodes } = useVueFeatureFlags();
  const needsUpscale = renderer === "LG" && shouldRenderVueNodes.value;
  const needsDownscale = renderer === "Vue" && !shouldRenderVueNodes.value;
  if (!needsUpscale && !needsDownscale) {
    graph.extra.workflowRendererVersion ??= renderer;
    return;
  }
  const lgBounds = createBounds(graph.nodes);
  if (!lgBounds) return;
  const [originX, originY] = lgBounds;
  const lgNodesById = new Map(graph.nodes.map((node2) => [node2.id, node2]));
  const yjsMoveNodeUpdates = [];
  const scaleFactor2 = needsUpscale ? SCALE_FACTOR : 1 / SCALE_FACTOR;
  const onActiveGraph = !targetGraph || targetGraph === canvas2?.graph;
  for (const node2 of graph.nodes) {
    const lgNode = lgNodesById.get(node2.id);
    if (!lgNode) continue;
    const [oldX, oldY] = lgNode.pos;
    const relativeX = oldX - originX;
    const relativeY = oldY - originY;
    const scaledX = originX + relativeX * scaleFactor2;
    const scaledY = originY + relativeY * scaleFactor2;
    const scaledWidth = lgNode.width * scaleFactor2;
    const scaledHeight = needsUpscale ? lgNode.size[1] * scaleFactor2 + LiteGraph.NODE_TITLE_HEIGHT : (lgNode.size[1] - LiteGraph.NODE_TITLE_HEIGHT) * scaleFactor2;
    lgNode.pos[0] = scaledX;
    lgNode.pos[1] = scaledY;
    lgNode.size[0] = scaledWidth;
    lgNode.size[1] = scaledHeight;
    if (onActiveGraph) {
      yjsMoveNodeUpdates.push({
        nodeId: String(lgNode.id),
        bounds: {
          x: scaledX,
          y: scaledY,
          width: scaledWidth,
          height: scaledHeight
        }
      });
    }
  }
  if (onActiveGraph && yjsMoveNodeUpdates.length > 0) {
    layoutStore.batchUpdateNodeBounds(yjsMoveNodeUpdates);
  }
  for (const reroute of graph.reroutes.values()) {
    const [oldX, oldY] = reroute.pos;
    const relativeX = oldX - originX;
    const relativeY = oldY - originY;
    const scaledX = originX + relativeX * scaleFactor2;
    const scaledY = originY + relativeY * scaleFactor2;
    reroute.pos = [scaledX, scaledY];
    if (onActiveGraph && shouldRenderVueNodes.value) {
      const layoutMutations2 = useLayoutMutations();
      layoutMutations2.moveReroute(
        reroute.id,
        { x: scaledX, y: scaledY },
        { x: oldX, y: oldY }
      );
    }
  }
  if ("inputNode" in graph && "outputNode" in graph) {
    const ioNodes = [
      graph.inputNode,
      graph.outputNode
    ];
    for (const ioNode of ioNodes) {
      const [oldX, oldY] = ioNode.pos;
      const [oldWidth, oldHeight] = ioNode.size;
      const relativeX = oldX - originX;
      const relativeY = oldY - originY;
      const scaledX = originX + relativeX * scaleFactor2;
      const scaledY = originY + relativeY * scaleFactor2;
      const scaledWidth = oldWidth * scaleFactor2;
      const scaledHeight = oldHeight * scaleFactor2;
      ioNode.pos = [scaledX, scaledY];
      ioNode.size = [scaledWidth, scaledHeight];
    }
  }
  graph.groups.forEach((group) => {
    const [oldX, oldY] = group.pos;
    const [oldWidth, oldHeight] = group.size;
    const relativeX = oldX - originX;
    const relativeY = oldY - originY;
    const scaledX = originX + relativeX * scaleFactor2;
    const scaledY = originY + relativeY * scaleFactor2;
    const scaledWidth = oldWidth * scaleFactor2;
    const scaledHeight = oldHeight * scaleFactor2;
    group.pos = [scaledX, scaledY];
    group.size = [scaledWidth, scaledHeight];
  });
  if (onActiveGraph && canvas2) {
    const originScreen = canvas2.ds.convertOffsetToCanvas([originX, originY]);
    canvas2.ds.changeScale(canvas2.ds.scale / scaleFactor2, originScreen);
  }
  graph.extra.workflowRendererVersion = needsUpscale ? "Vue" : "LG";
}
__name(ensureCorrectLayoutScale, "ensureCorrectLayoutScale");
async function extractFileFromDragEvent(event) {
  if (!event.dataTransfer) return;
  if (event.dataTransfer.files.length && event.dataTransfer.files[0].type !== "image/bmp") {
    return event.dataTransfer.files[0];
  }
  const validTypes = ["text/uri-list", "text/x-moz-url"];
  const match = [...event.dataTransfer.types].find(
    (t2) => validTypes.includes(t2)
  );
  if (!match) return;
  const uri = event.dataTransfer.getData(match)?.split("\n")?.[0];
  if (!uri) return;
  const response = await fetch(uri);
  const blob = await response.blob();
  return new File([blob], uri, { type: blob.type });
}
__name(extractFileFromDragEvent, "extractFileFromDragEvent");
const ANIM_PREVIEW_WIDGET = "$$comfy_animation_preview";
function sanitizeNodeName(string) {
  let entityMap = {
    "&": "",
    "<": "",
    ">": "",
    '"': "",
    "'": "",
    "`": "",
    "=": ""
  };
  return String(string).replace(/[&<>"'`=]/g, /* @__PURE__ */ __name(function fromEntityMap(s) {
    return entityMap[s];
  }, "fromEntityMap"));
}
__name(sanitizeNodeName, "sanitizeNodeName");
class ComfyApp {
  static {
    __name(this, "ComfyApp");
  }
  /**
   * List of entries to queue
   */
  queueItems = [];
  /**
   * If the queue is currently being processed
   */
  processingQueue = false;
  /**
   * Content Clipboard
   * @type {serialized node object}
   */
  static clipspace = null;
  static clipspace_invalidate_handler = null;
  static open_maskeditor = null;
  static maskeditor_is_opended = null;
  static clipspace_return_node = null;
  vueAppReady;
  api;
  ui;
  // @ts-expect-error fixme ts strict error
  extensionManager;
  // @ts-expect-error fixme ts strict error
  _nodeOutputs;
  nodePreviewImages;
  rootGraphInternal;
  // TODO: Migrate internal usage to the
  /** @deprecated Use {@link rootGraph} instead */
  get graph() {
    return this.rootGraphInternal;
  }
  get rootGraph() {
    if (!this.rootGraphInternal) {
      console.error("ComfyApp graph accessed before initialization");
    }
    return this.rootGraphInternal;
  }
  // @ts-expect-error fixme ts strict error
  canvas;
  dragOverNode = null;
  canvasElRef = shallowRef();
  get canvasEl() {
    return unref(this.canvasElRef);
  }
  configuringGraphLevel = 0;
  get configuringGraph() {
    return this.configuringGraphLevel > 0;
  }
  // @ts-expect-error fixme ts strict error
  ctx;
  bodyTop;
  bodyLeft;
  bodyRight;
  bodyBottom;
  canvasContainer;
  menu;
  // Set by Comfy.Clipspace extension
  openClipspace = /* @__PURE__ */ __name(() => {
  }, "openClipspace");
  positionConversion;
  /**
   * The node errors from the previous execution.
   * @deprecated Use useExecutionStore().lastNodeErrors instead
   */
  get lastNodeErrors() {
    return useExecutionStore().lastNodeErrors;
  }
  /**
   * The error from the previous execution.
   * @deprecated Use useExecutionStore().lastExecutionError instead
   */
  get lastExecutionError() {
    return useExecutionStore().lastExecutionError;
  }
  /**
   * @deprecated Use useExecutionStore().executingNodeId instead
   * TODO: Update to support multiple executing nodes. This getter returns only the first executing node.
   * Consider updating consumers to handle multiple nodes or use executingNodeIds array.
   */
  get runningNodeId() {
    return useExecutionStore().executingNodeId;
  }
  /**
   * @deprecated Use useWorkspaceStore().shiftDown instead
   */
  get shiftDown() {
    return useWorkspaceStore().shiftDown;
  }
  /**
   * @deprecated Use useWidgetStore().widgets instead
   */
  get widgets() {
    return Object.fromEntries(useWidgetStore().widgets.entries());
  }
  /**
   * @deprecated storageLocation is always 'server' since
   * https://github.com/comfyanonymous/ComfyUI/commit/53c8a99e6c00b5e20425100f6680cd9ea2652218
   */
  get storageLocation() {
    return "server";
  }
  /**
   * @deprecated storage migration is no longer needed.
   */
  get isNewUserSession() {
    return false;
  }
  /**
   * @deprecated Use useExtensionStore().extensions instead
   */
  get extensions() {
    return useExtensionStore().extensions;
  }
  /**
   * The progress on the current executing node, if the node reports any.
   * @deprecated Use useExecutionStore().executingNodeProgress instead
   */
  get progress() {
    return useExecutionStore()._executingNodeProgress;
  }
  /**
   * @deprecated Use {@link isImageNode} from @/utils/litegraphUtil instead
   */
  static isImageNode(node2) {
    return isImageNode(node2);
  }
  /**
   * Resets the canvas view to the default
   * @deprecated Use {@link useLitegraphService().resetView} instead
   */
  resetView() {
    useLitegraphService().resetView();
  }
  constructor() {
    this.vueAppReady = false;
    this.ui = new ComfyUI(this);
    this.api = api;
    this.bodyTop = $el("div.comfyui-body-top");
    this.bodyLeft = $el("div.comfyui-body-left");
    this.bodyRight = $el("div.comfyui-body-right");
    this.bodyBottom = $el("div.comfyui-body-bottom");
    this.canvasContainer = $el("div.graph-canvas-container");
    this.menu = new ComfyAppMenu(this);
    this.nodeOutputs = {};
    this.nodePreviewImages = {};
  }
  get nodeOutputs() {
    return this._nodeOutputs;
  }
  set nodeOutputs(value) {
    this._nodeOutputs = value;
    if (this.vueAppReady)
      useExtensionService().invokeExtensions("onNodeOutputsUpdated", value);
  }
  /**
   * If the user has specified a preferred format to receive preview images in,
   * this function will return that format as a url query param.
   * If the node's outputs are not images, this param should not be used, as it will
   * force the server to load the output file as an image.
   */
  getPreviewFormatParam() {
    let preview_format = useSettingStore().get("Comfy.PreviewFormat");
    if (preview_format) return `&preview=${preview_format}`;
    else return "";
  }
  getRandParam() {
    return "&rand=" + Math.random();
  }
  static onClipspaceEditorSave() {
    if (ComfyApp.clipspace_return_node) {
      ComfyApp.pasteFromClipspace(ComfyApp.clipspace_return_node);
    }
  }
  static onClipspaceEditorClosed() {
    ComfyApp.clipspace_return_node = null;
  }
  static copyToClipspace(node2) {
    var widgets2 = null;
    if (node2.widgets) {
      widgets2 = node2.widgets.map(({ type, name: name2, value }) => ({
        type,
        name: name2,
        value
      }));
    }
    var imgs = void 0;
    var orig_imgs = void 0;
    if (node2.imgs != void 0) {
      imgs = [];
      orig_imgs = [];
      for (let i = 0; i < node2.imgs.length; i++) {
        imgs[i] = new Image();
        imgs[i].src = node2.imgs[i].src;
        orig_imgs[i] = imgs[i];
      }
    }
    var selectedIndex = 0;
    if (node2.imageIndex) {
      selectedIndex = node2.imageIndex;
    }
    const paintedIndex = imgs ? imgs.length + 1 : 1;
    const combinedIndex = imgs ? imgs.length + 2 : 2;
    const images = node2.images ?? useNodeOutputStore().getNodeOutputs(node2)?.images;
    ComfyApp.clipspace = {
      widgets: widgets2,
      imgs,
      original_imgs: orig_imgs,
      images,
      selectedIndex,
      img_paste_mode: "selected",
      // reset to default im_paste_mode state on copy action
      paintedIndex,
      combinedIndex
    };
    ComfyApp.clipspace_return_node = null;
    if (ComfyApp.clipspace_invalidate_handler) {
      ComfyApp.clipspace_invalidate_handler();
    }
  }
  static pasteFromClipspace(node2) {
    if (ComfyApp.clipspace) {
      let combinedImgSrc;
      if (ComfyApp.clipspace.combinedIndex !== void 0 && ComfyApp.clipspace.imgs && ComfyApp.clipspace.combinedIndex < ComfyApp.clipspace.imgs.length) {
        combinedImgSrc = ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex].src;
      }
      if (ComfyApp.clipspace.imgs && node2.imgs) {
        if (ComfyApp.clipspace.images) {
          if (ComfyApp.clipspace["img_paste_mode"] == "selected") {
            node2.images = [
              ComfyApp.clipspace.images[ComfyApp.clipspace["selectedIndex"]]
            ];
          } else {
            node2.images = ComfyApp.clipspace.images;
          }
          if (app$1.nodeOutputs[node2.id + ""])
            app$1.nodeOutputs[node2.id + ""].images = node2.images;
        }
        if (ComfyApp.clipspace.imgs) {
          if (ComfyApp.clipspace["img_paste_mode"] == "selected") {
            const img = new Image();
            img.src = ComfyApp.clipspace.imgs[ComfyApp.clipspace["selectedIndex"]].src;
            node2.imgs = [img];
            node2.imageIndex = 0;
          } else {
            const imgs = [];
            for (let i = 0; i < ComfyApp.clipspace.imgs.length; i++) {
              imgs[i] = new Image();
              imgs[i].src = ComfyApp.clipspace.imgs[i].src;
              node2.imgs = imgs;
            }
          }
        }
      }
      if (ComfyApp.clipspace.imgs?.[ComfyApp.clipspace.paintedIndex] && node2.imgs) {
        const paintedImg = new Image();
        paintedImg.src = ComfyApp.clipspace.imgs[ComfyApp.clipspace.paintedIndex].src;
        node2.imgs.push(paintedImg);
      }
      if (ComfyApp.clipspace.imgs?.[ComfyApp.clipspace.combinedIndex] && node2.imgs && combinedImgSrc) {
        const combinedImg = new Image();
        combinedImg.src = combinedImgSrc;
        node2.imgs = [combinedImg];
      }
      if (node2.widgets) {
        if (ComfyApp.clipspace.images) {
          const clip_image = ComfyApp.clipspace.images[ComfyApp.clipspace["selectedIndex"]];
          const index2 = node2.widgets.findIndex((obj) => obj.name === "image");
          if (index2 >= 0) {
            if (node2.widgets[index2].type != "image" && typeof node2.widgets[index2].value == "string" && clip_image.filename) {
              node2.widgets[index2].value = (clip_image.subfolder ? clip_image.subfolder + "/" : "") + clip_image.filename + (clip_image.type ? ` [${clip_image.type}]` : "");
            } else {
              node2.widgets[index2].value = clip_image;
            }
          }
        }
        if (ComfyApp.clipspace.widgets) {
          ComfyApp.clipspace.widgets.forEach(({ type, name: name2, value }) => {
            const prop2 = Object.values(node2.widgets).find(
              (obj) => obj.type === type && obj.name === name2
            );
            if (prop2 && prop2.type != "button") {
              if (prop2.type != "image" && typeof prop2.value == "string" && // @ts-expect-error Custom widget value
              value.filename) {
                const resultItem = value;
                prop2.value = (resultItem.subfolder ? resultItem.subfolder + "/" : "") + resultItem.filename + (resultItem.type ? ` [${resultItem.type}]` : "");
              } else {
                prop2.value = value;
                prop2.callback?.(value);
              }
            }
          });
        }
      }
      app$1.canvas.setDirty(true);
      useNodeOutputStore().updateNodeImages(node2);
    }
  }
  /**
   * Adds a handler allowing drag+drop of files onto the window to load workflows
   */
  addDropHandler() {
    useEventListener(document, "drop", async (event) => {
      try {
        event.preventDefault();
        event.stopPropagation();
        const n = this.dragOverNode;
        this.dragOverNode = null;
        if (await n?.onDragDrop?.(event)) return;
        const fileMaybe = await extractFileFromDragEvent(event);
        if (!fileMaybe) return;
        const workspace = useWorkspaceStore();
        try {
          workspace.spinner = true;
          await this.handleFile(fileMaybe, "file_drop");
        } finally {
          workspace.spinner = false;
        }
      } catch (error) {
        useToastStore().addAlert(t("toastMessages.dropFileError", { error }));
      }
    });
    useEventListener(this.canvasElRef, "dragleave", async () => {
      if (!this.dragOverNode) return;
      this.dragOverNode = null;
      this.canvas.setDirty(false, true);
    });
    useEventListener(
      this.canvasElRef,
      "dragover",
      (event) => {
        this.canvas.adjustMouseEvent(event);
        const node2 = this.canvas.graph?.getNodeOnPos(
          event.canvasX,
          event.canvasY
        );
        if (!node2?.onDragOver?.(event)) {
          this.dragOverNode = null;
          return;
        }
        this.dragOverNode = node2;
        requestAnimationFrame(() => {
          this.canvas.setDirty(false, true);
        });
      },
      false
    );
  }
  /**
   * Handle keypress
   */
  addProcessKeyHandler() {
    const origProcessKey = LGraphCanvas.prototype.processKey;
    LGraphCanvas.prototype.processKey = function(e2) {
      if (!this.graph) return;
      if (e2.target instanceof Element && e2.target.localName == "input") {
        return;
      }
      if (e2.type == "keydown" && !e2.repeat) {
        const keyCombo = KeyComboImpl.fromEvent(e2);
        const keybindingStore = useKeybindingStore();
        const keybinding = keybindingStore.getKeybinding(keyCombo);
        if (keybinding && keybinding.targetElementId === "graph-canvas-container") {
          useCommandStore().execute(keybinding.commandId);
          this.graph.change();
          e2.preventDefault();
          e2.stopImmediatePropagation();
          return;
        }
        if (e2.key === "c" && (e2.metaKey || e2.ctrlKey)) {
          return;
        }
        if ((e2.key === "v" || e2.key == "V") && (e2.metaKey || e2.ctrlKey) && !e2.shiftKey) {
          return;
        }
      }
      return origProcessKey.apply(this, [e2]);
    };
  }
  /**
   * Handles updates from the API socket
   */
  addApiUpdateHandlers() {
    api.addEventListener("status", ({ detail }) => {
      this.ui.setStatus(detail);
    });
    api.addEventListener("progress", () => {
      this.canvas.setDirty(true, false);
    });
    api.addEventListener("executing", () => {
      this.canvas.setDirty(true, false);
    });
    api.addEventListener("executed", ({ detail }) => {
      const nodeOutputStore = useNodeOutputStore();
      const executionId = String(detail.display_node || detail.node);
      nodeOutputStore.setNodeOutputsByExecutionId(executionId, detail.output, {
        merge: detail.merge
      });
      const node2 = getNodeByExecutionId(this.rootGraph, executionId);
      if (node2 && node2.onExecuted) {
        node2.onExecuted(detail.output);
      }
    });
    api.addEventListener("execution_start", () => {
      triggerCallbackOnAllNodes(this.rootGraph, "onExecutionStart");
    });
    api.addEventListener("execution_error", ({ detail }) => {
      if (detail.exception_message?.includes(
        "Unauthorized: Please login first to use this node."
      )) {
        useDialogService().showApiNodesSignInDialog([detail.node_type]);
      } else if (detail.exception_message?.includes(
        "Payment Required: Please add credits to your account to use this node."
      )) {
        const { isActiveSubscription } = useSubscription();
        if (isActiveSubscription.value) {
          useDialogService().showTopUpCreditsDialog({
            isInsufficientCredits: true
          });
        }
      } else {
        useDialogService().showExecutionErrorDialog(detail);
      }
      this.canvas.draw(true, true);
    });
    api.addEventListener("b_preview_with_metadata", ({ detail }) => {
      const { blob, displayNodeId } = detail;
      const { setNodePreviewsByExecutionId, revokePreviewsByExecutionId } = useNodeOutputStore();
      revokePreviewsByExecutionId(displayNodeId);
      const blobUrl = URL.createObjectURL(blob);
      const nodeParents = displayNodeId.split(":");
      for (let i = 1; i <= nodeParents.length; i++) {
        setNodePreviewsByExecutionId(nodeParents.slice(0, i).join(":"), [
          blobUrl
        ]);
      }
    });
    api.init();
  }
  /** Flag that the graph is configuring to prevent nodes from running checks while its still loading */
  addConfigureHandler() {
    const app2 = this;
    const configure = LGraph.prototype.configure;
    LGraph.prototype.configure = function(...args) {
      app2.configuringGraphLevel++;
      try {
        return configure.apply(this, args);
      } finally {
        app2.configuringGraphLevel--;
      }
    };
  }
  addAfterConfigureHandler(graph) {
    const { onConfigure: onConfigure2 } = graph;
    graph.onConfigure = function(...args) {
      fixLinkInputSlots(this);
      triggerCallbackOnAllNodes(this, "onGraphConfigured");
      const r = onConfigure2?.apply(this, args);
      triggerCallbackOnAllNodes(this, "onAfterGraphConfigured");
      return r;
    };
  }
  /**
   * Set up the app on the page
   */
  async setup(canvasEl) {
    this.bodyTop = document.getElementById("comfyui-body-top");
    this.bodyLeft = document.getElementById("comfyui-body-left");
    this.bodyRight = document.getElementById("comfyui-body-right");
    this.bodyBottom = document.getElementById("comfyui-body-bottom");
    this.canvasContainer = document.getElementById("graph-canvas-container");
    this.canvasElRef.value = canvasEl;
    await useWorkspaceStore().workflow.syncWorkflows();
    void useSubgraphStore().fetchSubgraphs();
    await useExtensionService().loadExtensions();
    this.addProcessKeyHandler();
    this.addConfigureHandler();
    this.addApiUpdateHandlers();
    const graph = new LGraph();
    graph.events.addEventListener("subgraph-created", (e2) => {
      try {
        const { subgraph, data } = e2.detail;
        useSubgraphService().registerNewSubgraph(subgraph, data);
      } catch (err) {
        console.error("Failed to register subgraph", err);
        useToastStore().add({
          severity: "error",
          summary: "Failed to register subgraph",
          detail: err instanceof Error ? err.message : String(err)
        });
      }
    });
    this.addAfterConfigureHandler(graph);
    this.rootGraphInternal = graph;
    this.canvas = new LGraphCanvas(canvasEl, graph);
    this.canvas.state = reactive(this.canvas.state);
    this.ctx = canvasEl.getContext("2d");
    LiteGraph.alt_drag_do_clone_nodes = true;
    LiteGraph.macGesturesRequireMac = false;
    this.canvas.canvas.addEventListener(
      "litegraph:set-graph",
      (e2) => {
        const { newGraph } = e2.detail;
        const widgetStore = useDomWidgetStore();
        const activeWidgets = Object.fromEntries(
          newGraph.nodes.flatMap((node2) => node2.widgets ?? []).filter(
            (w) => w instanceof DOMWidgetImpl || w instanceof ComponentWidgetImpl
          ).map((w) => [w.id, w])
        );
        for (const [
          widgetId,
          widgetState
        ] of widgetStore.widgetStates.entries()) {
          if (widgetId in activeWidgets) {
            widgetState.active = true;
            widgetState.widget = activeWidgets[widgetId];
          } else {
            widgetState.active = false;
          }
        }
      }
    );
    this.canvas.canvas.addEventListener(
      "litegraph:set-graph",
      (e2) => {
        const { newGraph, oldGraph } = e2.detail;
        if (oldGraph) {
          ensureCorrectLayoutScale(
            newGraph.extra.workflowRendererVersion,
            newGraph
          );
        }
      }
    );
    registerProxyWidgets(this.canvas);
    this.rootGraph.start();
    useResizeObserver(this.canvasElRef, ([canvasEl2]) => {
      if (canvasEl2.target instanceof HTMLCanvasElement) {
        this.resizeCanvas(canvasEl2.target);
      }
    });
    await useExtensionService().invokeExtensionsAsync("init");
    await this.registerNodes();
    this.addDropHandler();
    await useExtensionService().invokeExtensionsAsync("setup");
    this.positionConversion = useCanvasPositionConversion(
      this.canvasContainer,
      this.canvas
    );
  }
  resizeCanvas(canvas2) {
    const scale = Math.max(window.devicePixelRatio, 1);
    canvas2.height = canvas2.width = NaN;
    const { width: width2, height } = canvas2.getBoundingClientRect();
    canvas2.width = Math.round(width2 * scale);
    canvas2.height = Math.round(height * scale);
    canvas2.getContext("2d").scale(scale, scale);
    this.canvas?.draw(true, true);
  }
  updateVueAppNodeDefs(defs) {
    const frontendOnlyDefs = {};
    for (const [name2, node2] of Object.entries(
      LiteGraph.registered_node_types
    )) {
      if (name2 in defs || name2 in SYSTEM_NODE_DEFS || node2.skip_list) {
        continue;
      }
      frontendOnlyDefs[name2] = {
        name: name2,
        display_name: name2,
        category: node2.category || "__frontend_only__",
        input: { required: {}, optional: {} },
        output: [],
        output_name: [],
        output_is_list: [],
        output_node: false,
        python_module: "custom_nodes.frontend_only",
        description: node2.description ?? `Frontend only node for ${name2}`
      };
    }
    const allNodeDefs = {
      ...frontendOnlyDefs,
      ...defs,
      ...SYSTEM_NODE_DEFS
    };
    const nodeDefStore = useNodeDefStore();
    const nodeDefArray = Object.values(allNodeDefs);
    useExtensionService().invokeExtensions(
      "beforeRegisterVueAppNodeDefs",
      nodeDefArray,
      this
    );
    nodeDefStore.updateNodeDefs(nodeDefArray);
  }
  async getNodeDefs() {
    const translateNodeDef = /* @__PURE__ */ __name((def) => ({
      ...def,
      display_name: st(
        `nodeDefs.${def.name}.display_name`,
        def.display_name ?? def.name
      ),
      description: def.description ? st(`nodeDefs.${def.name}.description`, def.description) : "",
      category: def.category.split("/").map((category) => st(`nodeCategories.${category}`, category)).join("/")
    }), "translateNodeDef");
    return toolkit.mapValues(await api.getNodeDefs(), (def) => translateNodeDef(def));
  }
  /**
   * Registers nodes with the graph
   */
  async registerNodes() {
    const defs = await this.getNodeDefs();
    await this.registerNodesFromDefs(defs);
    await useExtensionService().invokeExtensionsAsync("registerCustomNodes");
    if (this.vueAppReady) {
      this.updateVueAppNodeDefs(defs);
    }
  }
  async registerNodeDef(nodeId, nodeDef) {
    return await useLitegraphService().registerNodeDef(nodeId, nodeDef);
  }
  async registerNodesFromDefs(defs) {
    await useExtensionService().invokeExtensionsAsync("addCustomNodeDefs", defs);
    for (const nodeId in defs) {
      this.registerNodeDef(nodeId, defs[nodeId]);
    }
  }
  // @ts-expect-error fixme ts strict error
  loadTemplateData(templateData) {
    if (!templateData?.templates) {
      return;
    }
    const old = localStorage.getItem("litegrapheditor_clipboard");
    var maxY, nodeBottom, node2;
    for (const template of templateData.templates) {
      if (!template?.data) {
        continue;
      }
      const data = JSON.parse(template.data);
      if (!data.reroutes) {
        deserialiseAndCreate(template.data, app$1.canvas);
      } else {
        localStorage.setItem("litegrapheditor_clipboard", template.data);
        app$1.canvas.pasteFromClipboard();
      }
      maxY = false;
      for (const i in app$1.canvas.selected_nodes) {
        node2 = app$1.canvas.selected_nodes[i];
        nodeBottom = node2.pos[1] + node2.size[1];
        if (maxY === false || nodeBottom > maxY) {
          maxY = nodeBottom;
        }
      }
      app$1.canvas.graph_mouse[1] = maxY + 50;
    }
    localStorage.setItem("litegrapheditor_clipboard", old);
  }
  showMissingNodesError(missingNodeTypes) {
    if (useSettingStore().get("Comfy.Workflow.ShowMissingNodesWarning")) {
      useDialogService().showLoadWorkflowWarning({ missingNodeTypes });
    }
  }
  // @ts-expect-error fixme ts strict error
  showMissingModelsError(missingModels, paths) {
    if (useSettingStore().get("Comfy.Workflow.ShowMissingModelsWarning")) {
      useDialogService().showMissingModelsWarning({
        missingModels,
        paths
      });
    }
  }
  async loadGraphData(graphData, clean = true, restore_view = true, workflow = null, options = {}) {
    const {
      showMissingNodesDialog = true,
      showMissingModelsDialog = true,
      checkForRerouteMigration = false,
      openSource
    } = options;
    useWorkflowService().beforeLoadNewGraph();
    if (clean !== false) {
      this.clean();
    }
    let reset_invalid_values = false;
    if (!graphData || typeof graphData !== "object" || Array.isArray(graphData)) {
      graphData = defaultGraph;
      reset_invalid_values = true;
    }
    graphData = clone$1(graphData);
    if (useSettingStore().get("Comfy.Validation.Workflows")) {
      const { graphData: validatedGraphData } = await useWorkflowValidation().validateWorkflow(graphData);
      graphData = validatedGraphData ?? graphData;
    }
    if (checkForRerouteMigration && graphData.version === 0.4 && findLegacyRerouteNodes(graphData).length && noNativeReroutes(graphData)) {
      useToastStore().add({
        group: "reroute-migration",
        severity: "warn"
      });
    }
    useSubgraphService().loadSubgraphs(graphData);
    const missingNodeTypes = [];
    const missingModels = [];
    await useExtensionService().invokeExtensionsAsync(
      "beforeConfigureGraph",
      graphData,
      missingNodeTypes
    );
    const embeddedModels = [];
    const collectMissingNodesAndModels = /* @__PURE__ */ __name((nodes, path = "") => {
      if (!Array.isArray(nodes)) {
        console.warn(
          "Workflow nodes data is missing or invalid, skipping node processing",
          { nodes, path }
        );
        return;
      }
      for (let n of nodes) {
        if (n.type == "T2IAdapterLoader") n.type = "ControlNetLoader";
        if (n.type == "ConditioningAverage ") n.type = "ConditioningAverage";
        if (n.type == "SDV_img2vid_Conditioning")
          n.type = "SVD_img2vid_Conditioning";
        if (n.type == "Load3DAnimation") n.type = "Load3D";
        if (n.type == "Preview3DAnimation") n.type = "Preview3D";
        if (!(n.type in LiteGraph.registered_node_types)) {
          if (path) {
            missingNodeTypes.push({
              type: n.type,
              hint: `in subgraph '${path}'`
            });
          } else {
            missingNodeTypes.push(n.type);
          }
          n.type = sanitizeNodeName(n.type);
        }
        const selectedModels = getSelectedModelsMetadata(n);
        if (selectedModels?.length) {
          embeddedModels.push(...selectedModels);
        }
      }
    }, "collectMissingNodesAndModels");
    collectMissingNodesAndModels(graphData.nodes);
    if (graphData.definitions?.subgraphs) {
      for (const subgraph of graphData.definitions.subgraphs) {
        if (isSubgraphDefinition(subgraph)) {
          collectMissingNodesAndModels(
            subgraph.nodes,
            subgraph.name || subgraph.id
          );
        }
      }
    }
    const workflowSchemaV1Models = graphData.models;
    if (workflowSchemaV1Models?.length)
      embeddedModels.push(...workflowSchemaV1Models);
    const getModelKey = /* @__PURE__ */ __name((model) => model.url || model.hash, "getModelKey");
    const validModels = embeddedModels.filter(getModelKey);
    const uniqueModels = toolkit.uniqBy(validModels, getModelKey);
    if (uniqueModels.length && useSettingStore().get("Comfy.Workflow.ShowMissingModelsWarning")) {
      const modelStore = useModelStore();
      await modelStore.loadModelFolders();
      for (const m of uniqueModels) {
        const modelFolder = await modelStore.getLoadedModelFolder(m.directory);
        if (!modelFolder) m.directory_invalid = true;
        const modelsAvailable = modelFolder?.models;
        const modelExists = modelsAvailable && Object.values(modelsAvailable).some(
          (model) => model.file_name === m.name
        );
        if (!modelExists) missingModels.push(m);
      }
    }
    try {
      this.rootGraph.configure(graphData);
      const originalMainGraphRenderer = this.rootGraph.extra.workflowRendererVersion;
      ensureCorrectLayoutScale(originalMainGraphRenderer);
      for (const subgraph of this.rootGraph.subgraphs.values()) {
        ensureCorrectLayoutScale(
          subgraph.extra.workflowRendererVersion || originalMainGraphRenderer,
          subgraph
        );
      }
      if (restore_view && useSettingStore().get("Comfy.EnableWorkflowViewRestore")) {
        if (graphData.extra?.ds) {
          this.canvas.ds.offset = graphData.extra.ds.offset;
          this.canvas.ds.scale = graphData.extra.ds.scale;
          this.canvas.ds.computeVisibleArea(this.canvas.viewport);
          if (!anyItemOverlapsRect(
            this.rootGraph._nodes,
            this.canvas.visible_area
          )) {
            requestAnimationFrame(() => useLitegraphService().fitView());
          }
        } else {
          useLitegraphService().fitView();
        }
      }
    } catch (error) {
      useDialogService().showErrorDialog(error, {
        title: t("errorDialog.loadWorkflowTitle"),
        reportType: "loadWorkflowError"
      });
      console.error(error);
      return;
    }
    forEachNode(this.rootGraph, (node2) => {
      const size = node2.computeSize();
      size[0] = Math.max(node2.size[0], size[0]);
      size[1] = Math.max(node2.size[1], size[1]);
      node2.setSize(size);
      if (node2.widgets) {
        for (let widget of node2.widgets) {
          if (node2.type == "KSampler" || node2.type == "KSamplerAdvanced") {
            if (widget.name == "sampler_name") {
              if (typeof widget.value === "string" && widget.value.startsWith("sample_")) {
                widget.value = widget.value.slice(7);
              }
            }
          }
          if (node2.type == "KSampler" || node2.type == "KSamplerAdvanced" || node2.type == "PrimitiveNode") {
            if (widget.name == "control_after_generate") {
              if (widget.value === true) {
                widget.value = "randomize";
              } else if (widget.value === false) {
                widget.value = "fixed";
              }
            }
          }
          if (reset_invalid_values) {
            if (widget.type == "combo") {
              if (
                // @ts-expect-error fixme ts strict error
                !widget.options.values.includes(widget.value) && // @ts-expect-error fixme ts strict error
                widget.options.values.length > 0
              ) {
                widget.value = widget.options.values[0];
              }
            }
          }
        }
      }
      useExtensionService().invokeExtensions("loadedGraphNode", node2);
    });
    if (missingNodeTypes.length && showMissingNodesDialog) {
      this.showMissingNodesError(missingNodeTypes);
    }
    if (missingModels.length && showMissingModelsDialog) {
      const paths = await api.getFolderPaths();
      this.showMissingModelsError(missingModels, paths);
    }
    await useExtensionService().invokeExtensionsAsync(
      "afterConfigureGraph",
      missingNodeTypes
    );
    ({
      missing_node_count: missingNodeTypes.length,
      missing_node_types: missingNodeTypes.map(
        (node2) => typeof node2 === "string" ? node2 : node2.type
      )
    });
    await useWorkflowService().afterLoadNewGraph(
      workflow,
      this.rootGraph.serialize()
    );
    requestAnimationFrame(() => {
      this.canvas.setDirty(true, true);
    });
  }
  async graphToPrompt(graph = this.rootGraph) {
    return graphToPrompt(graph, {
      sortNodes: useSettingStore().get("Comfy.Workflow.SortNodeIdOnSave")
    });
  }
  async queuePrompt(number, batchCount = 1, queueNodeIds) {
    this.queueItems.push({ number, batchCount, queueNodeIds });
    if (this.processingQueue) {
      return false;
    }
    this.processingQueue = true;
    const executionStore = useExecutionStore();
    executionStore.lastNodeErrors = null;
    let comfyOrgAuthToken = await useFirebaseAuthStore().getIdToken();
    let comfyOrgApiKey = useApiKeyAuthStore().getApiKey();
    try {
      while (this.queueItems.length) {
        const { number: number2, batchCount: batchCount2, queueNodeIds: queueNodeIds2 } = this.queueItems.pop();
        const previewMethod = useSettingStore().get(
          "Comfy.Execution.PreviewMethod"
        );
        for (let i = 0; i < batchCount2; i++) {
          forEachNode(this.rootGraph, (node2) => {
            for (const widget of node2.widgets ?? []) widget.beforeQueued?.();
          });
          const p = await this.graphToPrompt(this.rootGraph);
          const queuedNodes = collectAllNodes(this.rootGraph);
          try {
            api.authToken = comfyOrgAuthToken;
            api.apiKey = comfyOrgApiKey ?? void 0;
            const res = await api.queuePrompt(number2, p, {
              partialExecutionTargets: queueNodeIds2,
              previewMethod
            });
            delete api.authToken;
            delete api.apiKey;
            executionStore.lastNodeErrors = res.node_errors ?? null;
            if (executionStore.lastNodeErrors?.length) {
              this.canvas.draw(true, true);
            } else {
              try {
                if (res.prompt_id) {
                  executionStore.storePrompt({
                    id: res.prompt_id,
                    nodes: Object.keys(p.output),
                    workflow: useWorkspaceStore().workflow.activeWorkflow
                  });
                }
              } catch (error) {
              }
            }
          } catch (error) {
            useDialogService().showErrorDialog(error, {
              title: t("errorDialog.promptExecutionError"),
              reportType: "promptExecutionError"
            });
            console.error(error);
            if (error instanceof PromptExecutionError) {
              executionStore.lastNodeErrors = error.response.node_errors ?? null;
              this.canvas.draw(true, true);
            }
            break;
          }
          executeWidgetsCallback(queuedNodes, "afterQueued");
          this.canvas.draw(true, true);
          await this.ui.queue.update();
        }
      }
    } finally {
      this.processingQueue = false;
    }
    api.dispatchCustomEvent("promptQueued", { number, batchCount });
    return !executionStore.lastNodeErrors;
  }
  showErrorOnFileLoad(file) {
    useToastStore().addAlert(
      t("toastMessages.fileLoadError", { fileName: file.name })
    );
  }
  /**
   * Loads workflow data from the specified file
   * @param {File} file
   */
  async handleFile(file, openSource) {
    const fileName = file.name.replace(/\.\w+$/, "");
    const workflowData = await getWorkflowDataFromFile(file);
    if (!workflowData) {
      this.showErrorOnFileLoad(file);
      return;
    }
    const { workflow, prompt, parameters, templates } = workflowData;
    if (templates) {
      this.loadTemplateData({ templates });
    }
    if (workflow) {
      let workflowObj = void 0;
      try {
        workflowObj = typeof workflow === "string" ? JSON.parse(workflow) : workflow;
        if (workflowObj && typeof workflowObj === "object" && !Array.isArray(workflowObj)) {
          await this.loadGraphData(workflowObj, true, true, fileName, {
            openSource
          });
          return;
        } else {
          console.error(
            "Invalid workflow structure, trying parameters fallback"
          );
          this.showErrorOnFileLoad(file);
        }
      } catch (err) {
        console.error("Failed to parse workflow:", err);
        this.showErrorOnFileLoad(file);
      }
    }
    if (prompt) {
      try {
        const promptObj = typeof prompt === "string" ? JSON.parse(prompt) : prompt;
        if (this.isApiJson(promptObj)) {
          this.loadApiJson(promptObj, fileName);
          return;
        }
      } catch (err) {
        console.error("Failed to parse prompt:", err);
      }
    }
    if (parameters) {
      useWorkflowService().beforeLoadNewGraph();
      importA1111(this.graph, parameters);
      useWorkflowService().afterLoadNewGraph(
        fileName,
        this.rootGraph.serialize()
      );
      return;
    }
    this.showErrorOnFileLoad(file);
  }
  // @deprecated
  isApiJson(data) {
    if (!toolkit.isObject(data) || Array.isArray(data)) {
      return false;
    }
    if (Object.keys(data).length === 0) return false;
    return Object.values(data).every((node2) => {
      if (!node2 || typeof node2 !== "object" || Array.isArray(node2)) {
        return false;
      }
      const { class_type: classType, inputs } = node2;
      const inputsIsRecord = toolkit.isObject(inputs) && !Array.isArray(inputs);
      return typeof classType === "string" && inputsIsRecord;
    });
  }
  loadApiJson(apiData, fileName) {
    useWorkflowService().beforeLoadNewGraph();
    const missingNodeTypes = Object.values(apiData).filter(
      (n) => !LiteGraph.registered_node_types[n.class_type]
    );
    if (missingNodeTypes.length) {
      this.showMissingNodesError(missingNodeTypes.map((t2) => t2.class_type));
      return;
    }
    const ids = Object.keys(apiData);
    app$1.rootGraph.clear();
    for (const id2 of ids) {
      const data = apiData[id2];
      const node2 = LiteGraph.createNode(data.class_type);
      if (!node2) continue;
      node2.id = isNaN(+id2) ? id2 : +id2;
      node2.title = data._meta?.title ?? node2.title;
      app$1.rootGraph.add(node2);
    }
    for (const id2 of ids) {
      const data = apiData[id2];
      const node2 = app$1.rootGraph.getNodeById(id2);
      for (const input in data.inputs ?? {}) {
        const value = data.inputs[input];
        if (value instanceof Array) {
          const [fromId, fromSlot] = value;
          const fromNode = app$1.rootGraph.getNodeById(fromId);
          let toSlot = node2.inputs?.findIndex((inp) => inp.name === input);
          if (toSlot == null || toSlot === -1) {
            try {
              const widget = node2.widgets?.find((w) => w.name === input);
              if (widget && node2.convertWidgetToInput?.(widget)) {
                toSlot = node2.inputs?.length - 1;
              }
            } catch (error) {
            }
          }
          if (toSlot != null || toSlot !== -1) {
            fromNode.connect(fromSlot, node2, toSlot);
          }
        } else {
          const widget = node2.widgets?.find((w) => w.name === input);
          if (widget) {
            widget.value = value;
            widget.callback?.(value);
          }
        }
      }
    }
    app$1.rootGraph.arrange();
    for (const id2 of ids) {
      const data = apiData[id2];
      const node2 = app$1.rootGraph.getNodeById(id2);
      for (const input in data.inputs ?? {}) {
        const value = data.inputs[input];
        if (value instanceof Array) {
          const [fromId, fromSlot] = value;
          const fromNode = app$1.rootGraph.getNodeById(fromId);
          let toSlot = node2.inputs?.findIndex((inp) => inp.name === input);
          if (toSlot == null || toSlot === -1) {
            try {
              const widget = node2.widgets?.find((w) => w.name === input);
              if (widget && node2.convertWidgetToInput?.(widget)) {
                toSlot = node2.inputs?.length - 1;
              }
            } catch (error) {
            }
          }
          if (toSlot != null || toSlot !== -1) {
            fromNode.connect(fromSlot, node2, toSlot);
          }
        } else {
          const widget = node2.widgets?.find((w) => w.name === input);
          if (widget) {
            widget.value = value;
            widget.callback?.(value);
          }
        }
      }
    }
    app$1.rootGraph.arrange();
    useWorkflowService().afterLoadNewGraph(
      fileName,
      this.rootGraph.serialize()
    );
  }
  /**
   * Registers a Comfy web extension with the app
   * @param {ComfyExtension} extension
   */
  registerExtension(extension) {
    useExtensionService().registerExtension(extension);
  }
  /**
   * Collects context menu items from all extensions for canvas menus
   * @param canvas The canvas instance
   * @returns Array of context menu items from all extensions
   */
  collectCanvasMenuItems(canvas2) {
    return useExtensionService().invokeExtensions("getCanvasMenuItems", canvas2).flat();
  }
  /**
   * Collects context menu items from all extensions for node menus
   * @param node The node being right-clicked
   * @returns Array of context menu items from all extensions
   */
  collectNodeMenuItems(node2) {
    return useExtensionService().invokeExtensions("getNodeMenuItems", node2).flat();
  }
  /**
   * Refresh combo list on whole nodes
   */
  async refreshComboInNodes() {
    const requestToastMessage = {
      severity: "info",
      summary: t("g.update"),
      detail: t("toastMessages.updateRequested")
    };
    if (this.vueAppReady) {
      useToastStore().add(requestToastMessage);
    }
    const defs = await this.getNodeDefs();
    for (const nodeId in defs) {
      this.registerNodeDef(nodeId, defs[nodeId]);
    }
    forEachNode(this.rootGraph, (node2) => {
      const def = defs[node2.type];
      node2.refreshComboInNode?.(defs);
      if (!def?.input) return;
      if (node2.widgets) {
        const nodeInputs = def.input;
        for (const widget of node2.widgets) {
          if (widget.type === "combo") {
            let inputType;
            if (nodeInputs.required?.[widget.name] !== void 0) {
              inputType = "required";
            } else if (nodeInputs.optional?.[widget.name] !== void 0) {
              inputType = "optional";
            }
            if (inputType !== void 0) {
              const inputSpec = nodeInputs[inputType]?.[widget.name];
              if (inputSpec) {
                if (isComboInputSpecV2(inputSpec)) {
                  widget.options.values = inputSpec[1]?.options;
                } else if (isComboInputSpecV1(inputSpec)) {
                  widget.options.values = inputSpec[0];
                }
              }
            }
          }
        }
      }
    });
    await useExtensionService().invokeExtensionsAsync(
      "refreshComboInNodes",
      defs
    );
    if (this.vueAppReady) {
      this.updateVueAppNodeDefs(defs);
      useToastStore().remove(requestToastMessage);
      useToastStore().add({
        severity: "success",
        summary: t("g.updated"),
        detail: t("toastMessages.nodeDefinitionsUpdated"),
        life: 1e3
      });
    }
  }
  /**
   * Clean current state
   */
  clean() {
    const nodeOutputStore = useNodeOutputStore();
    nodeOutputStore.resetAllOutputsAndPreviews();
    const executionStore = useExecutionStore();
    executionStore.lastNodeErrors = null;
    executionStore.lastExecutionError = null;
    useDomWidgetStore().clear();
    if (this.rootGraph && !this.canvas.subgraph) {
      this.rootGraph.clear();
    }
  }
  clientPosToCanvasPos(pos) {
    if (!this.positionConversion) {
      throw new Error("clientPosToCanvasPos called before setup");
    }
    return this.positionConversion.clientPosToCanvasPos(pos);
  }
  canvasPosToClientPos(pos) {
    if (!this.positionConversion) {
      throw new Error("canvasPosToClientPos called before setup");
    }
    return this.positionConversion.canvasPosToClientPos(pos);
  }
}
const app$1 = new ComfyApp();
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.app = window.comfyAPI.app || {};
window.comfyAPI.app.ANIM_PREVIEW_WIDGET = ANIM_PREVIEW_WIDGET;
window.comfyAPI.app.ComfyApp = ComfyApp;
window.comfyAPI.app.app = app$1;
const useTitleEditorStore = defineStore("titleEditor", () => {
  const titleEditorTarget = shallowRef(null);
  return {
    titleEditorTarget
  };
});
const useCanvasStore = defineStore("canvas", () => {
  const canvas2 = shallowRef(null);
  const selectedItems = ref([]);
  const updateSelectedItems = /* @__PURE__ */ __name(() => {
    const items = Array.from(canvas2.value?.selectedItems ?? []);
    selectedItems.value = items.map((item) => markRaw(item));
  }, "updateSelectedItems");
  const appScalePercentage = ref(100);
  const linearMode2 = ref(false);
  let originalOnChanged = void 0;
  const initScaleSync = /* @__PURE__ */ __name(() => {
    if (app$1.canvas?.ds) {
      originalOnChanged = app$1.canvas.ds.onChanged;
      appScalePercentage.value = Math.round(app$1.canvas.ds.scale * 100);
      app$1.canvas.ds.onChanged = () => {
        if (app$1.canvas?.ds?.scale) {
          appScalePercentage.value = Math.round(app$1.canvas.ds.scale * 100);
        }
        originalOnChanged?.(app$1.canvas.ds.scale, app$1.canvas.ds.offset);
      };
    }
  }, "initScaleSync");
  const cleanupScaleSync = /* @__PURE__ */ __name(() => {
    if (app$1.canvas?.ds) {
      app$1.canvas.ds.onChanged = originalOnChanged;
      originalOnChanged = void 0;
    }
  }, "cleanupScaleSync");
  const nodeSelected = computed(() => selectedItems.value.some(isLGraphNode));
  const groupSelected = computed(() => selectedItems.value.some(isLGraphGroup));
  const rerouteSelected = computed(() => selectedItems.value.some(isReroute));
  const getCanvas = /* @__PURE__ */ __name(() => {
    if (!canvas2.value) throw new Error("getCanvas: canvas is null");
    return canvas2.value;
  }, "getCanvas");
  const setAppZoomFromPercentage = /* @__PURE__ */ __name((percentage) => {
    if (!app$1.canvas?.ds || percentage <= 0) return;
    const newScale = percentage / 100;
    const ds = app$1.canvas.ds;
    ds.changeScale(
      newScale,
      ds.element ? [ds.element.width / 2, ds.element.height / 2] : void 0
    );
    app$1.canvas.setDirty(true, true);
    appScalePercentage.value = Math.round(newScale * 100);
  }, "setAppZoomFromPercentage");
  const currentGraph = shallowRef(null);
  const isInSubgraph = ref(false);
  const selectedNodeIds = computed(
    () => new Set(
      selectedItems.value.filter((item) => item.id !== void 0).map((item) => String(item.id))
    )
  );
  whenever(
    () => canvas2.value,
    (newCanvas) => {
      useEventListener(
        newCanvas.canvas,
        "litegraph:set-graph",
        (event) => {
          const newGraph = event.detail?.newGraph ?? app$1.canvas?.graph;
          currentGraph.value = newGraph;
          isInSubgraph.value = Boolean(app$1.canvas?.subgraph);
        }
      );
      useEventListener(newCanvas.canvas, "subgraph-opened", () => {
        isInSubgraph.value = true;
      });
    },
    { immediate: true }
  );
  return {
    canvas: canvas2,
    selectedItems,
    selectedNodeIds,
    nodeSelected,
    groupSelected,
    rerouteSelected,
    appScalePercentage,
    linearMode: linearMode2,
    updateSelectedItems,
    getCanvas,
    setAppZoomFromPercentage,
    initScaleSync,
    cleanupScaleSync,
    currentGraph,
    isInSubgraph
  };
});
function clone(obj) {
  return JSON.parse(JSON.stringify(obj));
}
__name(clone, "clone");
const logger = log.getLogger("ChangeTracker");
logger.setLevel("info");
class ChangeTracker {
  static {
    __name(this, "ChangeTracker");
  }
  constructor(workflow, initialState) {
    this.workflow = workflow;
    this.initialState = initialState;
    this.activeState = initialState;
  }
  static MAX_HISTORY = 50;
  /**
   * The active state of the workflow.
   */
  activeState;
  undoQueue = [];
  redoQueue = [];
  changeCount = 0;
  /**
   * Whether the redo/undo restoring is in progress.
   */
  _restoringState = false;
  ds;
  nodeOutputs;
  subgraphState;
  /**
   * Save the current state as the initial state.
   */
  reset(state2) {
    if (this._restoringState) return;
    logger.debug("Reset State");
    if (state2) this.activeState = clone(state2);
    this.initialState = clone(this.activeState);
  }
  store() {
    this.ds = {
      scale: app$1.canvas.ds.scale,
      offset: [app$1.canvas.ds.offset[0], app$1.canvas.ds.offset[1]]
    };
    const navigation = useSubgraphNavigationStore().exportState();
    this.subgraphState = { navigation };
  }
  restore() {
    if (this.ds) {
      app$1.canvas.ds.scale = this.ds.scale;
      app$1.canvas.ds.offset = this.ds.offset;
    }
    if (this.nodeOutputs) {
      useNodeOutputStore().restoreOutputs(this.nodeOutputs);
    }
    if (this.subgraphState) {
      const { navigation } = this.subgraphState;
      useSubgraphNavigationStore().restoreState(navigation);
      const activeId = navigation.at(-1);
      if (activeId) {
        const subgraph = app$1.rootGraph.subgraphs.get(activeId);
        if (subgraph) {
          app$1.canvas.setGraph(subgraph);
        }
      } else {
        app$1.canvas.setGraph(app$1.rootGraph);
      }
    }
  }
  updateModified() {
    api.dispatchCustomEvent("graphChanged", this.activeState);
    const workflow = useWorkflowStore().getWorkflowByPath(this.workflow.path);
    if (workflow) {
      workflow.isModified = !ChangeTracker.graphEqual(
        this.initialState,
        this.activeState
      );
      if (logger.getLevel() <= logger.levels.DEBUG && workflow.isModified) {
        const diff2 = ChangeTracker.graphDiff(
          this.initialState,
          this.activeState
        );
        logger.debug("Graph diff:", diff2);
      }
    }
  }
  checkState() {
    if (!app$1.graph || this.changeCount) return;
    const currentState = clone(app$1.rootGraph.serialize());
    if (!this.activeState) {
      this.activeState = currentState;
      return;
    }
    if (!ChangeTracker.graphEqual(this.activeState, currentState)) {
      this.undoQueue.push(this.activeState);
      if (this.undoQueue.length > ChangeTracker.MAX_HISTORY) {
        this.undoQueue.shift();
      }
      logger.debug("Diff detected. Undo queue length:", this.undoQueue.length);
      this.activeState = currentState;
      this.redoQueue.length = 0;
      this.updateModified();
    }
  }
  async updateState(source, target) {
    const prevState = source.pop();
    if (prevState) {
      target.push(this.activeState);
      this._restoringState = true;
      try {
        await app$1.loadGraphData(prevState, false, false, this.workflow, {
          showMissingModelsDialog: false,
          showMissingNodesDialog: false,
          checkForRerouteMigration: false
        });
        this.activeState = prevState;
        this.updateModified();
      } finally {
        this._restoringState = false;
      }
    }
  }
  async undo() {
    await this.updateState(this.undoQueue, this.redoQueue);
    logger.debug(
      "Undo. Undo queue length:",
      this.undoQueue.length,
      "Redo queue length:",
      this.redoQueue.length
    );
  }
  async redo() {
    await this.updateState(this.redoQueue, this.undoQueue);
    logger.debug(
      "Redo. Undo queue length:",
      this.undoQueue.length,
      "Redo queue length:",
      this.redoQueue.length
    );
  }
  async undoRedo(e2) {
    if ((e2.ctrlKey || e2.metaKey) && !e2.altKey) {
      const key = e2.key.toUpperCase();
      if (key === "Y" && !e2.shiftKey || key == "Z" && e2.shiftKey) {
        await this.redo();
        return true;
      } else if (key === "Z" && !e2.shiftKey) {
        await this.undo();
        return true;
      }
    }
  }
  beforeChange() {
    this.changeCount++;
  }
  afterChange() {
    if (!--this.changeCount) {
      this.checkState();
    }
  }
  static init() {
    const getCurrentChangeTracker = /* @__PURE__ */ __name(() => useWorkflowStore().activeWorkflow?.changeTracker, "getCurrentChangeTracker");
    const checkState = /* @__PURE__ */ __name(() => getCurrentChangeTracker()?.checkState(), "checkState");
    let keyIgnored = false;
    window.addEventListener(
      "keydown",
      (e2) => {
        if (e2.repeat) return;
        const comfyApp = app$1.constructor;
        if (comfyApp.maskeditor_is_opended?.()) return;
        const activeEl = document.activeElement;
        requestAnimationFrame(async () => {
          let bindInputEl = null;
          if (!app$1.ui.autoQueueEnabled || app$1.ui.autoQueueMode === "instant") {
            if (activeEl?.tagName === "INPUT" || activeEl && "type" in activeEl && activeEl.type === "textarea") {
              return;
            }
            bindInputEl = activeEl;
          }
          keyIgnored = e2.key === "Control" || e2.key === "Shift" || e2.key === "Alt" || e2.key === "Meta";
          if (keyIgnored) return;
          const changeTracker = getCurrentChangeTracker();
          if (!changeTracker) return;
          if (await changeTracker.undoRedo(e2)) return;
          if (ChangeTracker.bindInput(bindInputEl)) return;
          logger.debug("checkState on keydown");
          changeTracker.checkState();
        });
      },
      true
    );
    window.addEventListener("keyup", () => {
      if (keyIgnored) {
        keyIgnored = false;
        logger.debug("checkState on keyup");
        checkState();
      }
    });
    window.addEventListener("mouseup", () => {
      logger.debug("checkState on mouseup");
      checkState();
    });
    api.addEventListener("promptQueued", () => {
      logger.debug("checkState on promptQueued");
      checkState();
    });
    api.addEventListener("graphCleared", () => {
      logger.debug("checkState on graphCleared");
      checkState();
    });
    const processMouseUp = LGraphCanvas.prototype.processMouseUp;
    LGraphCanvas.prototype.processMouseUp = function(e2) {
      const v2 = processMouseUp.apply(this, [e2]);
      logger.debug("checkState on processMouseUp");
      checkState();
      return v2;
    };
    const prompt = LGraphCanvas.prototype.prompt;
    LGraphCanvas.prototype.prompt = function(title, value, callback, event) {
      const extendedCallback = /* @__PURE__ */ __name((v2) => {
        callback(v2);
        checkState();
      }, "extendedCallback");
      logger.debug("checkState on prompt");
      return prompt.apply(this, [title, value, extendedCallback, event]);
    };
    const close = LiteGraph.ContextMenu.prototype.close;
    LiteGraph.ContextMenu.prototype.close = function(e2) {
      const v2 = close.apply(this, [e2]);
      logger.debug("checkState on contextMenuClose");
      checkState();
      return v2;
    };
    document.addEventListener("litegraph:canvas", (e2) => {
      const detail = e2.detail;
      if (detail.subType === "before-change") {
        getCurrentChangeTracker()?.beforeChange();
      } else if (detail.subType === "after-change") {
        getCurrentChangeTracker()?.afterChange();
      }
    });
    api.addEventListener("executed", (e2) => {
      const detail = e2.detail;
      const workflow = useExecutionStore().queuedPrompts[detail.prompt_id]?.workflow;
      const changeTracker = workflow?.changeTracker;
      if (!changeTracker) return;
      changeTracker.nodeOutputs ??= {};
      const nodeOutputs = changeTracker.nodeOutputs;
      const output = nodeOutputs[detail.node];
      if (detail.merge && output) {
        for (const k in detail.output ?? {}) {
          const v2 = output[k];
          if (v2 instanceof Array) {
            output[k] = v2.concat(detail.output[k]);
          } else {
            output[k] = detail.output[k];
          }
        }
      } else {
        nodeOutputs[detail.node] = detail.output;
      }
    });
  }
  static bindInput(activeEl) {
    if (!activeEl || activeEl.tagName === "CANVAS" || activeEl.tagName === "BODY") {
      return false;
    }
    for (const evt of ["change", "input", "blur"]) {
      const htmlElement = activeEl;
      if (`on${evt}` in htmlElement) {
        const listener = /* @__PURE__ */ __name(() => {
          useWorkflowStore().activeWorkflow?.changeTracker?.checkState?.();
          htmlElement.removeEventListener(evt, listener);
        }, "listener");
        htmlElement.addEventListener(evt, listener);
        return true;
      }
    }
    return false;
  }
  static graphEqual(a, b) {
    if (a === b) return true;
    if (typeof a == "object" && a && typeof b == "object" && b) {
      if (!toolkit.isEqualWith(a.nodes, b.nodes, (arrA, arrB) => {
        if (Array.isArray(arrA) && Array.isArray(arrB)) {
          return toolkit.isEqual(new Set(arrA), new Set(arrB));
        }
      })) {
        return false;
      }
      if (!toolkit.isEqual(toolkit.omit(a.extra ?? {}, ["ds"]), toolkit.omit(b.extra ?? {}, ["ds"])))
        return false;
      for (const key of [
        "links",
        "floatingLinks",
        "reroutes",
        "groups",
        "definitions",
        "subgraphs"
      ]) {
        if (!toolkit.isEqual(a[key], b[key])) {
          return false;
        }
      }
      return true;
    }
    return false;
  }
  static graphDiff(a, b) {
    function sortGraphNodes(graph) {
      return {
        links: graph.links,
        floatingLinks: graph.floatingLinks,
        reroutes: graph.reroutes,
        groups: graph.groups,
        extra: graph.extra,
        definitions: graph.definitions,
        subgraphs: graph.subgraphs,
        nodes: graph.nodes.sort((a2, b2) => {
          if (typeof a2.id === "number" && typeof b2.id === "number") {
            return a2.id - b2.id;
          }
          return 0;
        })
      };
    }
    __name(sortGraphNodes, "sortGraphNodes");
    return diff(sortGraphNodes(a), sortGraphNodes(b));
  }
}
window.comfyAPI = window.comfyAPI || {};
window.comfyAPI.changeTracker = window.comfyAPI.changeTracker || {};
window.comfyAPI.changeTracker.ChangeTracker = ChangeTracker;
async function syncEntities(dir, entityByPath, createEntity, updateEntity, exclude = () => false) {
  const files = (await api.listUserDataFullInfo(dir)).map((file) => ({
    ...file,
    path: dir ? `${dir}/${file.path}` : file.path
  }));
  for (const file of files) {
    const existingEntity = entityByPath[file.path];
    if (!existingEntity) {
      entityByPath[file.path] = createEntity(file);
    } else if (exclude(existingEntity)) {
      continue;
    } else {
      updateEntity(existingEntity, file);
    }
  }
  for (const [path, entity] of Object.entries(entityByPath)) {
    if (exclude(entity)) continue;
    if (!files.some((file) => file.path === path)) {
      delete entityByPath[path];
    }
  }
}
__name(syncEntities, "syncEntities");
class UserFile {
  static {
    __name(this, "UserFile");
  }
  constructor(path, lastModified, size) {
    this.path = path;
    this.lastModified = lastModified;
    this.size = size;
    const details = getPathDetails(path);
    this.path = path;
    this.directory = details.directory;
    this.fullFilename = details.fullFilename;
    this.filename = details.filename;
    this.suffix = details.suffix;
  }
  /**
   * Various path components.
   * Example:
   * - path: 'dir/file.txt'
   * - directory: 'dir'
   * - fullFilename: 'file.txt'
   * - filename: 'file'
   * - suffix: 'txt'
   */
  directory;
  fullFilename;
  filename;
  suffix;
  isLoading = false;
  content = null;
  originalContent = null;
  updatePath(newPath) {
    const details = getPathDetails(newPath);
    this.path = newPath;
    this.directory = details.directory;
    this.fullFilename = details.fullFilename;
    this.filename = details.filename;
    this.suffix = details.suffix;
  }
  static createTemporary(path) {
    return new UserFile(path, Date.now(), -1);
  }
  get isTemporary() {
    return this.size === -1;
  }
  get isPersisted() {
    return !this.isTemporary;
  }
  get key() {
    return this.path;
  }
  get isLoaded() {
    return this.content !== null;
  }
  get isModified() {
    return this.content !== this.originalContent;
  }
  /**
   * Loads the file content from the remote storage.
   */
  async load({
    force = false
  } = {}) {
    if (this.isTemporary || !force && this.isLoaded)
      return this;
    this.isLoading = true;
    const resp = await api.getUserData(this.path);
    if (resp.status !== 200) {
      throw new Error(
        `Failed to load file '${this.path}': ${resp.status} ${resp.statusText}`
      );
    }
    this.content = await resp.text();
    this.originalContent = this.content;
    this.isLoading = false;
    return this;
  }
  /**
   * Unloads the file content from memory
   */
  unload() {
    this.content = null;
    this.originalContent = null;
    this.isLoading = false;
  }
  async saveAs(newPath) {
    const tempFile = this.isTemporary ? this : UserFile.createTemporary(newPath);
    tempFile.content = this.content;
    await tempFile.save();
    return tempFile;
  }
  /**
   * Saves the file to the remote storage.
   * @param force Whether to force the save even if the file is not modified.
   */
  async save({ force = false } = {}) {
    if (this.isPersisted && !this.isModified && !force) return this;
    const resp = await api.storeUserData(this.path, this.content, {
      overwrite: this.isPersisted,
      throwOnError: true,
      full_info: true
    });
    const updatedFile = await resp.json();
    if (typeof updatedFile === "object") {
      this.lastModified = updatedFile.modified;
      this.size = updatedFile.size;
    }
    this.originalContent = this.content;
    return this;
  }
  async delete() {
    if (this.isTemporary) return;
    const resp = await api.deleteUserData(this.path);
    if (resp.status !== 204) {
      throw new Error(
        `Failed to delete file '${this.path}': ${resp.status} ${resp.statusText}`
      );
    }
  }
  async rename(newPath) {
    if (this.isTemporary) {
      this.updatePath(newPath);
      return this;
    }
    const resp = await api.moveUserData(this.path, newPath);
    if (resp.status !== 200) {
      throw new Error(
        `Failed to rename file '${this.path}': ${resp.status} ${resp.statusText}`
      );
    }
    this.updatePath(newPath);
    const updatedFile = await resp.json();
    if (typeof updatedFile === "object") {
      this.lastModified = updatedFile.modified;
      this.size = updatedFile.size;
    }
    return this;
  }
}
class ComfyWorkflow extends UserFile {
  static {
    __name(this, "ComfyWorkflow");
  }
  static basePath = "workflows/";
  tintCanvasBg;
  /**
   * The change tracker for the workflow. Non-reactive raw object.
   */
  changeTracker = null;
  /**
   * Whether the workflow has been modified comparing to the initial state.
   */
  _isModified = false;
  /**
   * @param options The path, modified, and size of the workflow.
   * Note: path is the full path, including the 'workflows/' prefix.
   */
  constructor(options) {
    super(options.path, options.modified, options.size);
  }
  get key() {
    return this.path.substring(ComfyWorkflow.basePath.length);
  }
  get activeState() {
    return this.changeTracker?.activeState ?? null;
  }
  get initialState() {
    return this.changeTracker?.initialState ?? null;
  }
  get isLoaded() {
    return this.changeTracker !== null;
  }
  get isModified() {
    return this._isModified;
  }
  set isModified(value) {
    this._isModified = value;
  }
  /**
   * Load the workflow content from remote storage. Directly returns the loaded
   * workflow if the content is already loaded.
   *
   * @param force Whether to force loading the content even if it is already loaded.
   * @returns this
   */
  async load({ force = false } = {}) {
    await super.load({ force });
    if (!force && this.isLoaded) return this;
    if (!this.originalContent) {
      throw new Error("[ASSERT] Workflow content should be loaded");
    }
    this.changeTracker = markRaw(
      new ChangeTracker(
        this,
        /* initialState= */
        JSON.parse(this.originalContent)
      )
    );
    return this;
  }
  unload() {
    this.changeTracker = null;
    super.unload();
  }
  async save() {
    this.content = JSON.stringify(this.activeState);
    const ret = await super.save({ force: true });
    this.changeTracker?.reset();
    this.isModified = false;
    return ret;
  }
  /**
   * Save the workflow as a new file.
   * @param path The path to save the workflow to. Note: with 'workflows/' prefix.
   * @returns this
   */
  async saveAs(path) {
    this.content = JSON.stringify(this.activeState);
    return await super.saveAs(path);
  }
  async promptSave() {
    return await useDialogService().prompt({
      title: t("workflowService.saveWorkflow"),
      message: t("workflowService.enterFilename") + ":",
      defaultValue: this.filename
    });
  }
}
const useWorkflowStore = defineStore("workflow", () => {
  const tabActivationHistory = ref([]);
  const MAX_HISTORY_SIZE = 32;
  const detachWorkflow = /* @__PURE__ */ __name((workflow) => {
    delete workflowLookup.value[workflow.path];
    const index2 = openWorkflowPaths.value.indexOf(workflow.path);
    if (index2 !== -1) {
      openWorkflowPaths.value = openWorkflowPaths.value.filter(
        (path) => path !== workflow.path
      );
    }
    return index2;
  }, "detachWorkflow");
  const attachWorkflow = /* @__PURE__ */ __name((workflow, openIndex = -1) => {
    workflowLookup.value[workflow.path] = workflow;
    if (openIndex !== -1) {
      openWorkflowPaths.value.splice(openIndex, 0, workflow.path);
    }
  }, "attachWorkflow");
  const activeWorkflow = ref(null);
  const isActive = /* @__PURE__ */ __name((workflow) => activeWorkflow.value?.path === workflow.path, "isActive");
  const workflowLookup = ref({});
  const workflows = computed(
    () => Object.values(workflowLookup.value)
  );
  const getWorkflowByPath = /* @__PURE__ */ __name((path) => workflowLookup.value[path] ?? null, "getWorkflowByPath");
  const openWorkflowPaths = ref([]);
  const openWorkflowPathSet = computed(() => new Set(openWorkflowPaths.value));
  const openWorkflows = computed(
    () => openWorkflowPaths.value.map((path) => workflowLookup.value[path])
  );
  const reorderWorkflows = /* @__PURE__ */ __name((from, to) => {
    const movedTab = openWorkflowPaths.value[from];
    openWorkflowPaths.value.splice(from, 1);
    openWorkflowPaths.value.splice(to, 0, movedTab);
  }, "reorderWorkflows");
  const isOpen = /* @__PURE__ */ __name((workflow) => openWorkflowPathSet.value.has(workflow.path), "isOpen");
  const openWorkflowsInBackground = /* @__PURE__ */ __name((paths) => {
    const { left = [], right = [] } = paths;
    if (!left.length && !right.length) return;
    const isValidPath = /* @__PURE__ */ __name((path) => typeof path === "string" && path in workflowLookup.value, "isValidPath");
    openWorkflowPaths.value = toolkit.union(
      left,
      openWorkflowPaths.value,
      right
    ).filter(isValidPath);
  }, "openWorkflowsInBackground");
  const openWorkflow = /* @__PURE__ */ __name(async (workflow) => {
    if (isActive(workflow)) return workflow;
    if (!openWorkflowPaths.value.includes(workflow.path)) {
      openWorkflowPaths.value.push(workflow.path);
    }
    const loadedWorkflow = await workflow.load();
    activeWorkflow.value = loadedWorkflow;
    app$1.canvas.bg_tint = loadedWorkflow.tintCanvasBg;
    const historyIndex = tabActivationHistory.value.indexOf(workflow.path);
    if (historyIndex !== -1) {
      tabActivationHistory.value.splice(historyIndex, 1);
    }
    tabActivationHistory.value.push(workflow.path);
    if (tabActivationHistory.value.length > MAX_HISTORY_SIZE) {
      tabActivationHistory.value.shift();
    }
    useCanvasStore().linearMode = !!loadedWorkflow.activeState.extra?.linearMode;
    return loadedWorkflow;
  }, "openWorkflow");
  const getUnconflictedPath = /* @__PURE__ */ __name((basePath2) => {
    const { directory, filename, suffix } = getPathDetails(basePath2);
    let counter = 2;
    let newPath = basePath2;
    while (workflowLookup.value[newPath]) {
      newPath = `${directory}/${filename} (${counter}).${suffix}`;
      counter++;
    }
    return newPath;
  }, "getUnconflictedPath");
  const saveAs = /* @__PURE__ */ __name((existingWorkflow, path) => {
    const id2 = generateUUID();
    const state2 = JSON.parse(
      JSON.stringify(existingWorkflow.activeState)
    );
    state2.id = id2;
    const workflow = new existingWorkflow.constructor({
      path,
      modified: Date.now(),
      size: -1
    });
    workflow.originalContent = workflow.content = JSON.stringify(state2);
    workflowLookup.value[workflow.path] = workflow;
    return workflow;
  }, "saveAs");
  const createNewWorkflow = /* @__PURE__ */ __name((path, workflowData) => {
    const workflow = new ComfyWorkflow({
      path,
      modified: Date.now(),
      size: -1
    });
    workflow.originalContent = workflow.content = workflowData ? JSON.stringify(workflowData) : defaultGraphJSON;
    workflowLookup.value[workflow.path] = workflow;
    return workflow;
  }, "createNewWorkflow");
  const createTemporary = /* @__PURE__ */ __name((path, workflowData) => {
    const fullPath = getUnconflictedPath(
      ComfyWorkflow.basePath + (path ?? "Unsaved Workflow.json")
    );
    if (path && workflowData) {
      const existingWorkflow = workflows.value.find(
        (w) => w.fullFilename === path
      );
      if (existingWorkflow?.changeTracker && !existingWorkflow.directory.startsWith(
        ComfyWorkflow.basePath.slice(0, -1)
      )) {
        existingWorkflow.changeTracker.reset(workflowData);
        return existingWorkflow;
      }
    }
    return createNewWorkflow(fullPath, workflowData);
  }, "createTemporary");
  const createNewTemporary = /* @__PURE__ */ __name((path, workflowData) => {
    const fullPath = getUnconflictedPath(
      ComfyWorkflow.basePath + (path ?? "Unsaved Workflow.json")
    );
    return createNewWorkflow(fullPath, workflowData);
  }, "createNewTemporary");
  const closeWorkflow = /* @__PURE__ */ __name(async (workflow) => {
    openWorkflowPaths.value = openWorkflowPaths.value.filter(
      (path) => path !== workflow.path
    );
    if (workflow.isTemporary) {
      clearThumbnail(workflow.key);
      delete workflowLookup.value[workflow.path];
    } else {
      workflow.unload();
    }
  }, "closeWorkflow");
  const openedWorkflowIndexShift = /* @__PURE__ */ __name((shift) => {
    const index2 = openWorkflowPaths.value.indexOf(
      activeWorkflow.value?.path ?? ""
    );
    if (index2 !== -1) {
      const length = openWorkflows.value.length;
      const nextIndex = (index2 + shift + length) % length;
      const nextWorkflow = openWorkflows.value[nextIndex];
      return nextWorkflow ?? null;
    }
    return null;
  }, "openedWorkflowIndexShift");
  const getMostRecentWorkflow = /* @__PURE__ */ __name(() => {
    const currentPath = activeWorkflow.value?.path;
    const validPaths = [];
    for (let i = tabActivationHistory.value.length - 1; i >= 0; i--) {
      const path = tabActivationHistory.value[i];
      if (path === currentPath) continue;
      if (openWorkflowPathSet.value.has(path)) {
        validPaths.unshift(path);
        const workflow = workflowLookup.value[path];
        if (workflow) {
          tabActivationHistory.value = validPaths;
          return workflow;
        }
      }
    }
    tabActivationHistory.value = [];
    return null;
  }, "getMostRecentWorkflow");
  const persistedWorkflows = computed(
    () => Array.from(workflows.value).filter(
      (workflow) => workflow.isPersisted && !workflow.path.startsWith("subgraphs/")
    )
  );
  const syncWorkflows = /* @__PURE__ */ __name(async (dir = "") => {
    await syncEntities(
      dir ? "workflows/" + dir : "workflows",
      workflowLookup.value,
      (file) => new ComfyWorkflow({
        path: file.path,
        modified: file.modified,
        size: file.size
      }),
      (existingWorkflow, file) => {
        existingWorkflow.lastModified = file.modified;
        existingWorkflow.size = file.size;
        existingWorkflow.unload();
      },
      /* exclude */
      (workflow) => workflow.isTemporary
    );
  }, "syncWorkflows");
  const bookmarkStore = useWorkflowBookmarkStore();
  const bookmarkedWorkflows = computed(
    () => workflows.value.filter(
      (workflow) => bookmarkStore.isBookmarked(workflow.path)
    )
  );
  const modifiedWorkflows = computed(
    () => workflows.value.filter((workflow) => workflow.isModified)
  );
  const isBusy = ref(false);
  const { moveWorkflowThumbnail, clearThumbnail } = useWorkflowThumbnail();
  const renameWorkflow = /* @__PURE__ */ __name(async (workflow, newPath) => {
    isBusy.value = true;
    try {
      const oldPath = workflow.path;
      const oldKey = workflow.key;
      const wasBookmarked = bookmarkStore.isBookmarked(oldPath);
      const openIndex = detachWorkflow(workflow);
      try {
        await workflow.rename(newPath);
      } finally {
        attachWorkflow(workflow, openIndex);
      }
      const newKey = workflow.key;
      moveWorkflowThumbnail(oldKey, newKey);
      if (wasBookmarked) {
        await bookmarkStore.setBookmarked(oldPath, false);
        await bookmarkStore.setBookmarked(newPath, true);
      }
    } finally {
      isBusy.value = false;
    }
  }, "renameWorkflow");
  const deleteWorkflow = /* @__PURE__ */ __name(async (workflow) => {
    isBusy.value = true;
    try {
      await workflow.delete();
      if (bookmarkStore.isBookmarked(workflow.path)) {
        await bookmarkStore.setBookmarked(workflow.path, false);
      }
      clearThumbnail(workflow.key);
      delete workflowLookup.value[workflow.path];
    } finally {
      isBusy.value = false;
    }
  }, "deleteWorkflow");
  const saveWorkflow = /* @__PURE__ */ __name(async (workflow) => {
    isBusy.value = true;
    try {
      const openIndex = detachWorkflow(workflow);
      try {
        await workflow.save();
      } finally {
        attachWorkflow(workflow, openIndex);
      }
    } finally {
      isBusy.value = false;
    }
  }, "saveWorkflow");
  const isSubgraphActive = ref(false);
  const activeSubgraph = shallowRef();
  const updateActiveGraph = /* @__PURE__ */ __name(() => {
    const subgraph = app$1.canvas?.subgraph;
    activeSubgraph.value = subgraph ? markRaw(subgraph) : void 0;
    if (!app$1.canvas) return;
    isSubgraphActive.value = isSubgraph(subgraph);
  }, "updateActiveGraph");
  const subgraphNodeIdToSubgraph2 = /* @__PURE__ */ __name((id2, graph) => {
    const node2 = graph.getNodeById(id2);
    if (node2?.isSubgraphNode()) return node2.subgraph;
  }, "subgraphNodeIdToSubgraph");
  const getSubgraphsFromInstanceIds2 = /* @__PURE__ */ __name((currentGraph, subgraphNodeIds, subgraphs = []) => {
    const currentPart = subgraphNodeIds.shift();
    if (currentPart === void 0) return subgraphs;
    const subgraph = subgraphNodeIdToSubgraph2(currentPart, currentGraph);
    if (subgraph === void 0) throw new Error("Subgraph not found");
    subgraphs.push(subgraph);
    return getSubgraphsFromInstanceIds2(subgraph, subgraphNodeIds, subgraphs);
  }, "getSubgraphsFromInstanceIds");
  const executionIdToCurrentId = /* @__PURE__ */ __name((id2) => {
    const subgraph = activeSubgraph.value;
    if (!id2.includes(":")) {
      return !subgraph ? id2 : void 0;
    } else if (!subgraph) {
      return;
    }
    const subgraphNodeIds = id2.split(":");
    const graph = app$1.rootGraph;
    const subgraphs = getSubgraphsFromInstanceIds2(graph, subgraphNodeIds);
    if (subgraphs.at(-1) === subgraph) {
      return subgraphNodeIds.at(-1);
    }
  }, "executionIdToCurrentId");
  watch(activeWorkflow, updateActiveGraph);
  const nodeIdToNodeLocatorId = /* @__PURE__ */ __name((nodeId, subgraph) => {
    const targetSubgraph = subgraph ?? activeSubgraph.value;
    if (!targetSubgraph) {
      return String(nodeId);
    }
    return createNodeLocatorId(targetSubgraph.id, nodeId);
  }, "nodeIdToNodeLocatorId");
  const nodeToNodeLocatorId = /* @__PURE__ */ __name((node2) => {
    if (isSubgraph(node2.graph))
      return createNodeLocatorId(node2.graph.id, node2.id);
    return String(node2.id);
  }, "nodeToNodeLocatorId");
  const nodeExecutionIdToNodeLocatorId = /* @__PURE__ */ __name((nodeExecutionId) => {
    if (!nodeExecutionId.includes(":")) {
      return nodeExecutionId;
    }
    const parts2 = parseNodeExecutionId(nodeExecutionId);
    if (!parts2 || parts2.length === 0) return null;
    const nodeId = parts2[parts2.length - 1];
    const subgraphNodeIds = parts2.slice(0, -1);
    if (subgraphNodeIds.length === 0) {
      return String(nodeId);
    }
    try {
      const subgraphs = getSubgraphsFromInstanceIds2(
        app$1.rootGraph,
        subgraphNodeIds.map((id2) => String(id2))
      );
      const immediateSubgraph = subgraphs[subgraphs.length - 1];
      return createNodeLocatorId(immediateSubgraph.id, nodeId);
    } catch {
      return null;
    }
  }, "nodeExecutionIdToNodeLocatorId");
  const nodeLocatorIdToNodeId = /* @__PURE__ */ __name((locatorId) => {
    const parsed = parseNodeLocatorId(locatorId);
    return parsed?.localNodeId ?? null;
  }, "nodeLocatorIdToNodeId");
  const nodeLocatorIdToNodeExecutionId = /* @__PURE__ */ __name((locatorId, targetSubgraph) => {
    const parsed = parseNodeLocatorId(locatorId);
    if (!parsed) return null;
    const { subgraphUuid, localNodeId } = parsed;
    if (!subgraphUuid) {
      return String(localNodeId);
    }
    const findSubgraphPath = /* @__PURE__ */ __name((graph, targetUuid, path2 = []) => {
      if (isSubgraph(graph) && graph.id === targetUuid) {
        return path2;
      }
      for (const node2 of graph._nodes) {
        if (node2.isSubgraphNode() && node2.subgraph) {
          const result = findSubgraphPath(node2.subgraph, targetUuid, [
            ...path2,
            node2.id
          ]);
          if (result) return result;
        }
      }
      return null;
    }, "findSubgraphPath");
    const path = findSubgraphPath(app$1.rootGraph, subgraphUuid);
    if (!path) return null;
    if (targetSubgraph && !path.some((_2, idx) => {
      const subgraphs = getSubgraphsFromInstanceIds2(
        app$1.rootGraph,
        path.slice(0, idx + 1).map((id2) => String(id2))
      );
      return subgraphs[subgraphs.length - 1] === targetSubgraph;
    })) {
      return null;
    }
    return createNodeExecutionId([...path, localNodeId]);
  }, "nodeLocatorIdToNodeExecutionId");
  return {
    activeWorkflow,
    attachWorkflow,
    isActive,
    openWorkflows,
    openedWorkflowIndexShift,
    getMostRecentWorkflow,
    openWorkflow,
    openWorkflowsInBackground,
    isOpen,
    isBusy,
    closeWorkflow,
    createTemporary,
    createNewTemporary,
    renameWorkflow,
    deleteWorkflow,
    saveAs,
    saveWorkflow,
    reorderWorkflows,
    workflows,
    bookmarkedWorkflows,
    persistedWorkflows,
    modifiedWorkflows,
    getWorkflowByPath,
    syncWorkflows,
    isSubgraphActive,
    activeSubgraph,
    updateActiveGraph,
    executionIdToCurrentId,
    nodeIdToNodeLocatorId,
    nodeToNodeLocatorId,
    nodeExecutionIdToNodeLocatorId,
    nodeLocatorIdToNodeId,
    nodeLocatorIdToNodeExecutionId
  };
});
const useWorkflowBookmarkStore = defineStore("workflowBookmark", () => {
  const bookmarks = ref(/* @__PURE__ */ new Set());
  const isBookmarked = /* @__PURE__ */ __name((path) => bookmarks.value.has(path), "isBookmarked");
  const loadBookmarks = /* @__PURE__ */ __name(async () => {
    const resp = await api.getUserData("workflows/.index.json");
    if (resp.status === 200) {
      const info = await resp.json();
      bookmarks.value = new Set(info?.favorites ?? []);
    }
  }, "loadBookmarks");
  const saveBookmarks = /* @__PURE__ */ __name(async () => {
    await api.storeUserData("workflows/.index.json", {
      favorites: Array.from(bookmarks.value)
    });
  }, "saveBookmarks");
  const setBookmarked = /* @__PURE__ */ __name(async (path, value) => {
    if (bookmarks.value.has(path) === value) return;
    if (value) {
      bookmarks.value.add(path);
    } else {
      bookmarks.value.delete(path);
    }
    await saveBookmarks();
  }, "setBookmarked");
  const toggleBookmarked = /* @__PURE__ */ __name(async (path) => {
    await setBookmarked(path, !bookmarks.value.has(path));
  }, "toggleBookmarked");
  return {
    isBookmarked,
    loadBookmarks,
    saveBookmarks,
    setBookmarked,
    toggleBookmarked
  };
});
const DEFAULT_MAX_SIZE = 50;
function useCachedRequest(requestFunction, options = {}) {
  const { maxSize = DEFAULT_MAX_SIZE, cacheKeyFn = paramsToCacheKey } = options;
  const cache = new QuickLRU({ maxSize });
  const pendingRequests = /* @__PURE__ */ new Map();
  const abortControllers = /* @__PURE__ */ new Map();
  const executeAndCacheCall = /* @__PURE__ */ __name(async (params, cacheKey) => {
    try {
      const controller = new AbortController();
      abortControllers.set(cacheKey, controller);
      const responsePromise = requestFunction(params, controller.signal);
      pendingRequests.set(cacheKey, responsePromise);
      const result = await responsePromise;
      cache.set(cacheKey, result);
      return result;
    } catch (err) {
      cache.set(cacheKey, null);
      return null;
    } finally {
      pendingRequests.delete(cacheKey);
      abortControllers.delete(cacheKey);
    }
  }, "executeAndCacheCall");
  const handlePendingRequest = /* @__PURE__ */ __name(async (pendingRequest) => {
    try {
      return await pendingRequest;
    } catch (err) {
      console.error("Error in pending request:", err);
      return null;
    }
  }, "handlePendingRequest");
  const abortAllRequests = /* @__PURE__ */ __name(() => {
    for (const controller of abortControllers.values()) {
      controller.abort();
    }
  }, "abortAllRequests");
  const cancel = /* @__PURE__ */ __name(() => {
    abortAllRequests();
    abortControllers.clear();
    pendingRequests.clear();
  }, "cancel");
  const call = /* @__PURE__ */ __name(async (params) => {
    const cacheKey = cacheKeyFn(params);
    const cachedResult = cache.get(cacheKey);
    if (cachedResult !== void 0) return cachedResult;
    const pendingRequest = pendingRequests.get(cacheKey);
    if (pendingRequest) return handlePendingRequest(pendingRequest);
    return executeAndCacheCall(params, cacheKey);
  }, "call");
  return {
    call,
    cancel,
    clear: /* @__PURE__ */ __name(() => cache.clear(), "clear")
  };
}
__name(useCachedRequest, "useCachedRequest");
const API_BASE_URL = "https://api.comfy.org";
const registryApiClient = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    "Content-Type": "application/json"
  },
  paramsSerializer: {
    // Disables PHP-style notation (e.g. param[]=value) in favor of repeated params (e.g. param=value1&param=value2)
    indexes: null
  }
});
const useComfyRegistryService = /* @__PURE__ */ __name(() => {
  const isLoading = ref(false);
  const error = ref(null);
  const handleApiError = /* @__PURE__ */ __name((err, context, routeSpecificErrors) => {
    if (!axios.isAxiosError(err))
      return err instanceof Error ? `${context}: ${err.message}` : `${context}: Unknown error occurred`;
    const axiosError = err;
    if (axiosError.response) {
      const { status, data } = axiosError.response;
      if (routeSpecificErrors && routeSpecificErrors[status])
        return routeSpecificErrors[status];
      switch (status) {
        case 400:
          return `Bad request: ${data?.message || "Invalid input"}`;
        case 401:
          return "Unauthorized: Authentication required";
        case 403:
          return `Forbidden: ${data?.message || "Access denied"}`;
        case 404:
          return `Not found: ${data?.message || "Resource not found"}`;
        case 409:
          return `Conflict: ${data?.message || "Resource conflict"}`;
        case 500:
          return `Server error: ${data?.message || "Internal server error"}`;
        default:
          return `${context}: ${data?.message || axiosError.message}`;
      }
    }
    return `${context}: ${axiosError.message}`;
  }, "handleApiError");
  const executeApiRequest = /* @__PURE__ */ __name(async (apiCall, errorContext, routeSpecificErrors) => {
    isLoading.value = true;
    error.value = null;
    try {
      const response = await apiCall();
      return response.data;
    } catch (err) {
      if (isAbortError(err)) return null;
      error.value = handleApiError(err, errorContext, routeSpecificErrors);
      return null;
    } finally {
      isLoading.value = false;
    }
  }, "executeApiRequest");
  const getNodeDefs = /* @__PURE__ */ __name(async (params, signal) => {
    const { packId, version: versionId, ...queryParams } = params;
    if (!packId || !versionId) return null;
    const endpoint = `/nodes/${packId}/versions/${versionId}/comfy-nodes`;
    const errorContext = "Failed to get node definitions";
    const routeSpecificErrors = {
      403: "This pack has been banned and its definition is not available",
      404: "The requested node, version, or comfy node does not exist"
    };
    return executeApiRequest(
      () => registryApiClient.get(endpoint, {
        params: queryParams,
        signal
      }),
      errorContext,
      routeSpecificErrors
    );
  }, "getNodeDefs");
  const search = /* @__PURE__ */ __name(async (params, signal) => {
    const endpoint = "/nodes/search";
    const errorContext = "Failed to perform search";
    return executeApiRequest(
      () => registryApiClient.get(endpoint, { params, signal }),
      errorContext
    );
  }, "search");
  const getPublisherById = /* @__PURE__ */ __name(async (publisherId, signal) => {
    const endpoint = `/publishers/${publisherId}`;
    const errorContext = "Failed to get publisher";
    const routeSpecificErrors = {
      404: `Publisher not found: The publisher with ID ${publisherId} does not exist`
    };
    return executeApiRequest(
      () => registryApiClient.get(endpoint, {
        signal
      }),
      errorContext,
      routeSpecificErrors
    );
  }, "getPublisherById");
  const listPacksForPublisher = /* @__PURE__ */ __name(async (publisherId, includeBanned, signal) => {
    const params = includeBanned ? { include_banned: true } : void 0;
    const endpoint = `/publishers/${publisherId}/nodes`;
    const errorContext = "Failed to list packs for publisher";
    const routeSpecificErrors = {
      400: "Bad request: Invalid input data",
      404: `Publisher not found: The publisher with ID ${publisherId} does not exist`
    };
    return executeApiRequest(
      () => registryApiClient.get(endpoint, {
        params,
        signal
      }),
      errorContext,
      routeSpecificErrors
    );
  }, "listPacksForPublisher");
  const postPackReview = /* @__PURE__ */ __name(async (packId, star, signal) => {
    const endpoint = `/nodes/${packId}/reviews`;
    const params = { star };
    const errorContext = "Failed to add review";
    const routeSpecificErrors = {
      400: "Bad request: Invalid review",
      404: `Pack not found: Pack with ID ${packId} does not exist`
    };
    return executeApiRequest(
      () => registryApiClient.post(endpoint, null, {
        params,
        signal
      }),
      errorContext,
      routeSpecificErrors
    );
  }, "postPackReview");
  const listAllPacks = /* @__PURE__ */ __name(async (params, signal) => {
    const endpoint = "/nodes";
    const errorContext = "Failed to list packs";
    return executeApiRequest(
      () => registryApiClient.get(endpoint, { params, signal }),
      errorContext
    );
  }, "listAllPacks");
  const getPackVersions = /* @__PURE__ */ __name(async (packId, params, signal) => {
    const endpoint = `/nodes/${packId}/versions`;
    const errorContext = "Failed to get pack versions";
    const routeSpecificErrors = {
      403: "This pack has been banned and its versions are not available",
      404: `Pack not found: Pack with ID ${packId} does not exist`
    };
    return executeApiRequest(
      () => registryApiClient.get(
        endpoint,
        { params, signal }
      ),
      errorContext,
      routeSpecificErrors
    );
  }, "getPackVersions");
  const getPackByVersion = /* @__PURE__ */ __name(async (packId, versionId, signal) => {
    const endpoint = `/nodes/${packId}/versions/${versionId}`;
    const errorContext = "Failed to get pack version";
    const routeSpecificErrors = {
      403: "This pack has been banned and its versions are not available",
      404: `Pack not found: Pack with ID ${packId} does not exist`
    };
    return executeApiRequest(
      () => registryApiClient.get(endpoint, {
        signal
      }),
      errorContext,
      routeSpecificErrors
    );
  }, "getPackByVersion");
  const getPackById = /* @__PURE__ */ __name(async (packId, signal) => {
    const endpoint = `/nodes/${packId}`;
    const errorContext = "Failed to get pack";
    const routeSpecificErrors = {
      404: `Pack not found: The pack with ID ${packId} does not exist`
    };
    return executeApiRequest(
      () => registryApiClient.get(endpoint, {
        signal
      }),
      errorContext,
      routeSpecificErrors
    );
  }, "getPackById");
  const inferPackFromNodeName = /* @__PURE__ */ __name(async (nodeName, signal) => {
    const endpoint = `/comfy-nodes/${nodeName}/node`;
    const errorContext = "Failed to infer pack from comfy node name";
    const routeSpecificErrors = {
      404: `Comfy node not found: The node with name ${nodeName} does not exist in the registry`
    };
    return executeApiRequest(
      () => registryApiClient.get(endpoint, {
        signal
      }),
      errorContext,
      routeSpecificErrors
    );
  }, "inferPackFromNodeName");
  const getBulkNodeVersions = /* @__PURE__ */ __name(async (nodeVersions, signal) => {
    const endpoint = "/bulk/nodes/versions";
    const errorContext = "Failed to get bulk node versions";
    const routeSpecificErrors = {
      400: "Bad request: Invalid node version identifiers provided"
    };
    const requestBody = {
      node_versions: nodeVersions
    };
    return executeApiRequest(
      () => registryApiClient.post(endpoint, requestBody, {
        signal
      }),
      errorContext,
      routeSpecificErrors
    );
  }, "getBulkNodeVersions");
  return {
    isLoading,
    error,
    listAllPacks,
    search,
    getPackById,
    getPackVersions,
    getPackByVersion,
    getPublisherById,
    listPacksForPublisher,
    getNodeDefs,
    postPackReview,
    inferPackFromNodeName,
    getBulkNodeVersions
  };
}, "useComfyRegistryService");
const PACK_LIST_CACHE_SIZE = 20;
const PACK_BY_ID_CACHE_SIZE = 64;
const isNodePack = /* @__PURE__ */ __name((pack) => {
  return pack !== void 0 && "id" in pack;
}, "isNodePack");
const useComfyRegistryStore = defineStore("comfyRegistry", () => {
  const registryService = useComfyRegistryService();
  let getPacksByIdController = null;
  const getPacksByIdCache = new QuickLRU({
    maxSize: PACK_BY_ID_CACHE_SIZE
  });
  const listAllPacks = useCachedRequest(
    registryService.listAllPacks,
    { maxSize: PACK_LIST_CACHE_SIZE }
  );
  const getPackById = useCachedRequest(
    async (params) => {
      if (!params) return null;
      return registryService.getPackById(params);
    },
    { maxSize: PACK_BY_ID_CACHE_SIZE }
  );
  const getPacksByIds = /* @__PURE__ */ __name(async (ids) => {
    const [cachedPacksIds, uncachedPacksIds] = partition(
      ids,
      (id2) => getPacksByIdCache.has(id2)
    );
    const resolvedPacks = cachedPacksIds.map((id2) => getPacksByIdCache.get(id2)).filter(isNodePack);
    if (uncachedPacksIds.length) {
      getPacksByIdController = new AbortController();
      const uncachedPacks = await registryService.listAllPacks(
        {
          node_id: uncachedPacksIds.filter(
            (id2) => id2 !== void 0
          )
        },
        getPacksByIdController.signal
      );
      const { nodes = [] } = uncachedPacks ?? {};
      nodes.forEach((pack) => {
        if (pack?.id) {
          getPacksByIdCache.set(pack.id, pack);
          resolvedPacks.push(pack);
        }
      });
    }
    return resolvedPacks;
  }, "getPacksByIds");
  const getNodeDefs = useCachedRequest(registryService.getNodeDefs, { maxSize: PACK_BY_ID_CACHE_SIZE });
  const search = useCachedRequest(registryService.search, { maxSize: PACK_LIST_CACHE_SIZE });
  const inferPackFromNodeName = useCachedRequest(registryService.inferPackFromNodeName, { maxSize: PACK_BY_ID_CACHE_SIZE });
  const clearCache = /* @__PURE__ */ __name(() => {
    getNodeDefs.clear();
    listAllPacks.clear();
    getPackById.clear();
    inferPackFromNodeName.clear();
  }, "clearCache");
  const cancelRequests = /* @__PURE__ */ __name(() => {
    getNodeDefs.cancel();
    listAllPacks.cancel();
    getPackById.cancel();
    inferPackFromNodeName.cancel();
    getPacksByIdController?.abort();
  }, "cancelRequests");
  return {
    listAllPacks,
    getPackById,
    getPacksByIds: {
      call: getPacksByIds,
      cancel: /* @__PURE__ */ __name(() => getPacksByIdController?.abort(), "cancel")
    },
    getNodeDefs,
    search,
    inferPackFromNodeName,
    clearCache,
    cancelRequests,
    isLoading: registryService.isLoading,
    error: registryService.error
  };
});
const useNodePacks = /* @__PURE__ */ __name((packsIds, options = {}) => {
  const { immediate = false } = options;
  const { getPacksByIds } = useComfyRegistryStore();
  const fetchPacks = /* @__PURE__ */ __name(() => getPacksByIds.call(get$1(packsIds).filter(Boolean)), "fetchPacks");
  const {
    isReady,
    isLoading,
    error,
    execute,
    state: nodePacks
  } = useAsyncState(fetchPacks, [], {
    immediate
  });
  const cleanup = /* @__PURE__ */ __name(() => {
    getPacksByIds.cancel();
    isReady.value = false;
    isLoading.value = false;
  }, "cleanup");
  return {
    error,
    isLoading,
    isReady,
    nodePacks,
    startFetch: execute,
    cleanup
  };
}, "useNodePacks");
const CORE_NODES_PACK_NAME = "comfy-core";
const useWorkflowPacks = /* @__PURE__ */ __name((options = {}) => {
  const nodeDefStore = useNodeDefStore();
  const systemStatsStore = useSystemStatsStore();
  const { inferPackFromNodeName } = useComfyRegistryStore();
  const workflowPacks = ref([]);
  const getWorkflowNodePackId = /* @__PURE__ */ __name((node2) => {
    if (typeof node2.properties?.cnr_id === "string") {
      return node2.properties.cnr_id;
    }
    if (typeof node2.properties?.aux_id === "string") {
      return node2.properties.aux_id;
    }
    return void 0;
  }, "getWorkflowNodePackId");
  const cleanVersionString = /* @__PURE__ */ __name((version) => version.replace(/^v/, "").trim(), "cleanVersionString");
  const inferPack = /* @__PURE__ */ __name(async (node2) => {
    const nodeName = node2.type;
    const nodeDef = nodeDefStore.nodeDefsByName[nodeName];
    if (nodeDef?.nodeSource.type === "core") {
      if (!systemStatsStore.systemStats) {
        await systemStatsStore.refetchSystemStats();
      }
      return {
        id: CORE_NODES_PACK_NAME,
        version: systemStatsStore.systemStats?.system?.comfyui_version ?? "nightly"
      };
    }
    const pack = await inferPackFromNodeName.call(nodeName);
    if (pack) {
      return {
        id: pack.id,
        version: pack.latest_version?.version ?? "nightly"
      };
    }
    return void 0;
  }, "inferPack");
  const workflowNodeToPack = /* @__PURE__ */ __name(async (node2) => {
    const packId = getWorkflowNodePackId(node2);
    if (!packId) return inferPack(node2);
    if (packId === CORE_NODES_PACK_NAME) return void 0;
    const version = typeof node2.properties.ver === "string" ? cleanVersionString(node2.properties.ver) : void 0;
    return {
      id: packId,
      version
    };
  }, "workflowNodeToPack");
  const getWorkflowPacks = /* @__PURE__ */ __name(async () => {
    if (!app$1.rootGraph) return [];
    const packPromises = mapAllNodes(app$1.rootGraph, workflowNodeToPack);
    const packs = await Promise.all(packPromises);
    workflowPacks.value = packs.filter((pack) => pack !== void 0);
  }, "getWorkflowPacks");
  const packsToUniqueIds = /* @__PURE__ */ __name((packs) => packs.reduce((acc, pack) => {
    if (pack?.id) acc.add(pack.id);
    return acc;
  }, /* @__PURE__ */ new Set()), "packsToUniqueIds");
  const workflowPacksIds = computed(
    () => Array.from(packsToUniqueIds(workflowPacks.value))
  );
  const { startFetch, cleanup, error, isLoading, nodePacks, isReady } = useNodePacks(workflowPacksIds, options);
  const isIdInWorkflow = /* @__PURE__ */ __name((packId) => workflowPacksIds.value.includes(packId), "isIdInWorkflow");
  const filterWorkflowPack = /* @__PURE__ */ __name((packs) => packs.filter((pack) => !!pack.id && isIdInWorkflow(pack.id)), "filterWorkflowPack");
  onUnmounted(() => {
    cleanup();
  });
  return {
    error,
    isLoading,
    isReady,
    workflowPacks: nodePacks,
    startFetchWorkflowPacks: /* @__PURE__ */ __name(async () => {
      await getWorkflowPacks();
      await startFetch();
    }, "startFetchWorkflowPacks"),
    filterWorkflowPack
  };
}, "useWorkflowPacks");
const LOGS_MESSAGE_TYPE = "logs";
const MANAGER_WS_TASK_DONE_NAME$1 = "cm-task-completed";
const MANAGER_WS_TASK_STARTED_NAME$1 = "cm-task-started";
const useServerLogs = /* @__PURE__ */ __name((options = {}) => {
  const {
    ui_id,
    immediate = false,
    messageFilter = /* @__PURE__ */ __name((msg) => Boolean(msg.trim()), "messageFilter")
  } = options;
  const logs = ref([]);
  const isTaskStarted = ref(!ui_id);
  let stopLogs = null;
  let stopTaskDone = null;
  let stopTaskStarted = null;
  const isValidLogEvent = /* @__PURE__ */ __name((event) => event?.type === LOGS_MESSAGE_TYPE && event.detail?.entries?.length > 0, "isValidLogEvent");
  const parseLogMessage = /* @__PURE__ */ __name((event) => event.detail.entries.map((e2) => e2.m).filter(messageFilter), "parseLogMessage");
  const handleLogMessage = /* @__PURE__ */ __name((event) => {
    if (!isTaskStarted.value) return;
    if (isValidLogEvent(event)) {
      const messages2 = parseLogMessage(event);
      if (messages2.length > 0) {
        logs.value.push(...messages2);
      }
    }
  }, "handleLogMessage");
  const handleTaskStarted = /* @__PURE__ */ __name((event) => {
    if (ui_id && event?.detail?.ui_id === ui_id) {
      isTaskStarted.value = true;
    }
  }, "handleTaskStarted");
  const handleTaskDone = /* @__PURE__ */ __name((event) => {
    if (ui_id && event?.detail?.ui_id === ui_id) {
      isTaskStarted.value = false;
    }
  }, "handleTaskDone");
  const start = /* @__PURE__ */ __name(async () => {
    await api.subscribeLogs(true);
    stopLogs = useEventListener(api, LOGS_MESSAGE_TYPE, handleLogMessage);
    if (ui_id) {
      stopTaskStarted = useEventListener(
        api,
        MANAGER_WS_TASK_STARTED_NAME$1,
        handleTaskStarted
      );
      stopTaskDone = useEventListener(
        api,
        MANAGER_WS_TASK_DONE_NAME$1,
        handleTaskDone
      );
    }
  }, "start");
  const stopListening = /* @__PURE__ */ __name(async () => {
    stopLogs?.();
    stopTaskStarted?.();
    stopTaskDone?.();
    stopLogs = null;
    stopTaskStarted = null;
    stopTaskDone = null;
    await api.subscribeLogs(false);
  }, "stopListening");
  if (immediate) {
    void start();
  }
  onUnmounted(async () => {
    await stopListening();
    logs.value = [];
  });
  return {
    logs,
    startListening: start,
    stopListening
  };
}, "useServerLogs");
const MANAGER_WS_TASK_DONE_NAME = "cm-task-completed";
const MANAGER_WS_TASK_STARTED_NAME = "cm-task-started";
const useManagerQueue = /* @__PURE__ */ __name((taskHistory, taskQueue, installedPacks) => {
  const { showManagerProgressDialog } = useDialogService();
  const maxHistoryItems = ref(64);
  const isLoading = ref(false);
  const isProcessing = ref(false);
  const currentQueueLength = computed(
    () => taskQueue.value.running_queue.length + taskQueue.value.pending_queue.length
  );
  const updateProcessingState = /* @__PURE__ */ __name(() => {
    isProcessing.value = currentQueueLength.value > 0;
  }, "updateProcessingState");
  const allTasksDone = computed(() => currentQueueLength.value === 0);
  const historyCount = computed(() => Object.keys(taskHistory.value).length);
  const isTaskFromThisClient = /* @__PURE__ */ __name((task) => task.client_id === app$1.api.clientId, "isTaskFromThisClient");
  const isHistoryTaskFromThisClient = /* @__PURE__ */ __name((task) => task.client_id === app$1.api.clientId, "isHistoryTaskFromThisClient");
  const filterQueueByClientId = /* @__PURE__ */ __name((tasks) => tasks.filter(isTaskFromThisClient), "filterQueueByClientId");
  const filterHistoryByClientId = /* @__PURE__ */ __name((history) => pickBy(history, isHistoryTaskFromThisClient), "filterHistoryByClientId");
  const updateTaskState = /* @__PURE__ */ __name((state2) => {
    taskQueue.value.running_queue = filterQueueByClientId(state2.running_queue);
    taskQueue.value.pending_queue = filterQueueByClientId(state2.pending_queue);
    taskHistory.value = filterHistoryByClientId(state2.history);
    if (state2.installed_packs) {
      installedPacks.value = normalizePackKeys(state2.installed_packs);
    }
    updateProcessingState();
  }, "updateTaskState");
  const cleanupTaskDoneListener = useEventListener(
    app$1.api,
    MANAGER_WS_TASK_DONE_NAME,
    (event) => {
      if (event?.type === MANAGER_WS_TASK_DONE_NAME && event.detail?.state) {
        updateTaskState(event.detail.state);
        if (allTasksDone.value) {
          setTimeout(() => {
            if (allTasksDone.value) {
              showManagerProgressDialog();
            }
          }, 1e3);
        }
      }
    }
  );
  const cleanupTaskStartedListener = useEventListener(
    app$1.api,
    MANAGER_WS_TASK_STARTED_NAME,
    (event) => {
      if (event?.type === MANAGER_WS_TASK_STARTED_NAME && event.detail?.state) {
        updateTaskState(event.detail.state);
        showManagerProgressDialog();
      }
    }
  );
  const cleanup = /* @__PURE__ */ __name(() => {
    cleanupTaskDoneListener();
    cleanupTaskStartedListener();
    isProcessing.value = false;
    isLoading.value = false;
  }, "cleanup");
  return {
    // State
    isLoading,
    isProcessing,
    maxHistoryItems,
    // Computed
    allTasksDone,
    historyCount,
    currentQueueLength,
    // Actions
    updateTaskState,
    cleanup
  };
}, "useManagerQueue");
const isMergedNodePack = /* @__PURE__ */ __name((nodePack) => "comfy_nodes" in nodePack, "isMergedNodePack");
const IsInstallingKey = /* @__PURE__ */ Symbol("isInstalling");
var ManagerTab = /* @__PURE__ */ ((ManagerTab2) => {
  ManagerTab2["All"] = "all";
  ManagerTab2["Installed"] = "installed";
  ManagerTab2["Workflow"] = "workflow";
  ManagerTab2["Missing"] = "missing";
  ManagerTab2["UpdateAvailable"] = "updateAvailable";
  return ManagerTab2;
})(ManagerTab || {});
var SortableAlgoliaField = /* @__PURE__ */ ((SortableAlgoliaField2) => {
  SortableAlgoliaField2["Downloads"] = "total_install";
  SortableAlgoliaField2["Created"] = "create_time";
  SortableAlgoliaField2["Updated"] = "update_time";
  SortableAlgoliaField2["Publisher"] = "publisher_id";
  SortableAlgoliaField2["Name"] = "name";
  return SortableAlgoliaField2;
})(SortableAlgoliaField || {});
var ManagerUIState = /* @__PURE__ */ ((ManagerUIState2) => {
  ManagerUIState2["DISABLED"] = "disabled";
  ManagerUIState2["LEGACY_UI"] = "legacy";
  ManagerUIState2["NEW_UI"] = "new";
  return ManagerUIState2;
})(ManagerUIState || {});
function useManagerState() {
  const systemStatsStore = useSystemStatsStore();
  const { systemStats, isInitialized: systemInitialized } = storeToRefs(systemStatsStore);
  const managerUIState = readonly(
    computed(() => {
      if (!systemInitialized.value) {
        return "disabled";
      }
      const clientSupportsV4 = api.getClientFeatureFlags().supports_manager_v4_ui ?? false;
      const serverSupportsV4 = api.getServerFeature(
        "extension.manager.supports_v4"
      );
      const hasEnableManager = systemStats.value?.system?.argv?.includes("--enable-manager");
      if (!hasEnableManager) {
        return "disabled";
      }
      if (systemStats.value?.system?.argv?.includes("--enable-manager-legacy-ui")) {
        return "legacy";
      }
      if (clientSupportsV4 && serverSupportsV4 === true) {
        return "new";
      }
      if (serverSupportsV4 === true && !clientSupportsV4) {
        return "legacy";
      }
      if (serverSupportsV4 === false) {
        return "legacy";
      }
      if (serverSupportsV4 === void 0) {
        return "new";
      }
      return "disabled";
    })
  );
  const isManagerEnabled = readonly(
    computed(() => {
      return managerUIState.value !== "disabled";
    })
  );
  const isNewManagerUI = readonly(
    computed(() => {
      return managerUIState.value === "new";
    })
  );
  const isLegacyManagerUI = readonly(
    computed(() => {
      return managerUIState.value === "legacy";
    })
  );
  const shouldShowInstallButton = readonly(
    computed(() => {
      return isNewManagerUI.value;
    })
  );
  const shouldShowManagerButtons = readonly(
    computed(() => {
      return isManagerEnabled.value;
    })
  );
  const openManager = /* @__PURE__ */ __name(async (options) => {
    const state2 = managerUIState.value;
    const dialogService = useDialogService();
    const commandStore = useCommandStore();
    switch (state2) {
      case "disabled":
        dialogService.showSettingsDialog("extension");
        break;
      case "legacy": {
        const command = options?.legacyCommand || "Comfy.Manager.Menu.ToggleVisibility";
        try {
          await commandStore.execute(command);
        } catch {
          if (options?.showToastOnLegacyError !== false) {
            useToastStore().add({
              severity: "error",
              summary: t("g.error"),
              detail: t("manager.legacyMenuNotAvailable"),
              life: 3e3
            });
          }
          if (options?.showToastOnLegacyError === false) {
            dialogService.showSettingsDialog("extension");
          }
        }
        break;
      }
      case "new":
        if (options?.isLegacyOnly) {
          useToastStore().add({
            severity: "error",
            summary: t("g.error"),
            detail: t("manager.legacyMenuNotAvailable"),
            life: 3e3
          });
          dialogService.showManagerDialog({ initialTab: ManagerTab.All });
        } else {
          dialogService.showManagerDialog(
            options?.initialTab ? { initialTab: options.initialTab } : void 0
          );
        }
        break;
    }
  }, "openManager");
  return {
    managerUIState,
    isManagerEnabled,
    isNewManagerUI,
    isLegacyManagerUI,
    shouldShowInstallButton,
    shouldShowManagerButtons,
    openManager
  };
}
__name(useManagerState, "useManagerState");
const GENERIC_SECURITY_ERR_MSG = "Forbidden: A security error has occurred. Please check the terminal logs";
const managerApiClient = axios.create({
  baseURL: api.apiURL("/v2/"),
  headers: {
    "Content-Type": "application/json"
  }
});
const useComfyManagerService = /* @__PURE__ */ __name(() => {
  const isLoading = ref(false);
  const error = ref(null);
  const isManagerServiceAvailable = /* @__PURE__ */ __name(() => {
    const managerState = useManagerState();
    return managerState.isNewManagerUI.value;
  }, "isManagerServiceAvailable");
  const handleRequestError = /* @__PURE__ */ __name((err, context, routeSpecificErrors) => {
    if (isAbortError(err)) return;
    let message;
    if (!axios.isAxiosError(err)) {
      message = `${context} failed: ${err instanceof Error ? err.message : String(err)}`;
    } else {
      const axiosError = err;
      const status = axiosError.response?.status;
      if (status && routeSpecificErrors?.[status]) {
        message = routeSpecificErrors[status];
      } else if (status === 404) {
        message = "Could not connect to ComfyUI-Manager";
      } else {
        message = axiosError.response?.data?.message ?? `${context} failed with status ${status}`;
      }
    }
    error.value = message;
  }, "handleRequestError");
  const executeRequest = /* @__PURE__ */ __name(async (requestCall, options) => {
    const { errorContext, routeSpecificErrors, isQueueOperation } = options;
    if (!isManagerServiceAvailable()) {
      error.value = "Manager service is not available in current mode";
      return null;
    }
    isLoading.value = true;
    error.value = null;
    try {
      const response = await requestCall();
      if (isQueueOperation) await startQueue();
      return response.data;
    } catch (err) {
      handleRequestError(err, errorContext, routeSpecificErrors);
      return null;
    } finally {
      isLoading.value = false;
    }
  }, "executeRequest");
  const startQueue = /* @__PURE__ */ __name(async (signal) => {
    const errorContext = "Starting ComfyUI-Manager job queue";
    const routeSpecificErrors = {
      201: "Created: ComfyUI-Manager job queue is already running"
    };
    return executeRequest(
      () => managerApiClient.get("manager/queue/start", { signal }),
      { errorContext, routeSpecificErrors }
    );
  }, "startQueue");
  const getQueueStatus = /* @__PURE__ */ __name(async (client_id, signal) => {
    const errorContext = "Getting ComfyUI-Manager queue status";
    return executeRequest(
      () => managerApiClient.get("manager/queue/status", {
        params: client_id ? { client_id } : void 0,
        signal
      }),
      { errorContext }
    );
  }, "getQueueStatus");
  const listInstalledPacks = /* @__PURE__ */ __name(async (signal) => {
    const errorContext = "Fetching installed packs";
    return executeRequest(
      () => managerApiClient.get("customnode/installed", { signal }),
      { errorContext }
    );
  }, "listInstalledPacks");
  const getImportFailInfo = /* @__PURE__ */ __name(async (signal) => {
    const errorContext = "Fetching import failure information";
    return executeRequest(
      () => managerApiClient.get("customnode/import_fail_info", { signal }),
      { errorContext }
    );
  }, "getImportFailInfo");
  const getImportFailInfoBulk = /* @__PURE__ */ __name(async (params = {}, signal) => {
    const errorContext = "Fetching bulk import failure information";
    if (!params.cnr_ids?.length && !params.urls?.length) {
      return {};
    }
    return executeRequest(
      () => managerApiClient.post("customnode/import_fail_info_bulk", params, {
        signal
      }),
      { errorContext }
    );
  }, "getImportFailInfoBulk");
  const queueTask = /* @__PURE__ */ __name(async (kind, params, ui_id, signal) => {
    const task = {
      kind,
      params,
      ui_id: ui_id || v4(),
      client_id: api.clientId ?? api.initialClientId ?? "unknown"
    };
    const errorContext = `Queueing ${task.kind} task`;
    const routeSpecificErrors = {
      403: GENERIC_SECURITY_ERR_MSG,
      404: `Not Found: Task could not be queued`
    };
    return executeRequest(
      () => managerApiClient.post("manager/queue/task", task, { signal }),
      { errorContext, routeSpecificErrors, isQueueOperation: true }
    );
  }, "queueTask");
  const installPack = /* @__PURE__ */ __name(async (params, ui_id, signal) => {
    return queueTask("install", params, ui_id, signal);
  }, "installPack");
  const uninstallPack = /* @__PURE__ */ __name(async (params, ui_id, signal) => {
    return queueTask("uninstall", params, ui_id, signal);
  }, "uninstallPack");
  const disablePack = /* @__PURE__ */ __name(async (params, ui_id, signal) => {
    return queueTask("disable", params, ui_id, signal);
  }, "disablePack");
  const enablePack = /* @__PURE__ */ __name(async (params, ui_id, signal) => {
    return queueTask("enable", params, ui_id, signal);
  }, "enablePack");
  const updatePack = /* @__PURE__ */ __name(async (params, ui_id, signal) => {
    return queueTask("update", params, ui_id, signal);
  }, "updatePack");
  const updateAllPacks = /* @__PURE__ */ __name(async (params = {}, ui_id, signal) => {
    const errorContext = "Updating all packs";
    const routeSpecificErrors = {
      403: "Forbidden: To use this action, a security_level of `middle or below` is required",
      401: "Unauthorized: ComfyUI-Manager job queue is busy"
    };
    const queryParams = {
      mode: params.mode,
      client_id: api.clientId ?? api.initialClientId ?? "unknown",
      ui_id: ui_id || v4()
    };
    return executeRequest(
      () => managerApiClient.get("manager/queue/update_all", {
        params: queryParams,
        signal
      }),
      { errorContext, routeSpecificErrors, isQueueOperation: true }
    );
  }, "updateAllPacks");
  const updateComfyUI = /* @__PURE__ */ __name(async (params = { is_stable: true }, ui_id, signal) => {
    const errorContext = "Updating ComfyUI";
    const routeSpecificErrors = {
      400: "Bad Request: Missing required parameters",
      403: "Forbidden: To use this action, a security_level of `middle or below` is required"
    };
    const queryParams = {
      client_id: api.clientId ?? api.initialClientId ?? "unknown",
      ui_id: ui_id || v4(),
      ...params
    };
    return executeRequest(
      () => managerApiClient.get("manager/queue/update_comfyui", {
        params: queryParams,
        signal
      }),
      { errorContext, routeSpecificErrors, isQueueOperation: true }
    );
  }, "updateComfyUI");
  const rebootComfyUI = /* @__PURE__ */ __name(async (signal) => {
    const errorContext = "Rebooting ComfyUI";
    const routeSpecificErrors = {
      403: "Forbidden: Rebooting ComfyUI requires security_level of middle or below"
    };
    return executeRequest(
      () => managerApiClient.get("manager/reboot", { signal }),
      { errorContext, routeSpecificErrors }
    );
  }, "rebootComfyUI");
  const isLegacyManagerUI = /* @__PURE__ */ __name(async (signal) => {
    const errorContext = "Checking if user set Manager to use the legacy UI";
    return executeRequest(
      () => managerApiClient.get("manager/is_legacy_manager_ui", { signal }),
      { errorContext }
    );
  }, "isLegacyManagerUI");
  const getTaskHistory = /* @__PURE__ */ __name(async (options = {}, signal) => {
    const errorContext = "Getting ComfyUI-Manager task history";
    return executeRequest(
      () => managerApiClient.get("manager/queue/history", {
        params: options,
        signal
      }),
      { errorContext }
    );
  }, "getTaskHistory");
  return {
    // State
    isLoading,
    error,
    // Queue operations
    startQueue,
    getQueueStatus,
    getTaskHistory,
    // Pack management
    listInstalledPacks,
    getImportFailInfo,
    getImportFailInfoBulk,
    installPack,
    uninstallPack,
    enablePack,
    disablePack,
    updatePack,
    updateAllPacks,
    // System operations
    updateComfyUI,
    rebootComfyUI,
    isLegacyManagerUI
  };
}, "useComfyManagerService");
const useComfyManagerStore = defineStore("comfyManager", () => {
  const { t: t2 } = useI18n();
  const managerService = useComfyManagerService();
  const { showManagerProgressDialog } = useDialogService();
  const installedPacks = ref({});
  const enabledPacksIds = ref(/* @__PURE__ */ new Set());
  const disabledPacksIds = ref(/* @__PURE__ */ new Set());
  const installedPacksIds = ref(/* @__PURE__ */ new Set());
  const installingPacksIds = ref(/* @__PURE__ */ new Set());
  const isStale2 = ref(true);
  const taskLogs = ref([]);
  const succeededTasksLogs = ref([]);
  const failedTasksLogs = ref([]);
  const taskHistory = ref({});
  const succeededTasksIds = ref([]);
  const failedTasksIds = ref([]);
  const taskQueue = ref({
    history: {},
    running_queue: [],
    pending_queue: [],
    installed_packs: {}
  });
  const taskIdToPackId = ref(/* @__PURE__ */ new Map());
  const managerQueue = useManagerQueue(taskHistory, taskQueue, installedPacks);
  useEventListener(app$1.api, "cm-task-completed", (event) => {
    const taskId = event.detail?.ui_id;
    if (taskId && taskIdToPackId.value.has(taskId)) {
      const packId = taskIdToPackId.value.get(taskId);
      installingPacksIds.value.delete(packId);
      taskIdToPackId.value.delete(taskId);
    }
  });
  const setStale = /* @__PURE__ */ __name(() => {
    isStale2.value = true;
  }, "setStale");
  const partitionTaskLogs = /* @__PURE__ */ __name(() => {
    const successTaskLogs = [];
    const failTaskLogs = [];
    for (const log2 of taskLogs.value) {
      if (failedTasksIds.value.includes(log2.taskId)) {
        failTaskLogs.push(log2);
      } else {
        successTaskLogs.push(log2);
      }
    }
    succeededTasksLogs.value = successTaskLogs;
    failedTasksLogs.value = failTaskLogs;
  }, "partitionTaskLogs");
  const partitionTasks = /* @__PURE__ */ __name(() => {
    const successTasksIds = [];
    const failTasksIds = [];
    for (const task of Object.values(taskHistory.value)) {
      if (task.status?.status_str === "success") {
        successTasksIds.push(task.ui_id);
      } else {
        failTasksIds.push(task.ui_id);
      }
    }
    succeededTasksIds.value = successTasksIds;
    failedTasksIds.value = failTasksIds;
  }, "partitionTasks");
  whenever(
    taskHistory,
    () => {
      partitionTasks();
      partitionTaskLogs();
    },
    { deep: true }
  );
  const getPackId = /* @__PURE__ */ __name((pack) => pack.cnr_id || pack.aux_id, "getPackId");
  const isInstalledPackId = /* @__PURE__ */ __name((packName) => !!packName && installedPacksIds.value.has(packName), "isInstalledPackId");
  const isEnabledPackId = /* @__PURE__ */ __name((packName) => !!packName && isInstalledPackId(packName) && enabledPacksIds.value.has(packName), "isEnabledPackId");
  const isInstallingPackId = /* @__PURE__ */ __name((packName) => !!packName && installingPacksIds.value.has(packName), "isInstallingPackId");
  const packsToIdSet = /* @__PURE__ */ __name((packs) => packs.reduce((acc, pack) => {
    const id2 = pack.cnr_id || pack.aux_id;
    if (id2) acc.add(id2);
    return acc;
  }, /* @__PURE__ */ new Set()), "packsToIdSet");
  const updateDisabledIds = /* @__PURE__ */ __name((packs) => {
    const enabledIds = /* @__PURE__ */ new Set();
    const disabledIds = /* @__PURE__ */ new Set();
    for (const pack of packs) {
      const id2 = getPackId(pack);
      if (!id2) continue;
      const { enabled } = pack;
      if (enabled === true) enabledIds.add(id2);
      else if (enabled === false) disabledIds.add(id2);
      const inBothSets = enabledIds.has(id2) && disabledIds.has(id2);
      if (inBothSets) disabledIds.delete(id2);
    }
    enabledPacksIds.value = enabledIds;
    disabledPacksIds.value = disabledIds;
  }, "updateDisabledIds");
  const updateInstalledIds = /* @__PURE__ */ __name((packs) => {
    installedPacksIds.value = packsToIdSet(packs);
  }, "updateInstalledIds");
  const onPacksChanged = /* @__PURE__ */ __name(() => {
    const packs = Object.values(installedPacks.value);
    updateDisabledIds(packs);
    updateInstalledIds(packs);
  }, "onPacksChanged");
  watch(installedPacks, onPacksChanged, { deep: true });
  const refreshInstalledList = /* @__PURE__ */ __name(async () => {
    const packs = await managerService.listInstalledPacks();
    if (packs) {
      installedPacks.value = normalizePackKeys(packs);
    }
    isStale2.value = false;
  }, "refreshInstalledList");
  whenever(isStale2, refreshInstalledList, { immediate: true });
  const enqueueTaskWithLogs = /* @__PURE__ */ __name(async (task, taskName) => {
    const taskId = v4();
    const { logs } = useServerLogs({
      ui_id: taskId,
      immediate: true
    });
    try {
      showManagerProgressDialog();
      managerQueue.isProcessing.value = true;
      taskLogs.value.push({ taskName, taskId, logs: logs.value });
      await task(taskId);
    } catch (error) {
      managerQueue.isProcessing.value = false;
      taskHistory.value[taskId] = {
        ui_id: taskId,
        client_id: api.clientId || "unknown",
        kind: "error",
        result: "failed",
        status: {
          status_str: "error",
          completed: false,
          messages: [error instanceof Error ? error.message : String(error)]
        },
        timestamp: (/* @__PURE__ */ new Date()).toISOString()
      };
    }
  }, "enqueueTaskWithLogs");
  const installPack = useCachedRequest(
    async (params, signal) => {
      if (!params.id) return;
      let actionDescription = t2("g.installing");
      if (installedPacksIds.value.has(params.id)) {
        const installedPack = installedPacks.value[params.id];
        if (installedPack && installedPack.ver !== params.selected_version) {
          actionDescription = t2("manager.changingVersion", {
            from: installedPack.ver,
            to: params.selected_version
          });
        } else {
          actionDescription = t2("g.enabling");
        }
      }
      installingPacksIds.value.add(params.id);
      const task = /* @__PURE__ */ __name((taskId) => {
        taskIdToPackId.value.set(taskId, params.id);
        return managerService.installPack(params, taskId, signal);
      }, "task");
      await enqueueTaskWithLogs(task, `${actionDescription} ${params.id}`);
    },
    { maxSize: 1 }
  );
  const uninstallPack = /* @__PURE__ */ __name(async (params, signal) => {
    installPack.clear();
    installPack.cancel();
    installingPacksIds.value.add(params.id);
    const uninstallParams = {
      node_name: params.id,
      is_unknown: false
    };
    const task = /* @__PURE__ */ __name((taskId) => {
      taskIdToPackId.value.set(taskId, params.id);
      return managerService.uninstallPack(uninstallParams, taskId, signal);
    }, "task");
    await enqueueTaskWithLogs(
      task,
      t2("manager.uninstalling", { id: params.id })
    );
  }, "uninstallPack");
  const updatePack = useCachedRequest(
    async (params, signal) => {
      updateAllPacks.cancel();
      const updateParams = {
        node_name: params.id,
        node_ver: params.version
      };
      const task = /* @__PURE__ */ __name((taskId) => managerService.updatePack(updateParams, taskId, signal), "task");
      await enqueueTaskWithLogs(task, t2("g.updating", { id: params.id }));
    },
    { maxSize: 1 }
  );
  const updateAllPacks = useCachedRequest(
    async (params, signal) => {
      const task = /* @__PURE__ */ __name((taskId) => managerService.updateAllPacks(params, taskId, signal), "task");
      await enqueueTaskWithLogs(task, t2("manager.updatingAllPacks"));
    },
    { maxSize: 1 }
  );
  const disablePack = /* @__PURE__ */ __name(async (params, signal) => {
    const disableParams = {
      node_name: params.id,
      is_unknown: false
    };
    const task = /* @__PURE__ */ __name((taskId) => managerService.disablePack(disableParams, taskId, signal), "task");
    await enqueueTaskWithLogs(task, t2("g.disabling", { id: params.id }));
  }, "disablePack");
  const enablePack = /* @__PURE__ */ __name(async (params, signal) => {
    const enableParams = {
      cnr_id: params.id
    };
    const task = /* @__PURE__ */ __name((taskId) => managerService.enablePack(enableParams, taskId, signal), "task");
    await enqueueTaskWithLogs(task, t2("g.enabling", { id: params.id }));
  }, "enablePack");
  const getInstalledPackVersion = /* @__PURE__ */ __name((packId) => {
    const pack = installedPacks.value[packId];
    return pack?.ver;
  }, "getInstalledPackVersion");
  const clearLogs = /* @__PURE__ */ __name(() => {
    taskLogs.value = [];
  }, "clearLogs");
  const resetTaskState = /* @__PURE__ */ __name(() => {
    taskLogs.value = [];
    taskHistory.value = {};
    succeededTasksIds.value = [];
    failedTasksIds.value = [];
    succeededTasksLogs.value = [];
    failedTasksLogs.value = [];
    installingPacksIds.value.clear();
    taskIdToPackId.value.clear();
    taskQueue.value = {
      history: {},
      running_queue: [],
      pending_queue: [],
      installed_packs: {}
    };
  }, "resetTaskState");
  return {
    // Manager state
    isLoading: managerService.isLoading,
    error: managerService.error,
    taskLogs,
    clearLogs,
    resetTaskState,
    setStale,
    // Installed packs state
    installedPacks,
    installedPacksIds,
    isPackInstalled: isInstalledPackId,
    isPackEnabled: isEnabledPackId,
    isPackInstalling: isInstallingPackId,
    getInstalledPackVersion,
    refreshInstalledList,
    // Task queue state and actions
    taskHistory,
    taskQueue,
    isProcessingTasks: managerQueue.isProcessing,
    succeededTasksIds,
    failedTasksIds,
    succeededTasksLogs,
    failedTasksLogs,
    managerQueue,
    // Pack actions
    installPack,
    uninstallPack,
    updatePack,
    updateAllPacks,
    disablePack,
    enablePack
  };
});
const useManagerProgressDialogStore = defineStore(
  "managerProgressDialog",
  () => {
    const isExpanded = ref(false);
    const activeTabIndex = ref(0);
    const setActiveTabIndex = /* @__PURE__ */ __name((index2) => {
      activeTabIndex.value = index2;
    }, "setActiveTabIndex");
    const getActiveTabIndex = /* @__PURE__ */ __name(() => {
      return activeTabIndex.value;
    }, "getActiveTabIndex");
    const toggle = /* @__PURE__ */ __name(() => {
      isExpanded.value = !isExpanded.value;
    }, "toggle");
    const collapse = /* @__PURE__ */ __name(() => {
      isExpanded.value = false;
    }, "collapse");
    const expand = /* @__PURE__ */ __name(() => {
      isExpanded.value = true;
    }, "expand");
    return {
      isExpanded,
      toggle,
      collapse,
      expand,
      setActiveTabIndex,
      getActiveTabIndex
    };
  }
);
const useMissingNodes = createSharedComposable(() => {
  const nodeDefStore = useNodeDefStore();
  const comfyManagerStore = useComfyManagerStore();
  const workflowStore = useWorkflowStore();
  const { workflowPacks, isLoading, error, startFetchWorkflowPacks } = useWorkflowPacks();
  const filterMissingPacks = /* @__PURE__ */ __name((packs) => packs.filter((pack) => !comfyManagerStore.isPackInstalled(pack.id)), "filterMissingPacks");
  const missingNodePacks = computed(() => {
    if (!workflowPacks.value.length) return [];
    return filterMissingPacks(workflowPacks.value);
  });
  const isCorePack = /* @__PURE__ */ __name((packId) => {
    return packId === "comfy-core";
  }, "isCorePack");
  const isMissingCoreNode = /* @__PURE__ */ __name((node2) => {
    const packId = node2.properties?.cnr_id;
    if (packId === void 0 || !isCorePack(packId)) return false;
    const nodeName = node2.type;
    const isRegisteredNodeDef = !!nodeDefStore.nodeDefsByName[nodeName];
    return !isRegisteredNodeDef;
  }, "isMissingCoreNode");
  const missingCoreNodes = computed(() => {
    const missingNodes2 = collectAllNodes(app$1.rootGraph, isMissingCoreNode);
    return groupBy(missingNodes2, (node2) => String(node2.properties?.ver || ""));
  });
  const hasMissingNodes = computed(() => {
    return missingNodePacks.value.length > 0 || Object.keys(missingCoreNodes.value).length > 0;
  });
  watch(
    () => workflowStore.activeWorkflow,
    async () => {
      await startFetchWorkflowPacks();
    },
    { immediate: true }
  );
  return {
    missingNodePacks,
    missingCoreNodes,
    hasMissingNodes,
    isLoading,
    error
  };
});
const _hoisted_1$12 = { class: "flex h-full w-full flex-col gap-4 p-4" };
const _hoisted_2$O = { class: "m-0 text-sm leading-4 text-muted-foreground" };
const _hoisted_3$y = { class: "comfy-missing-nodes flex flex-col max-h-[256px] rounded-lg py-2 scrollbar-custom bg-secondary-background" };
const _hoisted_4$r = { class: "text-xs" };
const _hoisted_5$p = {
  key: 0,
  class: "text-xs"
};
const _hoisted_6$h = { class: "m-0 text-sm leading-4 text-muted-foreground" };
const _sfc_main$1d = /* @__PURE__ */ defineComponent({
  __name: "MissingNodesContent",
  props: {
    missingNodeTypes: {}
  },
  setup(__props) {
    const props = __props;
    const { missingCoreNodes } = useMissingNodes();
    const uniqueNodes = computed(() => {
      const seenTypes = /* @__PURE__ */ new Set();
      return props.missingNodeTypes.filter((node2) => {
        const type = typeof node2 === "object" ? node2.type : node2;
        if (seenTypes.has(type)) return false;
        seenTypes.add(type);
        return true;
      }).map((node2) => {
        if (typeof node2 === "object") {
          return {
            label: node2.type,
            hint: node2.hint,
            action: node2.action
          };
        }
        return { label: node2 };
      });
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(["flex w-[490px] flex-col border-t-1 border-border-default", unref(isCloud) ? "border-b-1" : ""])
      }, [
        createBaseVNode("div", _hoisted_1$12, [
          createBaseVNode("div", null, [
            createBaseVNode("p", _hoisted_2$O, toDisplayString(unref(isCloud) ? _ctx.$t("missingNodes.cloud.description") : _ctx.$t("missingNodes.oss.description")), 1)
          ]),
          !unref(isCloud) ? (openBlock(), createBlock(_sfc_main$2y, {
            key: 0,
            "missing-core-nodes": unref(missingCoreNodes)
          }, null, 8, ["missing-core-nodes"])) : createCommentVNode("", true),
          createBaseVNode("div", _hoisted_3$y, [
            (openBlock(true), createElementBlock(Fragment, null, renderList(uniqueNodes.value, (node2, i) => {
              return openBlock(), createElementBlock("div", {
                key: i,
                class: "flex min-h-8 items-center justify-between px-4 py-2 bg-secondary-background text-muted-foreground"
              }, [
                createBaseVNode("span", _hoisted_4$r, toDisplayString(node2.label), 1),
                node2.hint ? (openBlock(), createElementBlock("span", _hoisted_5$p, toDisplayString(node2.hint), 1)) : createCommentVNode("", true)
              ]);
            }), 128))
          ]),
          createBaseVNode("div", null, [
            createBaseVNode("p", _hoisted_6$h, toDisplayString(unref(isCloud) ? _ctx.$t("missingNodes.cloud.replacementInstruction") : _ctx.$t("missingNodes.oss.replacementInstruction")), 1)
          ])
        ])
      ], 2);
    };
  }
});
const _hoisted_1$11 = ["width", "height"];
const _hoisted_2$N = { "clip-path": "url(#clip0_776_9582)" };
const _hoisted_3$x = ["fill"];
const _hoisted_4$q = ["fill"];
const _hoisted_5$o = ["fill"];
const _hoisted_6$g = ["fill"];
const _hoisted_7$e = ["fill"];
const _hoisted_8$b = ["fill"];
const _hoisted_9$a = ["fill"];
const _hoisted_10$8 = ["fill"];
const _sfc_main$1c = /* @__PURE__ */ defineComponent({
  __name: "DotSpinner",
  props: {
    size: { default: 24 },
    duration: { default: "2s" }
  },
  setup(__props) {
    const colorPaletteStore = useColorPaletteStore();
    const color2 = computed(
      () => colorPaletteStore.completedActivePalette.light_theme ? "#2C2B30" : "#D4D4D4"
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: "inline-flex items-center justify-center",
        style: normalizeStyle({ width: _ctx.size + "px", height: _ctx.size + "px" })
      }, [
        (openBlock(), createElementBlock("svg", {
          xmlns: "http://www.w3.org/2000/svg",
          width: _ctx.size,
          height: _ctx.size,
          viewBox: "0 0 14 14",
          fill: "none",
          class: "animate-spin",
          style: normalizeStyle({ animationDuration: _ctx.duration })
        }, [
          createBaseVNode("g", _hoisted_2$N, [
            createBaseVNode("path", {
              class: "dot-animation",
              style: { "animation-delay": "0s" },
              "fill-rule": "evenodd",
              "clip-rule": "evenodd",
              d: "M7 2.21053C7.61042 2.21053 8.10526 1.71568 8.10526 1.10526C8.10526 0.494843 7.61042 0 7 0C6.38958 0 5.89474 0.494843 5.89474 1.10526C5.89474 1.71568 6.38958 2.21053 7 2.21053Z",
              fill: color2.value
            }, null, 8, _hoisted_3$x),
            createBaseVNode("path", {
              class: "dot-animation",
              style: { "animation-delay": "0.25s" },
              "fill-rule": "evenodd",
              "clip-rule": "evenodd",
              d: "M2.21053 7C2.21053 7.61042 1.71568 8.10526 1.10526 8.10526C0.494843 8.10526 0 7.61042 0 7C0 6.38958 0.494843 5.89474 1.10526 5.89474C1.71568 5.89474 2.21053 6.38958 2.21053 7Z",
              fill: color2.value
            }, null, 8, _hoisted_4$q),
            createBaseVNode("path", {
              class: "dot-animation",
              style: { "animation-delay": "0.5s" },
              "fill-rule": "evenodd",
              "clip-rule": "evenodd",
              d: "M14 7C14 7.61042 13.5052 8.10526 12.8947 8.10526C12.2843 8.10526 11.7895 7.61042 11.7895 7C11.7895 6.38958 12.2843 5.89474 12.8947 5.89474C13.5052 5.89474 14 6.38958 14 7Z",
              fill: color2.value
            }, null, 8, _hoisted_5$o),
            createBaseVNode("path", {
              class: "dot-animation",
              style: { "animation-delay": "0.75s" },
              "fill-rule": "evenodd",
              "clip-rule": "evenodd",
              d: "M8.10526 12.8947C8.10526 13.5052 7.61041 14 6.99999 14C6.38957 14 5.89473 13.5052 5.89473 12.8947C5.89473 12.2843 6.38957 11.7895 6.99999 11.7895C7.61041 11.7895 8.10526 12.2843 8.10526 12.8947Z",
              fill: color2.value
            }, null, 8, _hoisted_6$g),
            createBaseVNode("path", {
              class: "dot-animation",
              style: { "animation-delay": "0.125s" },
              "fill-rule": "evenodd",
              "clip-rule": "evenodd",
              d: "M2.05039 3.61349C2.48203 4.04513 3.18184 4.04513 3.61347 3.61349C4.0451 3.18186 4.0451 2.48205 3.61347 2.05042C3.18184 1.61878 2.48203 1.61878 2.05039 2.05042C1.61876 2.48205 1.61876 3.18186 2.05039 3.61349Z",
              fill: color2.value
            }, null, 8, _hoisted_7$e),
            createBaseVNode("path", {
              class: "dot-animation",
              style: { "animation-delay": "0.625s" },
              "fill-rule": "evenodd",
              "clip-rule": "evenodd",
              d: "M11.9496 11.9496C11.518 12.3812 10.8182 12.3812 10.3865 11.9496C9.9549 11.5179 9.9549 10.8181 10.3865 10.3865C10.8182 9.95485 11.518 9.95485 11.9496 10.3865C12.3812 10.8181 12.3812 11.5179 11.9496 11.9496Z",
              fill: color2.value
            }, null, 8, _hoisted_8$b),
            createBaseVNode("path", {
              class: "dot-animation",
              style: { "animation-delay": "0.875s" },
              "fill-rule": "evenodd",
              "clip-rule": "evenodd",
              d: "M2.05039 11.9496C2.48203 12.3812 3.18184 12.3812 3.61347 11.9496C4.0451 11.5179 4.0451 10.8181 3.61347 10.3865C3.18184 9.95485 2.48203 9.95485 2.05039 10.3865C1.61876 10.8181 1.61876 11.5179 2.05039 11.9496Z",
              fill: color2.value
            }, null, 8, _hoisted_9$a),
            createBaseVNode("path", {
              class: "dot-animation",
              style: { "animation-delay": "0.375s" },
              "fill-rule": "evenodd",
              "clip-rule": "evenodd",
              d: "M11.9496 3.61349C11.518 4.04513 10.8182 4.04513 10.3865 3.61349C9.9549 3.18186 9.9549 2.48205 10.3865 2.05042C10.8182 1.61878 11.518 1.61878 11.9496 2.05042C12.3812 2.48205 12.3812 3.18186 11.9496 3.61349Z",
              fill: color2.value
            }, null, 8, _hoisted_10$8)
          ]),
          _cache[0] || (_cache[0] = createBaseVNode("defs", null, [
            createBaseVNode("clipPath", { id: "clip0_776_9582" }, [
              createBaseVNode("rect", {
                width: "14",
                height: "14",
                fill: "white"
              })
            ])
          ], -1))
        ], 12, _hoisted_1$11))
      ], 4);
    };
  }
});
const DotSpinner = /* @__PURE__ */ _export_sfc(_sfc_main$1c, [["__scopeId", "data-v-ec0691c8"]]);
const useInstalledPacks = /* @__PURE__ */ __name((options = {}) => {
  const comfyManagerStore = useComfyManagerStore();
  const isInitializing = ref(false);
  const lastFetchedIds = ref("");
  const installedPackIds = computed(
    () => Array.from(comfyManagerStore.installedPacksIds)
  );
  const { startFetch, cleanup, error, isLoading, nodePacks, isReady } = useNodePacks(installedPackIds, options);
  const filterInstalledPack = /* @__PURE__ */ __name((packs) => packs.filter((pack) => comfyManagerStore.isPackInstalled(pack.id)), "filterInstalledPack");
  const startFetchInstalled = /* @__PURE__ */ __name(async () => {
    if (isInitializing.value) {
      return;
    }
    isInitializing.value = true;
    try {
      if (comfyManagerStore.installedPacksIds.size === 0) {
        await comfyManagerStore.refreshInstalledList();
      }
      await startFetch();
    } finally {
      isInitializing.value = false;
    }
  }, "startFetchInstalled");
  whenever(installedPackIds, async (newIds) => {
    const newIdsStr = newIds.sort().join(",");
    if (newIdsStr !== lastFetchedIds.value && !isInitializing.value) {
      lastFetchedIds.value = newIdsStr;
      await startFetch();
    }
  });
  onUnmounted(() => {
    cleanup();
  });
  const installedPacksWithVersions = computed(() => {
    const result = [];
    for (const pack of Object.values(comfyManagerStore.installedPacks)) {
      const id2 = pack.cnr_id || pack.aux_id;
      if (id2) {
        result.push({
          id: id2,
          version: pack.ver ?? ""
        });
      }
    }
    return result;
  });
  return {
    error,
    isLoading,
    isReady,
    installedPacks: nodePacks,
    installedPacksWithVersions,
    startFetchInstalled,
    filterInstalledPack
  };
}, "useInstalledPacks");
const useConflictDetectionStore = defineStore(
  "conflictDetection",
  () => {
    const conflictedPackages = ref([]);
    const isDetecting = ref(false);
    const lastDetectionTime = ref(null);
    const hasConflicts = computed(
      () => conflictedPackages.value.some((pkg) => pkg.has_conflict)
    );
    const getConflictsForPackageByID = computed(
      () => (packageId) => conflictedPackages.value.find((pkg) => pkg.package_id === packageId)
    );
    const bannedPackages = computed(
      () => conflictedPackages.value.filter(
        (pkg) => pkg.conflicts.some((conflict) => conflict.type === "banned")
      )
    );
    const securityPendingPackages = computed(
      () => conflictedPackages.value.filter(
        (pkg) => pkg.conflicts.some((conflict) => conflict.type === "pending")
      )
    );
    function setConflictedPackages(packages) {
      conflictedPackages.value = [...packages];
    }
    __name(setConflictedPackages, "setConflictedPackages");
    function clearConflicts() {
      conflictedPackages.value = [];
    }
    __name(clearConflicts, "clearConflicts");
    function setDetecting(detecting) {
      isDetecting.value = detecting;
    }
    __name(setDetecting, "setDetecting");
    function setLastDetectionTime(time) {
      lastDetectionTime.value = time;
    }
    __name(setLastDetectionTime, "setLastDetectionTime");
    return {
      // State
      conflictedPackages,
      isDetecting,
      lastDetectionTime,
      // Getters
      hasConflicts,
      getConflictsForPackageByID,
      bannedPackages,
      securityPendingPackages,
      // Actions
      setConflictedPackages,
      clearConflicts,
      setDetecting,
      setLastDetectionTime
    };
  }
);
const STORAGE_KEYS = {
  CONFLICT_MODAL_DISMISSED: "Comfy.ConflictModalDismissed",
  CONFLICT_RED_DOT_DISMISSED: "Comfy.ConflictRedDotDismissed",
  CONFLICT_WARNING_BANNER_DISMISSED: "Comfy.ConflictWarningBannerDismissed"
};
const modalDismissed = useStorage(STORAGE_KEYS.CONFLICT_MODAL_DISMISSED, false);
const redDotDismissed = useStorage(
  STORAGE_KEYS.CONFLICT_RED_DOT_DISMISSED,
  false
);
const warningBannerDismissed = useStorage(
  STORAGE_KEYS.CONFLICT_WARNING_BANNER_DISMISSED,
  false
);
function useConflictAcknowledgment() {
  const conflictDetectionStore = useConflictDetectionStore();
  const state2 = computed(() => ({
    modal_dismissed: modalDismissed.value,
    red_dot_dismissed: redDotDismissed.value,
    warning_banner_dismissed: warningBannerDismissed.value
  }));
  function dismissRedDotNotification() {
    redDotDismissed.value = true;
  }
  __name(dismissRedDotNotification, "dismissRedDotNotification");
  function dismissWarningBanner() {
    warningBannerDismissed.value = true;
    redDotDismissed.value = true;
  }
  __name(dismissWarningBanner, "dismissWarningBanner");
  function markConflictsAsSeen() {
    redDotDismissed.value = true;
    modalDismissed.value = true;
    warningBannerDismissed.value = true;
  }
  __name(markConflictsAsSeen, "markConflictsAsSeen");
  const hasConflicts = computed(() => conflictDetectionStore.hasConflicts);
  const shouldShowConflictModal = computed(() => !modalDismissed.value);
  const shouldShowRedDot = computed(() => {
    if (!hasConflicts.value) return false;
    if (redDotDismissed.value) return false;
    return true;
  });
  const shouldShowManagerBanner = computed(() => {
    return hasConflicts.value && !warningBannerDismissed.value;
  });
  return {
    // State
    acknowledgmentState: state2,
    shouldShowConflictModal,
    shouldShowRedDot,
    shouldShowManagerBanner,
    // Methods
    dismissRedDotNotification,
    dismissWarningBanner,
    markConflictsAsSeen
  };
}
__name(useConflictAcknowledgment, "useConflictAcknowledgment");
function createBannedConflict(isBanned) {
  if (isBanned === true) {
    return {
      type: "banned",
      current_value: "installed",
      required_value: "not_banned"
    };
  }
  return null;
}
__name(createBannedConflict, "createBannedConflict");
function createPendingConflict(isPending) {
  if (isPending === true) {
    return {
      type: "pending",
      current_value: "installed",
      required_value: "not_pending"
    };
  }
  return null;
}
__name(createPendingConflict, "createPendingConflict");
function consolidateConflictsByPackage(conflicts) {
  const grouped = groupBy(
    conflicts,
    (conflict) => normalizePackId(conflict.package_name)
  );
  return Object.entries(grouped).map(([packageName, packageConflicts]) => {
    const allConflicts = packageConflicts.flatMap((pc) => pc.conflicts);
    const uniqueConflicts = uniqBy(
      allConflicts,
      (conflict) => `${conflict.type}|${conflict.current_value}|${conflict.required_value}`
    );
    const baseItem = packageConflicts[0];
    return {
      ...baseItem,
      package_name: packageName,
      // Use normalized name
      conflicts: uniqueConflicts,
      has_conflict: uniqueConflicts.length > 0,
      is_compatible: uniqueConflicts.length === 0
    };
  });
}
__name(consolidateConflictsByPackage, "consolidateConflictsByPackage");
function getRegistryOS(systemOS) {
  if (!systemOS) return void 0;
  const lower = systemOS.toLowerCase();
  if (lower.includes("darwin") || lower.includes("mac")) return "macOS";
  if (lower.includes("win")) return "Windows";
  if (lower.includes("linux")) return "Linux";
  return void 0;
}
__name(getRegistryOS, "getRegistryOS");
function getRegistryAccelerator(deviceType) {
  if (!deviceType) return "CPU";
  const lower = deviceType.toLowerCase();
  if (lower === "cuda") return "CUDA";
  if (lower === "mps") return "Metal";
  if (lower === "rocm") return "ROCm";
  return "CPU";
}
__name(getRegistryAccelerator, "getRegistryAccelerator");
function checkOSCompatibility(supported, current) {
  if (isNil(supported) || isEmpty(supported)) return null;
  const currentOS = getRegistryOS(current);
  if (!currentOS) {
    return {
      type: "os",
      current_value: "Unknown",
      required_value: supported.join(", ")
    };
  }
  if (!supported.includes(currentOS)) {
    return {
      type: "os",
      current_value: currentOS,
      required_value: supported.join(", ")
    };
  }
  return null;
}
__name(checkOSCompatibility, "checkOSCompatibility");
function checkAcceleratorCompatibility(supported, current) {
  if (isNil(supported) || isEmpty(supported)) return null;
  const currentAcc = getRegistryAccelerator(current);
  if (!supported.includes(currentAcc)) {
    return {
      type: "accelerator",
      current_value: currentAcc,
      required_value: supported.join(", ")
    };
  }
  return null;
}
__name(checkAcceleratorCompatibility, "checkAcceleratorCompatibility");
function normalizeOSList(osValues) {
  if (isNil(osValues) || isEmpty(osValues)) return void 0;
  if (osValues.some((os) => os.toLowerCase() === "os independent")) {
    return void 0;
  }
  const validOS = [];
  osValues.forEach((os) => {
    if (os === "Windows" || os === "macOS" || os === "Linux") {
      if (!validOS.includes(os)) validOS.push(os);
    }
  });
  return validOS.length > 0 ? validOS : void 0;
}
__name(normalizeOSList, "normalizeOSList");
const config = {
  app_version: "1.36.14"
};
function cleanVersion(version) {
  return semverExports.clean(version) || version;
}
__name(cleanVersion, "cleanVersion");
function checkVersionCompatibility(type, currentVersion, supportedVersion) {
  if (isNil(currentVersion) || isEmpty(currentVersion)) {
    return null;
  }
  if (isNil(supportedVersion) || isEmpty(supportedVersion?.trim())) {
    return null;
  }
  const cleanCurrent = cleanVersion(currentVersion);
  let isCompatible = false;
  try {
    isCompatible = semverExports.satisfies(cleanCurrent, supportedVersion);
  } catch {
    return {
      type,
      current_value: currentVersion,
      required_value: supportedVersion
    };
  }
  if (isCompatible) return null;
  return {
    type,
    current_value: currentVersion,
    required_value: supportedVersion
  };
}
__name(checkVersionCompatibility, "checkVersionCompatibility");
function getFrontendVersion() {
  return config.app_version;
}
__name(getFrontendVersion, "getFrontendVersion");
function useConflictDetection() {
  const managerStore = useComfyManagerStore();
  const {
    startFetchInstalled,
    installedPacks,
    installedPacksWithVersions,
    isReady: installedPacksReady
  } = useInstalledPacks();
  const isDetecting = ref(false);
  const lastDetectionTime = ref(null);
  const detectionError = ref(null);
  const systemEnvironment = ref(null);
  const detectionResults = ref([]);
  const storedMergedConflicts = ref([]);
  const abortController = ref(null);
  const acknowledgment = useConflictAcknowledgment();
  const conflictStore = useConflictDetectionStore();
  const hasConflicts = computed(() => conflictStore.hasConflicts);
  const conflictedPackages = computed(() => {
    return conflictStore.conflictedPackages;
  });
  const bannedPackages = computed(() => conflictStore.bannedPackages);
  const securityPendingPackages = computed(
    () => conflictStore.securityPendingPackages
  );
  async function collectSystemEnvironment() {
    try {
      const systemStatsStore = useSystemStatsStore();
      const { systemStats } = systemStatsStore;
      await until(() => systemStatsStore.isInitialized).toBe(true);
      const frontendVersion = getFrontendVersion();
      const environment = {
        comfyui_version: systemStats?.system.comfyui_version ?? "",
        frontend_version: frontendVersion,
        os: systemStats?.system.os ?? "",
        accelerator: systemStats?.devices?.[0]?.type ?? ""
      };
      systemEnvironment.value = environment;
      return environment;
    } catch (error) {
      const fallbackEnvironment = {
        comfyui_version: void 0,
        frontend_version: void 0,
        os: void 0,
        accelerator: void 0
      };
      systemEnvironment.value = fallbackEnvironment;
      return fallbackEnvironment;
    }
  }
  __name(collectSystemEnvironment, "collectSystemEnvironment");
  async function buildNodeRequirements() {
    try {
      await startFetchInstalled();
      if (!installedPacksReady.value || !installedPacks.value || installedPacks.value.length === 0) {
        console.warn(
          "[ConflictDetection] No installed packages available from useInstalledPacks"
        );
        return [];
      }
      const registryService = useComfyRegistryService();
      abortController.value = new AbortController();
      const versionDataMap = /* @__PURE__ */ new Map();
      const nodeVersions = installedPacksWithVersions.value.map((pack) => ({
        node_id: pack.id,
        version: pack.version
      }));
      if (nodeVersions.length > 0) {
        try {
          const bulkResponse = await registryService.getBulkNodeVersions(
            nodeVersions,
            abortController.value?.signal
          );
          if (bulkResponse && bulkResponse.node_versions?.length > 0) {
            bulkResponse.node_versions.forEach((result) => {
              if (result.status === "success" && result.node_version) {
                versionDataMap.set(
                  result.identifier.node_id,
                  result.node_version
                );
              } else if (result.status === "error") {
                console.warn(
                  `[ConflictDetection] Failed to fetch version data for ${result.identifier.node_id}@${result.identifier.version}:`,
                  result.error_message
                );
              }
            });
          }
        } catch (error) {
          console.warn(
            "[ConflictDetection] Failed to fetch bulk version data:",
            error
          );
        }
      }
      const requirements = [];
      for (const installedPackVersion of installedPacksWithVersions.value) {
        const versionData = versionDataMap.get(installedPackVersion.id);
        const isEnabled = managerStore.isPackEnabled(installedPackVersion.id);
        const packInfo = find(installedPacks.value, {
          id: installedPackVersion.id
        });
        if (versionData) {
          const requirement = {
            // Basic package info
            id: installedPackVersion.id,
            name: packInfo?.name || installedPackVersion.id,
            installed_version: installedPackVersion.version,
            is_enabled: isEnabled,
            // Version-specific compatibility data
            supported_comfyui_version: versionData.supported_comfyui_version,
            supported_comfyui_frontend_version: versionData.supported_comfyui_frontend_version,
            supported_os: normalizeOSList(
              versionData.supported_os
            ),
            supported_accelerators: versionData.supported_accelerators,
            // Status information
            version_status: versionData.status,
            is_banned: versionData.status === "NodeVersionStatusBanned",
            is_pending: versionData.status === "NodeVersionStatusPending"
          };
          requirements.push(requirement);
        } else {
          console.warn(
            `[ConflictDetection] No Registry data found for ${installedPackVersion.id}, using fallback`
          );
          const fallbackRequirement = {
            id: installedPackVersion.id,
            name: packInfo?.name || installedPackVersion.id,
            installed_version: installedPackVersion.version,
            is_enabled: isEnabled,
            is_banned: false,
            is_pending: false
          };
          requirements.push(fallbackRequirement);
        }
      }
      return requirements;
    } catch (error) {
      console.warn(
        "[ConflictDetection] Failed to fetch package requirements:",
        error
      );
      return [];
    }
  }
  __name(buildNodeRequirements, "buildNodeRequirements");
  function analyzePackageConflicts(packageReq, systemEnvInfo) {
    const conflicts = [];
    const versionConflict = checkVersionCompatibility(
      "comfyui_version",
      systemEnvInfo.comfyui_version,
      packageReq.supported_comfyui_version
    );
    if (versionConflict) conflicts.push(versionConflict);
    const frontendConflict = checkVersionCompatibility(
      "frontend_version",
      systemEnvInfo.frontend_version,
      packageReq.supported_comfyui_frontend_version
    );
    if (frontendConflict) conflicts.push(frontendConflict);
    const osConflict = checkOSCompatibility(
      packageReq.supported_os,
      systemEnvInfo.os
    );
    if (osConflict) conflicts.push(osConflict);
    const acceleratorConflict = checkAcceleratorCompatibility(
      packageReq.supported_accelerators,
      systemEnvInfo.accelerator
    );
    if (acceleratorConflict) conflicts.push(acceleratorConflict);
    const bannedConflict = createBannedConflict(packageReq.is_banned);
    if (bannedConflict) {
      conflicts.push(bannedConflict);
    }
    const pendingConflict = createPendingConflict(packageReq.is_pending);
    if (pendingConflict) {
      conflicts.push(pendingConflict);
    }
    const hasConflict = conflicts.length > 0;
    return {
      package_id: packageReq.id ?? "",
      package_name: packageReq.name ?? "",
      has_conflict: hasConflict,
      conflicts,
      is_compatible: !hasConflict
    };
  }
  __name(analyzePackageConflicts, "analyzePackageConflicts");
  async function fetchImportFailInfo() {
    try {
      const comfyManagerService = useComfyManagerService();
      if (!installedPacksWithVersions.value || installedPacksWithVersions.value.length === 0) {
        console.warn(
          "[ConflictDetection] No installed packages available for import failure check"
        );
        return {};
      }
      const packageIds = installedPacksWithVersions.value.map((pack) => pack.id);
      const bulkResult = await comfyManagerService.getImportFailInfoBulk(
        { cnr_ids: packageIds },
        abortController.value?.signal
      );
      if (bulkResult) {
        const importFailures = {};
        Object.entries(bulkResult).forEach(([packageId, failInfo]) => {
          if (failInfo !== null) {
            importFailures[packageId] = failInfo;
          }
        });
        return importFailures;
      }
      return {};
    } catch (error) {
      console.warn(
        "[ConflictDetection] Failed to fetch import failure information:",
        error
      );
      return {};
    }
  }
  __name(fetchImportFailInfo, "fetchImportFailInfo");
  function detectImportFailConflicts(importFailInfo) {
    const results = [];
    if (!importFailInfo || typeof importFailInfo !== "object") {
      return results;
    }
    for (const [packageId, failureInfo] of Object.entries(importFailInfo)) {
      if (failureInfo && typeof failureInfo === "object") {
        const errorMsg = failureInfo.msg || "Unknown import error";
        const modulePath = failureInfo.path || "";
        results.push({
          package_id: packageId,
          package_name: packageId,
          has_conflict: true,
          conflicts: [
            {
              type: "import_failed",
              current_value: "installed",
              required_value: failureInfo.msg
            }
          ],
          is_compatible: false
        });
        console.warn(
          `[ConflictDetection] Python import failure detected for ${packageId}:`,
          {
            path: modulePath,
            error: errorMsg
          }
        );
      }
    }
    return results;
  }
  __name(detectImportFailConflicts, "detectImportFailConflicts");
  async function runFullConflictAnalysis() {
    if (isDetecting.value) {
      return {
        success: false,
        error_message: "Already detecting conflicts",
        results: detectionResults.value
      };
    }
    isDetecting.value = true;
    detectionError.value = null;
    try {
      const systemEnvInfo = await collectSystemEnvironment();
      const installedNodeRequirements = await buildNodeRequirements();
      const conflictDetectionTasks = installedNodeRequirements.map(
        async (packageReq) => {
          try {
            return analyzePackageConflicts(packageReq, systemEnvInfo);
          } catch (error) {
            console.warn(
              `[ConflictDetection] Failed to detect conflicts for package ${packageReq.name}:`,
              error
            );
            return null;
          }
        }
      );
      const conflictResults = await Promise.allSettled(conflictDetectionTasks);
      const packageResults = conflictResults.map((result) => result.status === "fulfilled" ? result.value : null).filter((result) => result !== null);
      const importFailInfo = await fetchImportFailInfo();
      const importFailResults = detectImportFailConflicts(importFailInfo);
      const allResults = [...packageResults, ...importFailResults];
      detectionResults.value = allResults;
      lastDetectionTime.value = (/* @__PURE__ */ new Date()).toISOString();
      if (allResults.some((result) => result.has_conflict)) {
        const conflictedResults = allResults.filter(
          (result) => result.has_conflict
        );
        const mergedConflicts = consolidateConflictsByPackage(conflictedResults);
        conflictStore.setConflictedPackages(mergedConflicts);
        detectionResults.value = [...mergedConflicts];
        storedMergedConflicts.value = [...mergedConflicts];
        const response2 = {
          success: true,
          results: mergedConflicts,
          detected_system_environment: systemEnvInfo
        };
        return response2;
      } else {
        conflictStore.clearConflicts();
        detectionResults.value = [];
      }
      const response = {
        success: true,
        results: allResults,
        detected_system_environment: systemEnvInfo
      };
      return response;
    } catch (error) {
      console.error(
        "[ConflictDetection] Error during conflict detection:",
        error
      );
      detectionError.value = error instanceof Error ? error.message : String(error);
      return {
        success: false,
        error_message: detectionError.value,
        results: []
      };
    } finally {
      isDetecting.value = false;
      if (abortController.value) {
        abortController.value = null;
      }
    }
  }
  __name(runFullConflictAnalysis, "runFullConflictAnalysis");
  async function initializeConflictDetection() {
    try {
      const systemStatsStore = useSystemStatsStore();
      await until(() => systemStatsStore.isInitialized).toBe(true);
      const managerState = useManagerState();
      if (!managerState.isNewManagerUI.value) {
        return;
      }
      await runFullConflictAnalysis();
    } catch (error) {
      console.warn(
        "[ConflictDetection] Error during initialization (ignored):",
        error
      );
    }
  }
  __name(initializeConflictDetection, "initializeConflictDetection");
  function cancelRequests() {
    if (abortController.value) {
      abortController.value.abort();
      abortController.value = null;
    }
  }
  __name(cancelRequests, "cancelRequests");
  const instance = getCurrentInstance();
  if (instance) {
    onUnmounted(() => {
      cancelRequests();
    });
  }
  async function shouldShowConflictModalAfterUpdate() {
    if (detectionResults.value.length === 0) {
      await runFullConflictAnalysis();
    }
    const hasActualConflicts = hasConflicts.value;
    const canShowModal = acknowledgment.shouldShowConflictModal.value;
    return hasActualConflicts && canShowModal;
  }
  __name(shouldShowConflictModalAfterUpdate, "shouldShowConflictModalAfterUpdate");
  function checkNodeCompatibility(node2) {
    const conflicts = [];
    const osConflict = checkOSCompatibility(
      normalizeOSList(node2.supported_os),
      systemEnvironment.value?.os
    );
    if (osConflict) {
      conflicts.push(osConflict);
    }
    const acceleratorConflict = checkAcceleratorCompatibility(
      node2.supported_accelerators,
      systemEnvironment.value?.accelerator
    );
    if (acceleratorConflict) {
      conflicts.push(acceleratorConflict);
    }
    const comfyUIVersionConflict = checkVersionCompatibility(
      "comfyui_version",
      systemEnvironment.value?.comfyui_version,
      node2.supported_comfyui_version
    );
    if (comfyUIVersionConflict) {
      conflicts.push(comfyUIVersionConflict);
    }
    const currentFrontendVersion = getFrontendVersion();
    const frontendVersionConflict = checkVersionCompatibility(
      "frontend_version",
      currentFrontendVersion,
      node2.supported_comfyui_frontend_version
    );
    if (frontendVersionConflict) {
      conflicts.push(frontendVersionConflict);
    }
    const bannedConflict = createBannedConflict(
      node2.status === "NodeStatusBanned" || node2.status === "NodeVersionStatusBanned"
    );
    if (bannedConflict) {
      conflicts.push(bannedConflict);
    }
    const pendingConflict = createPendingConflict(
      node2.status === "NodeVersionStatusPending"
    );
    if (pendingConflict) {
      conflicts.push(pendingConflict);
    }
    return {
      hasConflict: conflicts.length > 0,
      conflicts
    };
  }
  __name(checkNodeCompatibility, "checkNodeCompatibility");
  return {
    // State
    isDetecting: readonly(isDetecting),
    lastDetectionTime: readonly(lastDetectionTime),
    detectionError: readonly(detectionError),
    systemEnvironment: readonly(systemEnvironment),
    detectionResults: readonly(detectionResults),
    // Computed
    hasConflicts,
    conflictedPackages,
    bannedPackages,
    securityPendingPackages,
    // Methods
    runFullConflictAnalysis,
    collectSystemEnvironment,
    initializeConflictDetection,
    cancelRequests,
    shouldShowConflictModalAfterUpdate,
    // Helper functions for other components
    checkNodeCompatibility
  };
}
__name(useConflictDetection, "useConflictDetection");
const _hoisted_1$10 = {
  key: 0,
  class: "pi pi-exclamation-triangle text-yellow-500"
};
const _sfc_main$1b = /* @__PURE__ */ defineComponent({
  __name: "PackInstallButton",
  props: {
    nodePacks: {},
    isLoading: { type: Boolean, default: false },
    label: { default: "Install" },
    size: { default: "sm" },
    hasConflict: { type: Boolean },
    conflictInfo: {}
  },
  setup(__props) {
    const managerStore = useComfyManagerStore();
    const { showNodeConflictDialog } = useDialogService();
    const { t: t2 } = useI18n();
    const isInstalling = computed(() => {
      if (!__props.nodePacks?.length) return false;
      return __props.nodePacks.some((pack) => managerStore.isPackInstalling(pack.id));
    });
    const createPayload = /* @__PURE__ */ __name((installItem) => {
      if (!installItem.id) {
        throw new Error("Node ID is required for installation");
      }
      const isUnclaimedPack = installItem.publisher?.name === "Unclaimed";
      const versionToInstall = isUnclaimedPack ? "nightly" : installItem.latest_version?.version ?? "latest";
      return {
        id: installItem.id,
        repository: installItem.repository ?? "",
        channel: "dev",
        mode: "cache",
        selected_version: versionToInstall,
        version: versionToInstall
      };
    }, "createPayload");
    const installPack = /* @__PURE__ */ __name((item) => managerStore.installPack.call(createPayload(item)), "installPack");
    const installAllPacks = /* @__PURE__ */ __name(async () => {
      if (!__props.nodePacks?.length) return;
      if (__props.hasConflict && __props.conflictInfo) {
        const { checkNodeCompatibility } = useConflictDetection();
        const conflictedPackages = __props.nodePacks.map((pack) => {
          const compatibilityCheck = checkNodeCompatibility(pack);
          return {
            package_id: pack.id || "",
            package_name: pack.name || "",
            has_conflict: compatibilityCheck.hasConflict,
            conflicts: compatibilityCheck.conflicts,
            is_compatible: !compatibilityCheck.hasConflict
          };
        }).filter((result) => result.has_conflict);
        showNodeConflictDialog({
          conflictedPackages,
          buttonText: t2("manager.conflicts.installAnyway"),
          onButtonClick: /* @__PURE__ */ __name(async () => {
            const uninstalledPacks2 = __props.nodePacks.filter(
              (pack) => !managerStore.isPackInstalled(pack.id)
            );
            if (!uninstalledPacks2.length) return;
            await performInstallation(uninstalledPacks2);
          }, "onButtonClick")
        });
        return;
      }
      const uninstalledPacks = __props.nodePacks.filter(
        (pack) => !managerStore.isPackInstalled(pack.id)
      );
      if (!uninstalledPacks.length) return;
      await performInstallation(uninstalledPacks);
    }, "installAllPacks");
    const performInstallation = /* @__PURE__ */ __name(async (packs) => {
      await Promise.all(packs.map(installPack));
      managerStore.installPack.clear();
    }, "performInstallation");
    const computedLabel = computed(
      () => isInstalling.value ? t2("g.installing") : __props.label ?? (__props.nodePacks.length > 1 ? t2("manager.installSelected") : t2("g.install"))
    );
    return (_ctx, _cache) => {
      return openBlock(), createBlock(_sfc_main$2A, {
        variant: "secondary",
        size: _ctx.size,
        disabled: _ctx.isLoading || isInstalling.value,
        onClick: installAllPacks
      }, {
        default: withCtx(() => [
          _ctx.hasConflict && !isInstalling.value && !_ctx.isLoading ? (openBlock(), createElementBlock("i", _hoisted_1$10)) : _ctx.isLoading || isInstalling.value ? (openBlock(), createBlock(DotSpinner, {
            key: 1,
            duration: "1s",
            size: _ctx.size === "sm" ? 12 : 16
          }, null, 8, ["size"])) : createCommentVNode("", true),
          createBaseVNode("span", null, toDisplayString(computedLabel.value), 1)
        ]),
        _: 1
      }, 8, ["size", "disabled"]);
    };
  }
});
const _hoisted_1$$ = {
  key: 0,
  class: "flex w-full items-center justify-between gap-2 py-2 px-4"
};
const _hoisted_2$M = {
  key: 1,
  class: "flex justify-end gap-1 py-2 px-4"
};
const _sfc_main$1a = /* @__PURE__ */ defineComponent({
  __name: "MissingNodesFooter",
  setup(__props) {
    const dialogStore = useDialogStore();
    const { t: t2 } = useI18n();
    const handleGotItClick = /* @__PURE__ */ __name(() => {
      dialogStore.closeDialog({ key: "global-missing-nodes" });
    }, "handleGotItClick");
    const { missingNodePacks, isLoading, error } = useMissingNodes();
    const comfyManagerStore = useComfyManagerStore();
    const managerState = useManagerState();
    const isInstalling = computed(() => {
      if (!missingNodePacks.value?.length) return false;
      return missingNodePacks.value.some(
        (pack) => comfyManagerStore.isPackInstalling(pack.id)
      );
    });
    const showManagerButtons = computed(() => {
      return managerState.shouldShowManagerButtons.value;
    });
    const showInstallAllButton = computed(() => {
      return managerState.shouldShowInstallButton.value;
    });
    const openManager = /* @__PURE__ */ __name(async () => {
      await managerState.openManager({
        initialTab: ManagerTab.Missing,
        showToastOnLegacyError: true
      });
    }, "openManager");
    const allMissingNodesInstalled = computed(() => {
      return !isLoading.value && !isInstalling.value && missingNodePacks.value?.length === 0;
    });
    watch(allMissingNodesInstalled, async (allInstalled) => {
      if (allInstalled && showInstallAllButton.value) {
        await nextTick();
        dialogStore.closeDialog({ key: "global-missing-nodes" });
        useToastStore().add({
          severity: "success",
          summary: t2("g.success"),
          detail: t2("manager.allMissingNodesInstalled"),
          life: 3e3
        });
      }
    });
    return (_ctx, _cache) => {
      return unref(isCloud) ? (openBlock(), createElementBlock("div", _hoisted_1$$, [
        createVNode(_sfc_main$2A, {
          variant: "textonly",
          size: "sm",
          as: "a",
          href: "https://www.comfy.org/cloud",
          target: "_blank",
          rel: "noopener noreferrer"
        }, {
          default: withCtx(() => [
            _cache[0] || (_cache[0] = createBaseVNode("i", { class: "icon-[lucide--info]" }, null, -1)),
            createBaseVNode("span", null, toDisplayString(_ctx.$t("missingNodes.cloud.learnMore")), 1)
          ]),
          _: 1
        }),
        createVNode(_sfc_main$2A, {
          variant: "secondary",
          size: "md",
          onClick: handleGotItClick
        }, {
          default: withCtx(() => [
            createTextVNode(toDisplayString(_ctx.$t("missingNodes.cloud.gotIt")), 1)
          ]),
          _: 1
        })
      ])) : showManagerButtons.value ? (openBlock(), createElementBlock("div", _hoisted_2$M, [
        createVNode(_sfc_main$2A, {
          variant: "textonly",
          size: "sm",
          onClick: openManager
        }, {
          default: withCtx(() => [
            createTextVNode(toDisplayString(_ctx.$t("g.openManager")), 1)
          ]),
          _: 1
        }),
        showInstallAllButton.value ? (openBlock(), createBlock(_sfc_main$1b, {
          key: 0,
          type: "secondary",
          size: "md",
          disabled: unref(isLoading) || !!unref(error) || unref(missingNodePacks).length === 0 || isInstalling.value,
          "is-loading": unref(isLoading),
          "node-packs": unref(missingNodePacks),
          label: unref(isLoading) ? _ctx.$t("manager.gettingInfo") : _ctx.$t("manager.installAllMissingNodes")
        }, null, 8, ["disabled", "is-loading", "node-packs", "label"])) : createCommentVNode("", true)
      ])) : createCommentVNode("", true);
    };
  }
});
const _hoisted_1$_ = { class: "flex w-full items-center justify-between p-4" };
const _hoisted_2$L = { class: "flex items-center gap-2" };
const _hoisted_3$w = { class: "m-0 text-sm" };
const _sfc_main$19 = /* @__PURE__ */ defineComponent({
  __name: "MissingNodesHeader",
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$_, [
        createBaseVNode("div", _hoisted_2$L, [
          _cache[0] || (_cache[0] = createBaseVNode("i", { class: "icon-[lucide--triangle-alert] text-gold-600" }, null, -1)),
          createBaseVNode("p", _hoisted_3$w, toDisplayString(unref(isCloud) ? _ctx.$t("missingNodes.cloud.title") : _ctx.$t("missingNodes.oss.title")), 1)
        ])
      ]);
    };
  }
});
const _sfc_main$18 = /* @__PURE__ */ defineComponent({
  __name: "FindIssueButton",
  props: {
    errorMessage: {},
    repoOwner: {},
    repoName: {}
  },
  setup(__props) {
    const props = __props;
    const queryString = computed(() => props.errorMessage + " is:issue");
    const openGitHubIssues = /* @__PURE__ */ __name(() => {
      const query = encodeURIComponent(queryString.value);
      const url = `https://github.com/${props.repoOwner}/${props.repoName}/issues?q=${query}`;
      window.open(url, "_blank");
    }, "openGitHubIssues");
    return (_ctx, _cache) => {
      return openBlock(), createBlock(_sfc_main$2A, {
        variant: "secondary",
        onClick: openGitHubIssues
      }, {
        default: withCtx(() => [
          _cache[0] || (_cache[0] = createBaseVNode("i", { class: "pi pi-github" }, null, -1)),
          createTextVNode(" " + toDisplayString(_ctx.$t("g.findIssues")), 1)
        ]),
        _: 1
      });
    };
  }
});
function generateErrorReport(error) {
  const MAX_JSON_LENGTH = 2e4;
  const workflowJSONString = JSON.stringify(error.workflow);
  const workflowText = workflowJSONString.length > MAX_JSON_LENGTH ? "Workflow too large. Please manually upload the workflow from local file system." : workflowJSONString;
  const systemStats = error.systemStats;
  return `
# ComfyUI Error Report
${error ? `## Error Details
- **Node ID:** ${error.nodeId || "N/A"}
- **Node Type:** ${error.nodeType || "N/A"}
- **Exception Type:** ${error.exceptionType || "N/A"}
- **Exception Message:** ${error.exceptionMessage || "N/A"}
## Stack Trace
\`\`\`
${error.traceback || "No stack trace available"}
\`\`\`` : ""}
## System Information
- **ComfyUI Version:** ${systemStats.system.comfyui_version}
- **Arguments:** ${systemStats.system.argv.join(" ")}
- **OS:** ${systemStats.system.os}
- **Python Version:** ${systemStats.system.python_version}
- **Embedded Python:** ${systemStats.system.embedded_python}
- **PyTorch Version:** ${systemStats.system.pytorch_version}
## Devices
${systemStats.devices.map(
    (device) => `
- **Name:** ${device.name}
  - **Type:** ${device.type}
  - **VRAM Total:** ${device.vram_total}
  - **VRAM Free:** ${device.vram_free}
  - **Torch VRAM Total:** ${device.torch_vram_total}
  - **Torch VRAM Free:** ${device.torch_vram_free}
`
  ).join("\n")}
## Logs
\`\`\`
${error.serverLogs}
\`\`\`
## Attached Workflow
Please make sure that workflow does not contain any sensitive information such as API keys or passwords.
\`\`\`
${workflowText}
\`\`\`

## Additional Context
(Please add any additional context or steps to reproduce the error here)
`;
}
__name(generateErrorReport, "generateErrorReport");
const _hoisted_1$Z = { class: "comfy-error-report flex flex-col gap-4" };
const _hoisted_2$K = { class: "font-bold" };
const _hoisted_3$v = { class: "flex justify-center gap-2" };
const _hoisted_4$p = { class: "break-words whitespace-pre-wrap" };
const _hoisted_5$n = { class: "flex justify-end gap-4" };
const repoOwner = "comfyanonymous";
const repoName = "ComfyUI";
const _sfc_main$17 = /* @__PURE__ */ defineComponent({
  __name: "ErrorDialogContent",
  props: {
    error: {}
  },
  setup(__props) {
    const reportContent = ref("");
    const reportOpen = ref(false);
    const showReport = /* @__PURE__ */ __name(() => {
      reportOpen.value = true;
    }, "showReport");
    const toast = useToast();
    const { t: t2 } = useI18n();
    const systemStatsStore = useSystemStatsStore();
    const title = computed(
      () => __props.error.nodeType ?? __props.error.exceptionType ?? t2("errorDialog.defaultTitle")
    );
    const showContactSupport = /* @__PURE__ */ __name(async () => {
      await useCommandStore().execute("Comfy.ContactSupport");
    }, "showContactSupport");
    onMounted(async () => {
      if (!systemStatsStore.systemStats) {
        await systemStatsStore.refetchSystemStats();
      }
      try {
        const [logs] = await Promise.all([api.getLogs()]);
        reportContent.value = generateErrorReport({
          systemStats: systemStatsStore.systemStats,
          serverLogs: logs,
          workflow: app$1.rootGraph.serialize(),
          exceptionType: __props.error.exceptionType,
          exceptionMessage: __props.error.exceptionMessage,
          traceback: __props.error.traceback,
          nodeId: __props.error.nodeId,
          nodeType: __props.error.nodeType
        });
      } catch (error) {
        console.error("Error fetching logs:", error);
        toast.add({
          severity: "error",
          summary: t2("g.error"),
          detail: t2("toastMessages.failedToFetchLogs"),
          life: 5e3
        });
      }
    });
    const { copyToClipboard } = useCopyToClipboard();
    const copyReportToClipboard = /* @__PURE__ */ __name(async () => {
      await copyToClipboard(reportContent.value);
    }, "copyReportToClipboard");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$Z, [
        createVNode(NoResultsPlaceholder, {
          class: "pb-0",
          icon: "pi pi-exclamation-circle",
          title: title.value,
          message: _ctx.error.exceptionMessage,
          "text-class": "break-words max-w-[60vw]"
        }, null, 8, ["title", "message"]),
        _ctx.error.extensionFile ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
          createBaseVNode("span", null, toDisplayString(unref(t2)("errorDialog.extensionFileHint")) + ":", 1),
          _cache[0] || (_cache[0] = createBaseVNode("br", null, null, -1)),
          createBaseVNode("span", _hoisted_2$K, toDisplayString(_ctx.error.extensionFile), 1)
        ], 64)) : createCommentVNode("", true),
        createBaseVNode("div", _hoisted_3$v, [
          withDirectives(createVNode(_sfc_main$2A, {
            variant: "textonly",
            onClick: showReport
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(_ctx.$t("g.showReport")), 1)
            ]),
            _: 1
          }, 512), [
            [vShow, !reportOpen.value]
          ]),
          withDirectives(createVNode(_sfc_main$2A, {
            variant: "textonly",
            onClick: showContactSupport
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(_ctx.$t("issueReport.helpFix")), 1)
            ]),
            _: 1
          }, 512), [
            [vShow, !reportOpen.value]
          ])
        ]),
        reportOpen.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
          createVNode(unref(script$h)),
          createVNode(unref(script$d), { class: "h-[400px] w-full max-w-[80vw]" }, {
            default: withCtx(() => [
              createBaseVNode("pre", _hoisted_4$p, toDisplayString(reportContent.value), 1)
            ]),
            _: 1
          }),
          createVNode(unref(script$h))
        ], 64)) : createCommentVNode("", true),
        createBaseVNode("div", _hoisted_5$n, [
          createVNode(_sfc_main$18, {
            "error-message": _ctx.error.exceptionMessage,
            "repo-owner": repoOwner,
            "repo-name": repoName
          }, null, 8, ["error-message"]),
          reportOpen.value ? (openBlock(), createBlock(_sfc_main$2A, {
            key: 0,
            onClick: copyReportToClipboard
          }, {
            default: withCtx(() => [
              _cache[1] || (_cache[1] = createBaseVNode("i", { class: "pi pi-copy" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("g.copyToClipboard")), 1)
            ]),
            _: 1
          })) : createCommentVNode("", true)
        ])
      ]);
    };
  }
});
function useCivitaiModel(url) {
  const createModelVersionUrl = /* @__PURE__ */ __name((modelId) => `https://civitai.com/api/v1/model-versions/${modelId}`, "createModelVersionUrl");
  const extractModelIdFromUrl = /* @__PURE__ */ __name(() => {
    const urlObj = new URL(url);
    return urlObj.pathname.split("/").pop() || null;
  }, "extractModelIdFromUrl");
  const fetchModelData = /* @__PURE__ */ __name(async () => {
    const modelId = extractModelIdFromUrl();
    if (!modelId) return null;
    const apiUrl = createModelVersionUrl(modelId);
    const res = await fetch(apiUrl);
    return res.json();
  }, "fetchModelData");
  const findMatchingFileSize = /* @__PURE__ */ __name(() => {
    const matchingFile = modelData.value?.files?.find(
      (file) => file.downloadUrl && url.startsWith(file.downloadUrl)
    );
    return matchingFile?.sizeKB ? matchingFile.sizeKB << 10 : null;
  }, "findMatchingFileSize");
  const {
    state: modelData,
    isLoading,
    error
  } = useAsyncState(fetchModelData, null, {
    immediate: true
  });
  const fileSize = computed(
    () => !isLoading.value ? findMatchingFileSize() : null
  );
  return {
    fileSize,
    isLoading,
    error,
    modelData
  };
}
__name(useCivitaiModel, "useCivitaiModel");
function useDownload(url, fileName) {
  const fileSize = ref(null);
  const error = ref(null);
  const setFileSize = /* @__PURE__ */ __name((size) => {
    fileSize.value = size;
  }, "setFileSize");
  const fetchFileSize = /* @__PURE__ */ __name(async () => {
    try {
      const response = await fetch(url, { method: "HEAD" });
      if (!response.ok) throw new Error("Failed to fetch file size");
      const size = response.headers.get("content-length");
      if (size) {
        setFileSize(parseInt(size));
      } else {
        console.error('"content-length" header not found');
        return null;
      }
    } catch (e2) {
      console.error("Error fetching file size:", e2);
      error.value = e2 instanceof Error ? e2 : new Error(String(e2));
      return null;
    }
  }, "fetchFileSize");
  const triggerBrowserDownload = /* @__PURE__ */ __name(() => {
    const link = document.createElement("a");
    if (url.includes("huggingface.co") && error.value) {
      link.href = downloadUrlToHfRepoUrl(url);
    } else {
      link.href = url;
      link.download = url.split("/").pop() || "download";
    }
    link.target = "_blank";
    link.rel = "noopener noreferrer";
    link.click();
  }, "triggerBrowserDownload");
  onMounted(() => {
    if (isCivitaiModelUrl(url)) {
      const { fileSize: civitaiSize, error: civitaiErr } = useCivitaiModel(url);
      whenever(civitaiSize, setFileSize);
      whenever(civitaiErr, fetchFileSize, { once: true });
    } else {
      void fetchFileSize();
    }
  });
  return {
    triggerBrowserDownload,
    fileSize
  };
}
__name(useDownload, "useDownload");
const _hoisted_1$Y = { class: "flex flex-col" };
const _hoisted_2$J = { class: "flex flex-row items-center gap-2" };
const _hoisted_3$u = {
  key: 0,
  class: "pi pi-check text-green-500"
};
const _hoisted_4$o = { class: "file-info" };
const _hoisted_5$m = { class: "file-details" };
const _hoisted_6$f = ["title"];
const _hoisted_7$d = {
  key: 0,
  class: "file-error"
};
const _hoisted_8$a = { class: "file-action flex flex-row items-center gap-2" };
const _hoisted_9$9 = {
  key: 0,
  class: "flex flex-row items-center gap-2"
};
const _sfc_main$16 = /* @__PURE__ */ defineComponent({
  __name: "ElectronFileDownload",
  props: {
    url: {},
    hint: {},
    label: {},
    error: {}
  },
  setup(__props) {
    const props = __props;
    const { t: t2 } = useI18n();
    const label = computed(() => props.label || props.url.split("/").pop());
    const hint = computed(() => props.hint || props.url);
    const download = useDownload(props.url);
    const downloadProgress = ref(0);
    const status = ref(null);
    const fileSize = computed(
      () => download.fileSize.value ? formatSize(download.fileSize.value) : "?"
    );
    const { copyToClipboard } = useCopyToClipboard();
    const electronDownloadStore = useElectronDownloadStore();
    const [savePath, filename] = props.label.split("/");
    electronDownloadStore.$subscribe((_, { downloads }) => {
      const download2 = downloads.find((download3) => props.url === download3.url);
      if (download2) {
        downloadProgress.value = Number((download2.progress * 100).toFixed(1));
        status.value = download2.status;
      }
    });
    const triggerDownload = /* @__PURE__ */ __name(async () => {
      await electronDownloadStore.start({
        url: props.url,
        savePath: savePath.trim(),
        filename: filename.trim()
      });
    }, "triggerDownload");
    const triggerCancelDownload = /* @__PURE__ */ __name(() => electronDownloadStore.cancel(props.url), "triggerCancelDownload");
    const triggerPauseDownload = /* @__PURE__ */ __name(() => electronDownloadStore.pause(props.url), "triggerPauseDownload");
    const triggerResumeDownload = /* @__PURE__ */ __name(() => electronDownloadStore.resume(props.url), "triggerResumeDownload");
    const copyURL = /* @__PURE__ */ __name(async () => {
      await copyToClipboard(props.url);
    }, "copyURL");
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", _hoisted_1$Y, [
        createBaseVNode("div", _hoisted_2$J, [
          status.value === "completed" ? (openBlock(), createElementBlock("i", _hoisted_3$u)) : createCommentVNode("", true),
          createBaseVNode("div", _hoisted_4$o, [
            createBaseVNode("div", _hoisted_5$m, [
              createBaseVNode("span", {
                class: "file-type",
                title: hint.value
              }, toDisplayString(label.value), 9, _hoisted_6$f)
            ]),
            props.error ? (openBlock(), createElementBlock("div", _hoisted_7$d, toDisplayString(props.error), 1)) : createCommentVNode("", true)
          ]),
          createBaseVNode("div", _hoisted_8$a, [
            status.value === null || status.value === "error" ? (openBlock(), createBlock(_sfc_main$2A, {
              key: 0,
              class: "file-action-button",
              variant: "secondary",
              size: "sm",
              disabled: !!props.error,
              onClick: triggerDownload
            }, {
              default: withCtx(() => [
                _cache[0] || (_cache[0] = createBaseVNode("i", { class: "pi pi-download" }, null, -1)),
                createTextVNode(" " + toDisplayString(_ctx.$t("g.download") + " (" + fileSize.value + ")"), 1)
              ]),
              _: 1
            }, 8, ["disabled"])) : createCommentVNode("", true),
            (status.value === null || status.value === "error") && !!props.url ? (openBlock(), createBlock(_sfc_main$2A, {
              key: 1,
              variant: "secondary",
              size: "sm",
              onClick: copyURL
            }, {
              default: withCtx(() => [
                createTextVNode(toDisplayString(_ctx.$t("g.copyURL")), 1)
              ]),
              _: 1
            })) : createCommentVNode("", true)
          ])
        ]),
        status.value === "in_progress" || status.value === "paused" ? (openBlock(), createElementBlock("div", _hoisted_9$9, [
          createVNode(unref(script$j), {
            class: "flex-1",
            value: downloadProgress.value,
            "show-value": downloadProgress.value > 10
          }, null, 8, ["value", "show-value"]),
          status.value === "in_progress" ? withDirectives((openBlock(), createBlock(_sfc_main$2A, {
            key: 0,
            class: "file-action-button",
            variant: "secondary",
            size: "sm",
            disabled: !!props.error,
            onClick: triggerPauseDownload
          }, {
            default: withCtx(() => _cache[1] || (_cache[1] = [
              createBaseVNode("i", { class: "pi pi-pause-circle" }, null, -1)
            ])),
            _: 1
          }, 8, ["disabled"])), [
            [
              _directive_tooltip,
              unref(t2)("electronFileDownload.pause"),
              void 0,
              { top: true }
            ]
          ]) : createCommentVNode("", true),
          status.value === "paused" ? withDirectives((openBlock(), createBlock(_sfc_main$2A, {
            key: 1,
            class: "file-action-button",
            variant: "secondary",
            size: "sm",
            "aria-label": unref(t2)("electronFileDownload.resume"),
            disabled: !!props.error,
            onClick: triggerResumeDownload
          }, {
            default: withCtx(() => _cache[2] || (_cache[2] = [
              createBaseVNode("i", { class: "pi pi-play-circle" }, null, -1)
            ])),
            _: 1
          }, 8, ["aria-label", "disabled"])), [
            [
              _directive_tooltip,
              unref(t2)("electronFileDownload.resume"),
              void 0,
              { top: true }
            ]
          ]) : createCommentVNode("", true),
          withDirectives((openBlock(), createBlock(_sfc_main$2A, {
            class: "file-action-button",
            variant: "destructive",
            size: "sm",
            "aria-label": unref(t2)("electronFileDownload.cancel"),
            disabled: !!props.error,
            onClick: triggerCancelDownload
          }, {
            default: withCtx(() => _cache[3] || (_cache[3] = [
              createBaseVNode("i", { class: "pi pi-times-circle" }, null, -1)
            ])),
            _: 1
          }, 8, ["aria-label", "disabled"])), [
            [
              _directive_tooltip,
              unref(t2)("electronFileDownload.cancel"),
              void 0,
              { top: true }
            ]
          ])
        ])) : createCommentVNode("", true)
      ]);
    };
  }
});
const _hoisted_1$X = { class: "flex flex-row items-center gap-2" };
const _hoisted_2$I = ["title"];
const _sfc_main$15 = /* @__PURE__ */ defineComponent({
  __name: "FileDownload",
  props: {
    url: {},
    hint: {},
    label: {},
    error: {}
  },
  setup(__props) {
    const props = __props;
    const label = computed(() => props.label || props.url.split("/").pop());
    const hint = computed(() => props.hint || props.url);
    const download = useDownload(props.url);
    const fileSize = computed(
      () => download.fileSize.value ? formatSize(download.fileSize.value) : "?"
    );
    const copyURL = /* @__PURE__ */ __name(async () => {
      await copyToClipboard(props.url);
    }, "copyURL");
    const { copyToClipboard } = useCopyToClipboard();
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$X, [
        createBaseVNode("div", null, [
          createBaseVNode("div", null, [
            createBaseVNode("span", { title: hint.value }, toDisplayString(label.value), 9, _hoisted_2$I)
          ]),
          props.error ? (openBlock(), createBlock(unref(script), {
            key: 0,
            severity: "error",
            icon: "pi pi-exclamation-triangle",
            size: "small",
            variant: "outlined",
            class: "my-2 h-min max-w-xs px-1",
            title: props.error,
            pt: {
              text: { class: "overflow-hidden text-ellipsis" }
            }
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(props.error), 1)
            ]),
            _: 1
          }, 8, ["title"])) : createCommentVNode("", true)
        ]),
        createBaseVNode("div", null, [
          createVNode(_sfc_main$2A, {
            variant: "secondary",
            disabled: !!props.error,
            title: props.url,
            onClick: unref(download).triggerBrowserDownload
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(_ctx.$t("g.download") + " (" + fileSize.value + ")"), 1)
            ]),
            _: 1
          }, 8, ["disabled", "title", "onClick"])
        ]),
        createBaseVNode("div", null, [
          createVNode(_sfc_main$2A, {
            variant: "secondary",
            disabled: !!props.error,
            onClick: copyURL
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(_ctx.$t("g.copyURL")), 1)
            ]),
            _: 1
          }, 8, ["disabled"])
        ])
      ]);
    };
  }
});
const _hoisted_1$W = { class: "mb-4 flex gap-1" };
const _hoisted_2$H = { for: "doNotAskAgain" };
const _sfc_main$14 = /* @__PURE__ */ defineComponent({
  __name: "MissingModelsWarning",
  props: {
    missingModels: {},
    paths: {}
  },
  setup(__props) {
    const allowedSources = [
      "https://civitai.com/",
      "https://huggingface.co/",
      "http://localhost:"
      // Included for testing usage only
    ];
    const allowedSuffixes = [".safetensors", ".sft"];
    const whiteListedUrls = /* @__PURE__ */ new Set([
      "https://huggingface.co/stabilityai/stable-zero123/resolve/main/stable_zero123.ckpt",
      "https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_depth_sd14v1.pth?download=true",
      "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth"
    ]);
    const props = __props;
    const { t: t2 } = useI18n();
    const doNotAskAgain = ref(false);
    const modelDownloads = ref({});
    const missingModels = computed(() => {
      return props.missingModels.map((model) => {
        const paths = props.paths[model.directory];
        if (model.directory_invalid || !paths) {
          return {
            label: `${model.directory} / ${model.name}`,
            url: model.url,
            error: "Invalid directory specified (does this require custom nodes?)"
          };
        }
        const downloadInfo = modelDownloads.value[model.name] ?? {
          downloading: false,
          completed: false,
          progress: 0,
          error: null,
          name: model.name,
          directory: model.directory,
          url: model.url,
          folder_path: paths[0]
        };
        modelDownloads.value[model.name] = downloadInfo;
        if (!whiteListedUrls.has(model.url)) {
          if (!allowedSources.some((source) => model.url.startsWith(source))) {
            return {
              label: `${model.directory} / ${model.name}`,
              url: model.url,
              error: `Download not allowed from source '${model.url}', only allowed from '${allowedSources.join("', '")}'`
            };
          }
          if (!allowedSuffixes.some((suffix) => model.name.endsWith(suffix))) {
            return {
              label: `${model.directory} / ${model.name}`,
              url: model.url,
              error: `Only allowed suffixes are: '${allowedSuffixes.join("', '")}'`
            };
          }
        }
        return {
          url: model.url,
          label: `${model.directory} / ${model.name}`,
          downloading: downloadInfo.downloading,
          completed: downloadInfo.completed,
          progress: downloadInfo.progress,
          error: downloadInfo.error,
          name: model.name,
          paths,
          folderPath: downloadInfo.folder_path
        };
      });
    });
    onBeforeUnmount(async () => {
      if (doNotAskAgain.value) {
        await useSettingStore().set(
          "Comfy.Workflow.ShowMissingModelsWarning",
          false
        );
      }
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock(Fragment, null, [
        createVNode(NoResultsPlaceholder, {
          class: "pb-0",
          icon: "pi pi-exclamation-circle",
          title: unref(t2)("missingModelsDialog.missingModels"),
          message: unref(t2)("missingModelsDialog.missingModelsMessage")
        }, null, 8, ["title", "message"]),
        createBaseVNode("div", _hoisted_1$W, [
          createVNode(unref(script$b), {
            modelValue: doNotAskAgain.value,
            "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => doNotAskAgain.value = $event),
            binary: "",
            "input-id": "doNotAskAgain"
          }, null, 8, ["modelValue"]),
          createBaseVNode("label", _hoisted_2$H, toDisplayString(unref(t2)("missingModelsDialog.doNotAskAgain")), 1)
        ]),
        createVNode(unref(script$p), {
          options: missingModels.value,
          class: "comfy-missing-models"
        }, {
          option: withCtx(({ option }) => [
            unref(isElectron)() ? (openBlock(), createBlock(Suspense, { key: 0 }, {
              default: withCtx(() => [
                createVNode(_sfc_main$16, {
                  url: option.url,
                  label: option.label,
                  error: option.error
                }, null, 8, ["url", "label", "error"])
              ]),
              _: 2
            }, 1024)) : (openBlock(), createBlock(_sfc_main$15, {
              key: 1,
              url: option.url,
              label: option.label,
              error: option.error
            }, null, 8, ["url", "label", "error"]))
          ]),
          _: 1
        }, 8, ["options"])
      ], 64);
    };
  }
});
const MissingModelsWarning = /* @__PURE__ */ _export_sfc(_sfc_main$14, [["__scopeId", "data-v-520e7481"]]);
const _hoisted_1$V = { class: "prompt-dialog-content flex flex-col gap-2 pt-8" };
const _sfc_main$13 = /* @__PURE__ */ defineComponent({
  __name: "PromptDialogContent",
  props: {
    message: {},
    defaultValue: {},
    onConfirm: { type: Function },
    placeholder: {}
  },
  setup(__props) {
    const props = __props;
    const inputValue = ref(props.defaultValue);
    const onConfirm = /* @__PURE__ */ __name(() => {
      props.onConfirm(inputValue.value);
      useDialogStore().closeDialog();
    }, "onConfirm");
    const inputRef = ref();
    const selectAllText = /* @__PURE__ */ __name(() => {
      if (!inputRef.value) return;
      const inputElement = inputRef.value.$el;
      inputElement.setSelectionRange(0, inputElement.value.length);
    }, "selectAllText");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$V, [
        createVNode(unref(script$q), null, {
          default: withCtx(() => [
            createVNode(unref(script$5), {
              ref_key: "inputRef",
              ref: inputRef,
              modelValue: inputValue.value,
              "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => inputValue.value = $event),
              placeholder: _ctx.placeholder,
              autofocus: "",
              onKeyup: withKeys(onConfirm, ["enter"]),
              onFocus: selectAllText
            }, null, 8, ["modelValue", "placeholder"]),
            createBaseVNode("label", null, toDisplayString(_ctx.message), 1)
          ]),
          _: 1
        }),
        createVNode(_sfc_main$2A, { onClick: onConfirm }, {
          default: withCtx(() => [
            createTextVNode(toDisplayString(_ctx.$t("g.confirm")), 1)
          ]),
          _: 1
        })
      ]);
    };
  }
});
const _imports_0$1 = "" + new URL("images/comfy-logo-mono.svg", import.meta.url).href;
const HOST_WHITELIST = ["localhost"];
function normalizeHost(input) {
  let h2 = (input || "").trim().toLowerCase();
  h2 = h2.replace(/\.$/, "");
  const mBracket = h2.match(/^\[([^\]]+)\]:(\d+)$/);
  if (mBracket) {
    h2 = mBracket[1];
  } else {
    const mPort = h2.match(/^([^:]+):(\d+)$/);
    if (mPort) h2 = mPort[1];
  }
  h2 = h2.replace(/^\[|\]$/g, "");
  return h2;
}
__name(normalizeHost, "normalizeHost");
function isHostWhitelisted(rawHost) {
  const host = normalizeHost(rawHost);
  if (isLocalhostLabel(host)) return true;
  if (isIPv4Loopback(host)) return true;
  if (isIPv6Loopback(host)) return true;
  if (isComfyOrgHost(host)) return true;
  const normalizedList = HOST_WHITELIST.map(normalizeHost);
  return normalizedList.includes(host);
}
__name(isHostWhitelisted, "isHostWhitelisted");
function isLocalhostLabel(h2) {
  return h2 === "localhost" || h2.endsWith(".localhost");
}
__name(isLocalhostLabel, "isLocalhostLabel");
const IPV4_OCTET = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|0?\\d?\\d)";
const V4_LOOPBACK_RE = new RegExp(
  "^127\\." + IPV4_OCTET + "\\." + IPV4_OCTET + "\\." + IPV4_OCTET + "$"
);
function isIPv4Loopback(h2) {
  return V4_LOOPBACK_RE.test(h2);
}
__name(isIPv4Loopback, "isIPv4Loopback");
const V6_FULL_LOOPBACK_RE = /^(?:0{1,4}:){7}0{0,3}1$/i;
const V6_COMPRESSED_LOOPBACK_RE = /^((?:0{1,4}(?::0{1,4}){0,6})?)::((?:0{1,4}:){0,6})0{0,3}1$/i;
function isIPv6Loopback(h2) {
  if (V6_FULL_LOOPBACK_RE.test(h2)) return true;
  const m = h2.match(V6_COMPRESSED_LOOPBACK_RE);
  if (!m) return false;
  const leftCount = m[1] ? m[1].match(/0{1,4}:/gi)?.length ?? 0 : 0;
  const rightCount = m[2] ? m[2].match(/0{1,4}:/gi)?.length ?? 0 : 0;
  return leftCount + rightCount <= 6;
}
__name(isIPv6Loopback, "isIPv6Loopback");
const COMFY_ORG_HOST = /\.comfy\.org$/;
function isComfyOrgHost(h2) {
  return COMFY_ORG_HOST.test(h2);
}
__name(isComfyOrgHost, "isComfyOrgHost");
const VALID_STATUS_CODES = [200, 201, 301, 302, 307, 308];
const checkUrlReachable = /* @__PURE__ */ __name(async (url) => {
  try {
    const response = await axios.head(url);
    return VALID_STATUS_CODES.includes(response.status);
  } catch {
    return false;
  }
}, "checkUrlReachable");
async function isInChina() {
  const isChineseLocale = navigator.language.toLowerCase().startsWith("zh-cn");
  try {
    const googleTest = await Promise.race([
      fetch("https://www.google.com", {
        mode: "no-cors",
        cache: "no-cache"
      }),
      new Promise((_, reject) => setTimeout(() => reject(), 2e3))
    ]);
    if (googleTest) {
      return false;
    }
  } catch {
    if (isChineseLocale) {
      return true;
    }
    try {
      const start = performance.now();
      await fetch("https://www.baidu.com", {
        mode: "no-cors",
        cache: "no-cache"
      });
      const latency = performance.now() - start;
      return latency < 150;
    } catch {
      return isChineseLocale;
    }
  }
  return false;
}
__name(isInChina, "isInChina");
const apiKeySchema = z.object({
  apiKey: z.string().trim().startsWith("comfyui-", t("validation.prefix", { prefix: "comfyui-" })).length(72, t("validation.length", { length: 72 }))
});
const signInSchema = z.object({
  email: z.string().email(t("validation.invalidEmail")).min(1, t("validation.required")),
  password: z.string().min(1, t("validation.required"))
});
const passwordSchema = z.object({
  password: z.string().min(8, t("validation.minLength", { length: 8 })).max(32, t("validation.maxLength", { length: 32 })).regex(/[A-Z]/, t("validation.password.uppercase")).regex(/[a-z]/, t("validation.password.lowercase")).regex(/\d/, t("validation.password.number")).regex(/[^A-Za-z0-9]/, t("validation.password.special")),
  confirmPassword: z.string().min(1, t("validation.required"))
});
const updatePasswordSchema = passwordSchema.refine(
  (data) => data.password === data.confirmPassword,
  {
    message: t("validation.password.match"),
    path: ["confirmPassword"]
  }
);
const signUpSchema = passwordSchema.extend({
  email: z.string().email(t("validation.invalidEmail")).min(1, t("validation.required"))
}).refine((data) => data.password === data.confirmPassword, {
  message: t("validation.password.match"),
  path: ["confirmPassword"]
});
const _hoisted_1$U = { class: "flex flex-col gap-6" };
const _hoisted_2$G = { class: "mb-8 flex flex-col gap-4" };
const _hoisted_3$t = { class: "my-0 text-2xl leading-normal font-medium" };
const _hoisted_4$n = { class: "flex flex-col gap-2" };
const _hoisted_5$l = { class: "my-0 text-base text-muted" };
const _hoisted_6$e = {
  href: "https://docs.comfy.org/interface/user#logging-in-with-an-api-key",
  target: "_blank",
  class: "cursor-pointer text-blue-500"
};
const _hoisted_7$c = { class: "flex flex-col gap-2" };
const _hoisted_8$9 = {
  class: "mb-2 text-base font-medium opacity-80",
  for: "comfy-org-api-key"
};
const _hoisted_9$8 = { class: "flex flex-col gap-2" };
const _hoisted_10$7 = { class: "text-muted" };
const _hoisted_11$5 = ["href"];
const _hoisted_12$3 = {
  href: "https://docs.comfy.org/tutorials/api-nodes/overview#log-in-with-api-key-on-non-whitelisted-websites",
  target: "_blank",
  class: "cursor-pointer text-blue-500"
};
const _hoisted_13$3 = { class: "mt-4 flex items-center justify-between" };
const _sfc_main$12 = /* @__PURE__ */ defineComponent({
  __name: "ApiKeyForm",
  emits: ["back", "success"],
  setup(__props, { emit: __emit }) {
    const authStore = useFirebaseAuthStore();
    const apiKeyStore = useApiKeyAuthStore();
    const loading = computed(() => authStore.loading);
    const comfyPlatformBaseUrl = computed(
      () => configValueOrDefault(
        remoteConfig.value,
        "comfy_platform_base_url",
        getComfyPlatformBaseUrl()
      )
    );
    const { t: t2 } = useI18n();
    const emit = __emit;
    const onSubmit = /* @__PURE__ */ __name(async (event) => {
      if (event.valid) {
        await apiKeyStore.storeApiKey(event.values.apiKey);
        emit("success");
      }
    }, "onSubmit");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$U, [
        createBaseVNode("div", _hoisted_2$G, [
          createBaseVNode("h1", _hoisted_3$t, toDisplayString(unref(t2)("auth.apiKey.title")), 1),
          createBaseVNode("div", _hoisted_4$n, [
            createBaseVNode("p", _hoisted_5$l, toDisplayString(unref(t2)("auth.apiKey.description")), 1),
            createBaseVNode("a", _hoisted_6$e, toDisplayString(unref(t2)("g.learnMore")), 1)
          ])
        ]),
        createVNode(unref(script$r), {
          class: "flex flex-col gap-6",
          resolver: unref(zodResolver)(unref(apiKeySchema)),
          onSubmit
        }, {
          default: withCtx(($form) => [
            $form.apiKey?.invalid ? (openBlock(), createBlock(unref(script), {
              key: 0,
              severity: "error",
              class: "mb-4"
            }, {
              default: withCtx(() => [
                createTextVNode(toDisplayString($form.apiKey.error.message), 1)
              ]),
              _: 2
            }, 1024)) : createCommentVNode("", true),
            createBaseVNode("div", _hoisted_7$c, [
              createBaseVNode("label", _hoisted_8$9, toDisplayString(unref(t2)("auth.apiKey.label")), 1),
              createBaseVNode("div", _hoisted_9$8, [
                createVNode(unref(script$5), {
                  "pt:root:id": "comfy-org-api-key",
                  "pt:root:autocomplete": "off",
                  class: "h-10",
                  name: "apiKey",
                  type: "password",
                  placeholder: unref(t2)("auth.apiKey.placeholder"),
                  invalid: $form.apiKey?.invalid
                }, null, 8, ["placeholder", "invalid"]),
                createBaseVNode("small", _hoisted_10$7, [
                  createTextVNode(toDisplayString(unref(t2)("auth.apiKey.helpText")) + " ", 1),
                  createBaseVNode("a", {
                    href: `${comfyPlatformBaseUrl.value}/login`,
                    target: "_blank",
                    class: "cursor-pointer text-blue-500"
                  }, toDisplayString(unref(t2)("auth.apiKey.generateKey")), 9, _hoisted_11$5),
                  _cache[1] || (_cache[1] = createBaseVNode("span", { class: "mx-1" }, "•", -1)),
                  createBaseVNode("a", _hoisted_12$3, toDisplayString(unref(t2)("auth.apiKey.whitelistInfo")), 1)
                ])
              ])
            ]),
            createBaseVNode("div", _hoisted_13$3, [
              createVNode(_sfc_main$2A, {
                type: "button",
                variant: "textonly",
                onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("back"))
              }, {
                default: withCtx(() => [
                  createTextVNode(toDisplayString(unref(t2)("g.back")), 1)
                ]),
                _: 1
              }),
              createVNode(_sfc_main$2A, {
                type: "submit",
                variant: "primary",
                loading: loading.value,
                disabled: loading.value
              }, {
                default: withCtx(() => [
                  createTextVNode(toDisplayString(unref(t2)("g.save")), 1)
                ]),
                _: 1
              }, 8, ["loading", "disabled"])
            ])
          ]),
          _: 1
        }, 8, ["resolver"])
      ]);
    };
  }
});
const _hoisted_1$T = { class: "flex flex-col gap-2" };
const _hoisted_2$F = {
  key: 0,
  class: "text-red-500"
};
const _hoisted_3$s = { class: "flex flex-col gap-2" };
const _hoisted_4$m = { class: "mb-2 flex items-center justify-between" };
const _hoisted_5$k = {
  class: "text-base font-medium opacity-80",
  for: "comfy-org-sign-in-password"
};
const _hoisted_6$d = ["onClick"];
const _hoisted_7$b = {
  key: 0,
  class: "text-red-500"
};
const emailInputId = "comfy-org-sign-in-email";
const _sfc_main$11 = /* @__PURE__ */ defineComponent({
  __name: "SignInForm",
  emits: ["submit"],
  setup(__props, { emit: __emit }) {
    const authStore = useFirebaseAuthStore();
    const firebaseAuthActions = useFirebaseAuthActions();
    const loading = computed(() => authStore.loading);
    const toast = useToast();
    const { t: t2 } = useI18n();
    const emit = __emit;
    const onSubmit = useThrottleFn((event) => {
      if (event.valid) {
        emit("submit", event.values);
      }
    }, 1500);
    const handleForgotPassword = /* @__PURE__ */ __name(async (email, isValid) => {
      if (!email || !isValid) {
        toast.add({
          severity: "warn",
          summary: t2("auth.login.emailPlaceholder"),
          life: 5e3
        });
        document.getElementById(emailInputId)?.focus?.();
        return;
      }
      await firebaseAuthActions.sendPasswordReset(email);
    }, "handleForgotPassword");
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$r), {
        class: "flex flex-col gap-6",
        resolver: unref(zodResolver)(unref(signInSchema)),
        onSubmit: unref(onSubmit)
      }, {
        default: withCtx(($form) => [
          createBaseVNode("div", _hoisted_1$T, [
            createBaseVNode("label", {
              class: "mb-2 text-base font-medium opacity-80",
              for: emailInputId
            }, toDisplayString(unref(t2)("auth.login.emailLabel")), 1),
            createVNode(unref(script$5), {
              id: emailInputId,
              autocomplete: "email",
              class: "h-10",
              name: "email",
              type: "text",
              placeholder: unref(t2)("auth.login.emailPlaceholder"),
              invalid: $form.email?.invalid
            }, null, 8, ["placeholder", "invalid"]),
            $form.email?.invalid ? (openBlock(), createElementBlock("small", _hoisted_2$F, toDisplayString($form.email.error.message), 1)) : createCommentVNode("", true)
          ]),
          createBaseVNode("div", _hoisted_3$s, [
            createBaseVNode("div", _hoisted_4$m, [
              createBaseVNode("label", _hoisted_5$k, toDisplayString(unref(t2)("auth.login.passwordLabel")), 1),
              createBaseVNode("span", {
                class: normalizeClass(["cursor-pointer text-base font-medium text-muted select-none", {
                  "text-link-disabled": !$form.email?.value || $form.email?.invalid
                }]),
                onClick: /* @__PURE__ */ __name(($event) => handleForgotPassword($form.email?.value, $form.email?.valid), "onClick")
              }, toDisplayString(unref(t2)("auth.login.forgotPassword")), 11, _hoisted_6$d)
            ]),
            createVNode(unref(script$s), {
              "input-id": "comfy-org-sign-in-password",
              "pt:pc-input-text:root:autocomplete": "current-password",
              name: "password",
              feedback: false,
              "toggle-mask": "",
              placeholder: unref(t2)("auth.login.passwordPlaceholder"),
              class: normalizeClass([{ "p-invalid": $form.password?.invalid }, "h-10"]),
              fluid: ""
            }, null, 8, ["placeholder", "class"]),
            $form.password?.invalid ? (openBlock(), createElementBlock("small", _hoisted_7$b, toDisplayString($form.password.error.message), 1)) : createCommentVNode("", true)
          ]),
          loading.value ? (openBlock(), createBlock(unref(script$7), {
            key: 0,
            class: "mx-auto h-8 w-8"
          })) : (openBlock(), createBlock(_sfc_main$2A, {
            key: 1,
            type: "submit",
            class: "mt-4 h-10 font-medium",
            disabled: !$form.valid
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(unref(t2)("auth.login.loginButton")), 1)
            ]),
            _: 2
          }, 1032, ["disabled"]))
        ]),
        _: 1
      }, 8, ["resolver", "onSubmit"]);
    };
  }
});
const SignInForm = /* @__PURE__ */ _export_sfc(_sfc_main$11, [["__scopeId", "data-v-7fcd01b3"]]);
const _hoisted_1$S = { class: "mb-2 flex items-center justify-between" };
const _hoisted_2$E = {
  class: "text-base font-medium opacity-80",
  for: "comfy-org-sign-up-password"
};
const _hoisted_3$r = { class: "flex flex-col gap-1" };
const _hoisted_4$l = {
  key: 0,
  class: "text-sm"
};
const _hoisted_5$j = { class: "mt-1 space-y-1" };
const _hoisted_6$c = {
  class: "mb-2 text-base font-medium opacity-80",
  for: "comfy-org-sign-up-confirm-password"
};
const _hoisted_7$a = {
  key: 0,
  class: "text-red-500"
};
const _sfc_main$10 = /* @__PURE__ */ defineComponent({
  __name: "PasswordFields",
  setup(__props) {
    const { t: t2 } = useI18n();
    const password = ref("");
    const passwordChecks = computed(() => ({
      length: password.value.length >= 8 && password.value.length <= 32,
      uppercase: /[A-Z]/.test(password.value),
      lowercase: /[a-z]/.test(password.value),
      number: /\d/.test(password.value),
      special: /[^A-Za-z0-9]/.test(password.value)
    }));
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock(Fragment, null, [
        createVNode(unref(script$t), {
          name: "password",
          class: "flex flex-col gap-2"
        }, {
          default: withCtx(($field) => [
            createBaseVNode("div", _hoisted_1$S, [
              createBaseVNode("label", _hoisted_2$E, toDisplayString(unref(t2)("auth.signup.passwordLabel")), 1)
            ]),
            createVNode(unref(script$s), {
              modelValue: password.value,
              "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => password.value = $event),
              "input-id": "comfy-org-sign-up-password",
              "pt:pc-input-text:root:autocomplete": "new-password",
              name: "password",
              feedback: false,
              "toggle-mask": "",
              placeholder: unref(t2)("auth.signup.passwordPlaceholder"),
              class: normalizeClass([{ "p-invalid": $field.invalid }, "h-10"]),
              fluid: ""
            }, null, 8, ["modelValue", "placeholder", "class"]),
            createBaseVNode("div", _hoisted_3$r, [
              $field.dirty || $field.invalid ? (openBlock(), createElementBlock("small", _hoisted_4$l, [
                createTextVNode(toDisplayString(unref(t2)("validation.password.requirements")) + ": ", 1),
                createBaseVNode("ul", _hoisted_5$j, [
                  createBaseVNode("li", {
                    class: normalizeClass({
                      "text-red-500": !passwordChecks.value.length
                    })
                  }, toDisplayString(unref(t2)("validation.password.minLength")), 3),
                  createBaseVNode("li", {
                    class: normalizeClass({
                      "text-red-500": !passwordChecks.value.uppercase
                    })
                  }, toDisplayString(unref(t2)("validation.password.uppercase")), 3),
                  createBaseVNode("li", {
                    class: normalizeClass({
                      "text-red-500": !passwordChecks.value.lowercase
                    })
                  }, toDisplayString(unref(t2)("validation.password.lowercase")), 3),
                  createBaseVNode("li", {
                    class: normalizeClass({
                      "text-red-500": !passwordChecks.value.number
                    })
                  }, toDisplayString(unref(t2)("validation.password.number")), 3),
                  createBaseVNode("li", {
                    class: normalizeClass({
                      "text-red-500": !passwordChecks.value.special
                    })
                  }, toDisplayString(unref(t2)("validation.password.special")), 3)
                ])
              ])) : createCommentVNode("", true)
            ])
          ]),
          _: 1
        }),
        createVNode(unref(script$t), {
          name: "confirmPassword",
          class: "flex flex-col gap-2"
        }, {
          default: withCtx(($field) => [
            createBaseVNode("label", _hoisted_6$c, toDisplayString(unref(t2)("auth.login.confirmPasswordLabel")), 1),
            createVNode(unref(script$s), {
              name: "confirmPassword",
              "input-id": "comfy-org-sign-up-confirm-password",
              "pt:pc-input-text:root:autocomplete": "new-password",
              feedback: false,
              "toggle-mask": "",
              placeholder: unref(t2)("auth.login.confirmPasswordPlaceholder"),
              class: normalizeClass([{ "p-invalid": $field.invalid }, "h-10"]),
              fluid: ""
            }, null, 8, ["placeholder", "class"]),
            $field.error ? (openBlock(), createElementBlock("small", _hoisted_7$a, toDisplayString($field.error.message), 1)) : createCommentVNode("", true)
          ]),
          _: 1
        })
      ], 64);
    };
  }
});
const _hoisted_1$R = {
  class: "mb-2 text-base font-medium opacity-80",
  for: "comfy-org-sign-up-email"
};
const _hoisted_2$D = {
  key: 0,
  class: "text-red-500"
};
const _sfc_main$$ = /* @__PURE__ */ defineComponent({
  __name: "SignUpForm",
  emits: ["submit"],
  setup(__props, { emit: __emit }) {
    const { t: t2 } = useI18n();
    const authStore = useFirebaseAuthStore();
    const loading = computed(() => authStore.loading);
    const emit = __emit;
    const onSubmit = useThrottleFn((event) => {
      if (event.valid) {
        emit("submit", event.values);
      }
    }, 1500);
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$r), {
        class: "flex flex-col gap-6",
        resolver: unref(zodResolver)(unref(signUpSchema)),
        onSubmit: unref(onSubmit)
      }, {
        default: withCtx(($form) => [
          createVNode(unref(script$t), {
            name: "email",
            class: "flex flex-col gap-2"
          }, {
            default: withCtx(($field) => [
              createBaseVNode("label", _hoisted_1$R, toDisplayString(unref(t2)("auth.signup.emailLabel")), 1),
              createVNode(unref(script$5), {
                "pt:root:id": "comfy-org-sign-up-email",
                "pt:root:autocomplete": "email",
                class: "h-10",
                type: "text",
                placeholder: unref(t2)("auth.signup.emailPlaceholder"),
                invalid: $field.invalid
              }, null, 8, ["placeholder", "invalid"]),
              $field.error ? (openBlock(), createElementBlock("small", _hoisted_2$D, toDisplayString($field.error.message), 1)) : createCommentVNode("", true)
            ]),
            _: 1
          }),
          createVNode(_sfc_main$10),
          loading.value ? (openBlock(), createBlock(unref(script$7), {
            key: 0,
            class: "mx-auto h-8 w-8"
          })) : (openBlock(), createBlock(_sfc_main$2A, {
            key: 1,
            type: "submit",
            class: "mt-4 h-10 font-medium",
            disabled: !$form.valid
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(unref(t2)("auth.signup.signUpButton")), 1)
            ]),
            _: 2
          }, 1032, ["disabled"]))
        ]),
        _: 1
      }, 8, ["resolver", "onSubmit"]);
    };
  }
});
const _hoisted_1$Q = { class: "w-96 overflow-x-hidden p-2" };
const _hoisted_2$C = { class: "mb-8 flex flex-col gap-4" };
const _hoisted_3$q = { class: "my-0 text-2xl leading-normal font-medium" };
const _hoisted_4$k = { class: "my-0 text-base" };
const _hoisted_5$i = { class: "text-muted" };
const _hoisted_6$b = { class: "text-muted" };
const _hoisted_7$9 = { class: "flex flex-col gap-6" };
const _hoisted_8$8 = ["alt"];
const _hoisted_9$7 = { class: "text-center text-muted" };
const _hoisted_10$6 = ["href"];
const _hoisted_11$4 = { class: "mt-8 text-xs text-muted" };
const _hoisted_12$2 = {
  href: "https://www.comfy.org/terms-of-service",
  target: "_blank",
  class: "cursor-pointer text-blue-500"
};
const _hoisted_13$2 = {
  href: "https://www.comfy.org/privacy",
  target: "_blank",
  class: "cursor-pointer text-blue-500"
};
const _sfc_main$_ = /* @__PURE__ */ defineComponent({
  __name: "SignInContent",
  props: {
    onSuccess: { type: Function }
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const authActions = useFirebaseAuthActions();
    const isSecureContext = window.isSecureContext;
    const isSignIn = ref(true);
    const showApiKeyForm = ref(false);
    const ssoAllowed = isHostWhitelisted(normalizeHost(window.location.hostname));
    const comfyPlatformBaseUrl = computed(
      () => configValueOrDefault(
        remoteConfig.value,
        "comfy_platform_base_url",
        getComfyPlatformBaseUrl()
      )
    );
    const toggleState = /* @__PURE__ */ __name(() => {
      isSignIn.value = !isSignIn.value;
      showApiKeyForm.value = false;
    }, "toggleState");
    const signInWithGoogle = /* @__PURE__ */ __name(async () => {
      if (await authActions.signInWithGoogle()) {
        __props.onSuccess();
      }
    }, "signInWithGoogle");
    const signInWithGithub = /* @__PURE__ */ __name(async () => {
      if (await authActions.signInWithGithub()) {
        __props.onSuccess();
      }
    }, "signInWithGithub");
    const signInWithEmail = /* @__PURE__ */ __name(async (values) => {
      if (await authActions.signInWithEmail(values.email, values.password)) {
        __props.onSuccess();
      }
    }, "signInWithEmail");
    const signUpWithEmail = /* @__PURE__ */ __name(async (values) => {
      if (await authActions.signUpWithEmail(values.email, values.password)) {
        __props.onSuccess();
      }
    }, "signUpWithEmail");
    const userIsInChina = ref(false);
    onMounted(async () => {
      userIsInChina.value = await isInChina();
    });
    onUnmounted(() => {
      authActions.accessError.value = false;
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$Q, [
        showApiKeyForm.value ? (openBlock(), createBlock(_sfc_main$12, {
          key: 0,
          onBack: _cache[0] || (_cache[0] = ($event) => showApiKeyForm.value = false),
          onSuccess: _ctx.onSuccess
        }, null, 8, ["onSuccess"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
          createBaseVNode("div", _hoisted_2$C, [
            createBaseVNode("h1", _hoisted_3$q, toDisplayString(isSignIn.value ? unref(t2)("auth.login.title") : unref(t2)("auth.signup.title")), 1),
            createBaseVNode("p", _hoisted_4$k, [
              createBaseVNode("span", _hoisted_5$i, toDisplayString(isSignIn.value ? unref(t2)("auth.login.newUser") : unref(t2)("auth.signup.alreadyHaveAccount")), 1),
              createBaseVNode("span", {
                class: "ml-1 cursor-pointer text-blue-500",
                onClick: toggleState
              }, toDisplayString(isSignIn.value ? unref(t2)("auth.login.signUp") : unref(t2)("auth.signup.signIn")), 1)
            ])
          ]),
          !unref(isSecureContext) ? (openBlock(), createBlock(unref(script), {
            key: 0,
            severity: "warn",
            class: "mb-4"
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(unref(t2)("auth.login.insecureContextWarning")), 1)
            ]),
            _: 1
          })) : createCommentVNode("", true),
          isSignIn.value ? (openBlock(), createBlock(SignInForm, {
            key: 1,
            onSubmit: signInWithEmail
          })) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
            userIsInChina.value ? (openBlock(), createBlock(unref(script), {
              key: 0,
              severity: "warn",
              class: "mb-4"
            }, {
              default: withCtx(() => [
                createTextVNode(toDisplayString(unref(t2)("auth.signup.regionRestrictionChina")), 1)
              ]),
              _: 1
            })) : (openBlock(), createBlock(_sfc_main$$, {
              key: 1,
              onSubmit: signUpWithEmail
            }))
          ], 64)),
          createVNode(unref(script$h), {
            align: "center",
            layout: "horizontal",
            class: "my-8"
          }, {
            default: withCtx(() => [
              createBaseVNode("span", _hoisted_6$b, toDisplayString(unref(t2)("auth.login.orContinueWith")), 1)
            ]),
            _: 1
          }),
          createBaseVNode("div", _hoisted_7$9, [
            unref(ssoAllowed) ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
              createVNode(_sfc_main$2A, {
                type: "button",
                class: "h-10",
                variant: "secondary",
                onClick: signInWithGoogle
              }, {
                default: withCtx(() => [
                  _cache[2] || (_cache[2] = createBaseVNode("i", { class: "pi pi-google mr-2" }, null, -1)),
                  createTextVNode(" " + toDisplayString(isSignIn.value ? unref(t2)("auth.login.loginWithGoogle") : unref(t2)("auth.signup.signUpWithGoogle")), 1)
                ]),
                _: 1
              }),
              createVNode(_sfc_main$2A, {
                type: "button",
                class: "h-10",
                variant: "secondary",
                onClick: signInWithGithub
              }, {
                default: withCtx(() => [
                  _cache[3] || (_cache[3] = createBaseVNode("i", { class: "pi pi-github mr-2" }, null, -1)),
                  createTextVNode(" " + toDisplayString(isSignIn.value ? unref(t2)("auth.login.loginWithGithub") : unref(t2)("auth.signup.signUpWithGithub")), 1)
                ]),
                _: 1
              })
            ], 64)) : createCommentVNode("", true),
            createVNode(_sfc_main$2A, {
              type: "button",
              class: "h-10",
              variant: "secondary",
              onClick: _cache[1] || (_cache[1] = ($event) => showApiKeyForm.value = true)
            }, {
              default: withCtx(() => [
                createBaseVNode("img", {
                  src: _imports_0$1,
                  class: "mr-2 h-5 w-5",
                  alt: _ctx.$t("g.comfy")
                }, null, 8, _hoisted_8$8),
                createTextVNode(" " + toDisplayString(unref(t2)("auth.login.useApiKey")), 1)
              ]),
              _: 1
            }),
            createBaseVNode("small", _hoisted_9$7, [
              createTextVNode(toDisplayString(unref(t2)("auth.apiKey.helpText")) + " ", 1),
              createBaseVNode("a", {
                href: `${comfyPlatformBaseUrl.value}/login`,
                target: "_blank",
                class: "cursor-pointer text-blue-500"
              }, toDisplayString(unref(t2)("auth.apiKey.generateKey")), 9, _hoisted_10$6)
            ]),
            unref(authActions).accessError.value ? (openBlock(), createBlock(unref(script), {
              key: 1,
              severity: "info",
              icon: "pi pi-info-circle",
              variant: "outlined",
              closable: ""
            }, {
              default: withCtx(() => [
                createTextVNode(toDisplayString(unref(t2)("toastMessages.useApiKeyTip")), 1)
              ]),
              _: 1
            })) : createCommentVNode("", true)
          ]),
          createBaseVNode("p", _hoisted_11$4, [
            createTextVNode(toDisplayString(unref(t2)("auth.login.termsText")) + " ", 1),
            createBaseVNode("a", _hoisted_12$2, toDisplayString(unref(t2)("auth.login.termsLink")), 1),
            createTextVNode(" " + toDisplayString(unref(t2)("auth.login.andText")) + " ", 1),
            createBaseVNode("a", _hoisted_13$2, toDisplayString(unref(t2)("auth.login.privacyLink")), 1),
            createTextVNode(". " + toDisplayString(unref(t2)("auth.login.questionsContactPrefix")) + " ", 1),
            _cache[4] || (_cache[4] = createBaseVNode("a", {
              href: "mailto:hello@comfy.org",
              class: "cursor-pointer text-blue-500"
            }, " hello@comfy.org", -1)),
            _cache[5] || (_cache[5] = createTextVNode(". "))
          ])
        ], 64))
      ]);
    };
  }
});
const DEFAULT_NUMBER_FORMAT = {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
};
const formatNumber = /* @__PURE__ */ __name(({
  value,
  locale,
  options
}) => {
  const merged = {
    ...DEFAULT_NUMBER_FORMAT,
    ...options
  };
  if (typeof merged.maximumFractionDigits === "number" && typeof merged.minimumFractionDigits === "number" && merged.maximumFractionDigits < merged.minimumFractionDigits) {
    merged.minimumFractionDigits = merged.maximumFractionDigits;
  }
  return new Intl.NumberFormat(locale, merged).format(value);
}, "formatNumber");
const CREDITS_PER_USD = 211;
const COMFY_CREDIT_RATE_CENTS = CREDITS_PER_USD / 100;
const centsToCredits = /* @__PURE__ */ __name((cents) => Math.round(cents * COMFY_CREDIT_RATE_CENTS), "centsToCredits");
const usdToCredits = /* @__PURE__ */ __name((usd) => Math.round(usd * CREDITS_PER_USD), "usdToCredits");
const creditsToUsd = /* @__PURE__ */ __name((credits2) => Math.round(credits2 / CREDITS_PER_USD * 100) / 100, "creditsToUsd");
const formatCredits = /* @__PURE__ */ __name(({
  value,
  locale,
  numberOptions
}) => formatNumber({ value, locale, options: numberOptions }), "formatCredits");
const formatCreditsFromCents = /* @__PURE__ */ __name(({
  cents,
  locale,
  numberOptions
}) => formatCredits({
  value: centsToCredits(cents),
  locale,
  numberOptions
}), "formatCreditsFromCents");
const formatCreditsFromUsd = /* @__PURE__ */ __name(({
  usd,
  locale,
  numberOptions
}) => formatCredits({
  value: usdToCredits(usd),
  locale,
  numberOptions
}), "formatCreditsFromUsd");
const _hoisted_1$P = {
  key: 0,
  class: "flex items-center gap-1"
};
const _hoisted_2$B = { class: "flex items-center gap-2" };
const _hoisted_3$p = {
  key: 1,
  class: "flex items-center gap-1"
};
const _sfc_main$Z = /* @__PURE__ */ defineComponent({
  __name: "UserCredit",
  props: {
    textClass: {},
    showCreditsOnly: { type: Boolean }
  },
  setup(__props) {
    const authStore = useFirebaseAuthStore();
    const balanceLoading = computed(() => authStore.isFetchingBalance);
    const { t: t2, locale } = useI18n();
    const formattedBalance = computed(() => {
      const cents = authStore.balance?.effective_balance_micros ?? authStore.balance?.amount_micros ?? 0;
      const amount = formatCreditsFromCents({
        cents,
        locale: locale.value
      });
      return `${amount} ${t2("credits.credits")}`;
    });
    const formattedCreditsOnly = computed(() => {
      const cents = authStore.balance?.effective_balance_micros ?? authStore.balance?.amount_micros ?? 0;
      const amount = formatCreditsFromCents({
        cents,
        locale: locale.value,
        numberOptions: { minimumFractionDigits: 0, maximumFractionDigits: 0 }
      });
      return amount;
    });
    return (_ctx, _cache) => {
      return balanceLoading.value ? (openBlock(), createElementBlock("div", _hoisted_1$P, [
        createBaseVNode("div", _hoisted_2$B, [
          createVNode(unref(script$1), {
            shape: "circle",
            width: "1.5rem",
            height: "1.5rem"
          })
        ]),
        _cache[0] || (_cache[0] = createBaseVNode("div", { class: "flex-1" }, null, -1)),
        createVNode(unref(script$1), {
          width: "8rem",
          height: "2rem"
        })
      ])) : (openBlock(), createElementBlock("div", _hoisted_3$p, [
        !_ctx.showCreditsOnly ? (openBlock(), createBlock(unref(script$l), {
          key: 0,
          severity: "secondary",
          rounded: "",
          class: "p-1 text-amber-400"
        }, {
          icon: withCtx(() => _cache[1] || (_cache[1] = [
            createBaseVNode("i", { class: "icon-[lucide--component]" }, null, -1)
          ])),
          _: 1
        })) : createCommentVNode("", true),
        createBaseVNode("div", {
          class: normalizeClass(_ctx.textClass)
        }, toDisplayString(_ctx.showCreditsOnly ? formattedCreditsOnly.value : formattedBalance.value), 3)
      ]));
    };
  }
});
const _hoisted_1$O = { class: "text-base font-bold text-base-foreground" };
const _hoisted_2$A = { class: "text-sm font-normal text-muted-foreground" };
const _sfc_main$Y = /* @__PURE__ */ defineComponent({
  __name: "CreditTopUpOption",
  props: {
    credits: {},
    description: {},
    selected: { type: Boolean }
  },
  emits: ["select"],
  setup(__props) {
    const { locale } = useI18n();
    const formattedCredits = computed(() => {
      return formatCredits({
        value: __props.credits,
        locale: locale.value,
        numberOptions: { minimumFractionDigits: 0, maximumFractionDigits: 0 }
      });
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(["flex items-center justify-between p-2 rounded-lg cursor-pointer transition-all duration-200", [
          _ctx.selected ? "bg-secondary-background border-2 border-border-default" : "bg-component-node-disabled hover:bg-secondary-background border-2 border-transparent"
        ]]),
        onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("select"))
      }, [
        createBaseVNode("span", _hoisted_1$O, toDisplayString(formattedCredits.value), 1),
        createBaseVNode("span", _hoisted_2$A, toDisplayString(_ctx.description), 1)
      ], 2);
    };
  }
});
const _hoisted_1$N = { class: "flex w-112 flex-col gap-8 p-8" };
const _hoisted_2$z = { class: "flex flex-col gap-4" };
const _hoisted_3$o = { class: "text-2xl font-semibold text-base-foreground m-0" };
const _hoisted_4$j = {
  key: 0,
  class: "flex flex-col gap-2"
};
const _hoisted_5$h = { class: "text-sm text-muted-foreground m-0 w-96" };
const _hoisted_6$a = {
  key: 1,
  class: "flex flex-col gap-2"
};
const _hoisted_7$8 = { class: "text-sm text-muted-foreground m-0" };
const _hoisted_8$7 = { class: "flex flex-col gap-4" };
const _hoisted_9$6 = { class: "flex items-baseline gap-2" };
const _hoisted_10$5 = { class: "text-sm text-muted-foreground" };
const _hoisted_11$3 = {
  key: 0,
  class: "text-sm text-muted-foreground"
};
const _hoisted_12$1 = { class: "flex flex-col gap-4" };
const _hoisted_13$1 = { class: "text-sm text-muted-foreground" };
const _hoisted_14$1 = { class: "flex flex-col gap-2" };
const _hoisted_15$1 = { class: "text-xs text-muted-foreground w-96" };
const _sfc_main$X = /* @__PURE__ */ defineComponent({
  __name: "TopUpCreditsDialogContent",
  props: {
    isInsufficientCredits: { type: Boolean, default: false }
  },
  setup(__props) {
    const { formattedRenewalDate } = useSubscription();
    const { t: t2 } = useI18n();
    const authActions = useFirebaseAuthActions();
    const telemetry = useTelemetry();
    const toast = useToast();
    const selectedCredits = ref(null);
    const loading = ref(false);
    const creditOptions = [
      {
        credits: 1055,
        // $5.00
        description: t2("credits.topUp.videosEstimate", { count: 41 })
      },
      {
        credits: 2110,
        // $10.00
        description: t2("credits.topUp.videosEstimate", { count: 82 })
      },
      {
        credits: 4220,
        // $20.00
        description: t2("credits.topUp.videosEstimate", { count: 184 })
      },
      {
        credits: 10550,
        // $50.00
        description: t2("credits.topUp.videosEstimate", { count: 412 })
      }
    ];
    const handleBuy = /* @__PURE__ */ __name(async () => {
      if (!selectedCredits.value) return;
      loading.value = true;
      try {
        const usdAmount = creditsToUsd(selectedCredits.value);
        telemetry?.trackApiCreditTopupButtonPurchaseClicked(usdAmount);
        await authActions.purchaseCredits(usdAmount);
      } catch (error) {
        console.error("Purchase failed:", error);
        const errorMessage = error instanceof Error ? error.message : t2("credits.topUp.unknownError");
        toast.add({
          severity: "error",
          summary: t2("credits.topUp.purchaseError"),
          detail: t2("credits.topUp.purchaseErrorDetail", { error: errorMessage }),
          life: 5e3
        });
      } finally {
        loading.value = false;
      }
    }, "handleBuy");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$N, [
        createBaseVNode("div", _hoisted_2$z, [
          createBaseVNode("h1", _hoisted_3$o, toDisplayString(_ctx.isInsufficientCredits ? _ctx.$t("credits.topUp.addMoreCreditsToRun") : _ctx.$t("credits.topUp.addMoreCredits")), 1),
          _ctx.isInsufficientCredits ? (openBlock(), createElementBlock("div", _hoisted_4$j, [
            createBaseVNode("p", _hoisted_5$h, toDisplayString(_ctx.$t("credits.topUp.insufficientWorkflowMessage")), 1)
          ])) : (openBlock(), createElementBlock("div", _hoisted_6$a, [
            createBaseVNode("p", _hoisted_7$8, toDisplayString(_ctx.$t("credits.topUp.creditsDescription")), 1)
          ]))
        ]),
        createBaseVNode("div", _hoisted_8$7, [
          createBaseVNode("div", _hoisted_9$6, [
            createVNode(_sfc_main$Z, {
              "text-class": "text-3xl font-bold",
              "show-credits-only": ""
            }),
            createBaseVNode("span", _hoisted_10$5, toDisplayString(_ctx.$t("credits.creditsAvailable")), 1)
          ]),
          unref(formattedRenewalDate) ? (openBlock(), createElementBlock("div", _hoisted_11$3, toDisplayString(_ctx.$t("credits.refreshes", { date: unref(formattedRenewalDate) })), 1)) : createCommentVNode("", true)
        ]),
        createBaseVNode("div", _hoisted_12$1, [
          createBaseVNode("span", _hoisted_13$1, toDisplayString(_ctx.$t("credits.topUp.howManyCredits")), 1),
          createBaseVNode("div", _hoisted_14$1, [
            (openBlock(), createElementBlock(Fragment, null, renderList(creditOptions, (option) => {
              return createVNode(_sfc_main$Y, {
                key: option.credits,
                credits: option.credits,
                description: option.description,
                selected: selectedCredits.value === option.credits,
                onSelect: /* @__PURE__ */ __name(($event) => selectedCredits.value = option.credits, "onSelect")
              }, null, 8, ["credits", "description", "selected", "onSelect"]);
            }), 64))
          ]),
          createBaseVNode("div", _hoisted_15$1, toDisplayString(_ctx.$t("credits.topUp.templateNote")), 1)
        ]),
        createVNode(_sfc_main$2A, {
          disabled: !selectedCredits.value || loading.value,
          loading: loading.value,
          variant: "primary",
          class: normalizeClass(unref(cn)("w-full", (!selectedCredits.value || loading.value) && "opacity-30")),
          onClick: handleBuy
        }, {
          default: withCtx(() => [
            createTextVNode(toDisplayString(_ctx.$t("credits.topUp.buy")), 1)
          ]),
          _: 1
        }, 8, ["disabled", "loading", "class"])
      ]);
    };
  }
});
const _sfc_main$W = /* @__PURE__ */ defineComponent({
  __name: "UpdatePasswordContent",
  props: {
    onSuccess: { type: Function }
  },
  setup(__props) {
    const authActions = useFirebaseAuthActions();
    const loading = ref(false);
    const onSubmit = /* @__PURE__ */ __name(async (event) => {
      if (event.valid) {
        loading.value = true;
        try {
          await authActions.updatePassword(event.values.password);
          __props.onSuccess();
        } finally {
          loading.value = false;
        }
      }
    }, "onSubmit");
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$r), {
        class: "flex w-96 flex-col gap-6",
        resolver: unref(zodResolver)(unref(updatePasswordSchema)),
        onSubmit
      }, {
        default: withCtx(() => [
          createVNode(_sfc_main$10),
          createVNode(_sfc_main$2A, {
            type: "submit",
            class: "mt-4 h-10 font-medium",
            loading: loading.value
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(_ctx.$t("userSettings.updatePassword")), 1)
            ]),
            _: 1
          }, 8, ["loading"])
        ]),
        _: 1
      }, 8, ["resolver"]);
    };
  }
});
const _imports_0 = "" + new URL("images/comfy-logo-single.svg", import.meta.url).href;
const _sfc_main$V = {};
const _hoisted_1$M = { class: "px-2 py-4" };
const _hoisted_2$y = ["alt"];
function _sfc_render$2(_ctx, _cache) {
  return openBlock(), createElementBlock("div", _hoisted_1$M, [
    createBaseVNode("img", {
      src: _imports_0,
      alt: _ctx.$t("g.comfyOrgLogoAlt"),
      width: "32",
      height: "32"
    }, null, 8, _hoisted_2$y)
  ]);
}
__name(_sfc_render$2, "_sfc_render$2");
const ComfyOrgHeader = /* @__PURE__ */ _export_sfc(_sfc_main$V, [["render", _sfc_render$2]]);
const BUILD_TIME_IS_STAGING = true;
const isStaging = computed(() => {
  {
    return BUILD_TIME_IS_STAGING;
  }
});
const _hoisted_1$L = { class: "px-4" };
const _sfc_main$U = /* @__PURE__ */ defineComponent({
  __name: "SettingDialogHeader",
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", null, [
        createBaseVNode("h2", _hoisted_1$L, [
          _cache[0] || (_cache[0] = createBaseVNode("i", { class: "pi pi-cog" }, null, -1)),
          createBaseVNode("span", null, toDisplayString(_ctx.$t("g.settings")), 1),
          unref(isStaging) ? (openBlock(), createBlock(unref(script$l), {
            key: 0,
            value: "staging",
            severity: "warn",
            class: "ml-2 text-xs"
          })) : createCommentVNode("", true)
        ])
      ]);
    };
  }
});
const SettingDialogHeader = /* @__PURE__ */ _export_sfc(_sfc_main$U, [["__scopeId", "data-v-dcb7c822"]]);
const useUserStore = defineStore("user", () => {
  const userConfig = ref(null);
  const currentUserId = ref(null);
  const isMultiUserServer = computed(
    () => userConfig.value && "users" in userConfig.value
  );
  const needsLogin = computed(
    () => !currentUserId.value && isMultiUserServer.value
  );
  const users = computed(
    () => Object.entries(userConfig.value?.users ?? {}).map(([userId, username]) => ({
      userId,
      username
    }))
  );
  const currentUser = computed(
    () => users.value.find((user) => user.userId === currentUserId.value) ?? null
  );
  const initialized = computed(() => userConfig.value !== null);
  async function initialize() {
    userConfig.value = await api.getUserConfig();
    currentUserId.value = localStorage["Comfy.userId"];
  }
  __name(initialize, "initialize");
  async function createUser(username) {
    const resp = await api.createUser(username);
    const data = await resp.json();
    if (resp.status >= 300) {
      throw new Error(
        data.error ?? "Error creating user: " + resp.status + " " + resp.statusText
      );
    }
    return {
      userId: data,
      username
    };
  }
  __name(createUser, "createUser");
  async function login({
    userId,
    username
  }) {
    currentUserId.value = userId;
    localStorage["Comfy.userId"] = userId;
    localStorage["Comfy.userName"] = username;
  }
  __name(login, "login");
  watchEffect(() => {
    if (isMultiUserServer.value && currentUserId.value) {
      api.user = currentUserId.value;
    }
  });
  async function logout() {
    delete localStorage["Comfy.userId"];
    delete localStorage["Comfy.userName"];
  }
  __name(logout, "logout");
  return {
    users,
    currentUser,
    isMultiUserServer,
    needsLogin,
    initialized,
    initialize,
    createUser,
    login,
    logout
  };
});
const _hoisted_1$K = { class: "flex items-center justify-between" };
const _sfc_main$T = /* @__PURE__ */ defineComponent({
  __name: "CurrentUserMessage",
  setup(__props) {
    const userStore = useUserStore();
    const logout = /* @__PURE__ */ __name(async () => {
      await userStore.logout();
      window.location.reload();
    }, "logout");
    return (_ctx, _cache) => {
      return unref(userStore).isMultiUserServer ? (openBlock(), createBlock(unref(script), {
        key: 0,
        severity: "info",
        icon: "pi pi-user",
        "pt:text": "w-full"
      }, {
        default: withCtx(() => [
          createBaseVNode("div", _hoisted_1$K, [
            createBaseVNode("div", null, toDisplayString(_ctx.$t("g.currentUser")) + ": " + toDisplayString(unref(userStore).currentUser?.username), 1),
            createVNode(_sfc_main$2A, {
              class: "text-inherit",
              variant: "textonly",
              size: "icon",
              "aria-label": _ctx.$t("menuLabels.Sign Out"),
              onClick: logout
            }, {
              default: withCtx(() => _cache[0] || (_cache[0] = [
                createBaseVNode("i", { class: "pi pi-sign-out" }, null, -1)
              ])),
              _: 1
            }, 8, ["aria-label"])
          ])
        ]),
        _: 1
      })) : createCommentVNode("", true);
    };
  }
});
const _hoisted_1$J = { class: "flex h-full w-full flex-col gap-2" };
const _sfc_main$S = /* @__PURE__ */ defineComponent({
  __name: "PanelTemplate",
  props: {
    value: {},
    class: {}
  },
  setup(__props) {
    const props = __props;
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$u), {
        value: props.value,
        class: normalizeClass(["h-full w-full", props.class])
      }, {
        default: withCtx(() => [
          createBaseVNode("div", _hoisted_1$J, [
            renderSlot(_ctx.$slots, "header"),
            createVNode(unref(script$d), { class: "h-0 grow pr-2" }, {
              default: withCtx(() => [
                renderSlot(_ctx.$slots, "default")
              ]),
              _: 3
            }),
            renderSlot(_ctx.$slots, "footer")
          ])
        ]),
        _: 3
      }, 8, ["value", "class"]);
    };
  }
});
const _hoisted_1$I = { class: "flex items-center justify-between" };
const _hoisted_2$x = { class: "actions" };
const _sfc_main$R = /* @__PURE__ */ defineComponent({
  __name: "ColorPaletteMessage",
  setup(__props) {
    const settingStore = useSettingStore();
    const colorPaletteStore = useColorPaletteStore();
    const colorPaletteService = useColorPaletteService();
    const { palettes, activePaletteId } = storeToRefs(colorPaletteStore);
    const importCustomPalette = /* @__PURE__ */ __name(async () => {
      const palette = await colorPaletteService.importColorPalette();
      if (palette) {
        await settingStore.set("Comfy.ColorPalette", palette.id);
      }
    }, "importCustomPalette");
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script), {
        severity: "info",
        icon: "pi pi-palette",
        "pt:text": "w-full"
      }, {
        default: withCtx(() => [
          createBaseVNode("div", _hoisted_1$I, [
            createBaseVNode("div", null, toDisplayString(_ctx.$t("settingsCategories.ColorPalette")), 1),
            createBaseVNode("div", _hoisted_2$x, [
              createVNode(unref(script$9), {
                modelValue: unref(activePaletteId),
                "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => isRef(activePaletteId) ? activePaletteId.value = $event : null),
                class: "w-44",
                options: unref(palettes),
                "option-label": "name",
                "option-value": "id"
              }, null, 8, ["modelValue", "options"]),
              createVNode(_sfc_main$2A, {
                size: "icon",
                variant: "textonly",
                title: _ctx.$t("g.export"),
                onClick: _cache[1] || (_cache[1] = ($event) => unref(colorPaletteService).exportColorPalette(unref(activePaletteId)))
              }, {
                default: withCtx(() => _cache[3] || (_cache[3] = [
                  createBaseVNode("i", { class: "pi pi-file-export" }, null, -1)
                ])),
                _: 1
              }, 8, ["title"]),
              createVNode(_sfc_main$2A, {
                size: "icon",
                variant: "textonly",
                title: _ctx.$t("g.import"),
                onClick: importCustomPalette
              }, {
                default: withCtx(() => _cache[4] || (_cache[4] = [
                  createBaseVNode("i", { class: "pi pi-file-import" }, null, -1)
                ])),
                _: 1
              }, 8, ["title"]),
              createVNode(_sfc_main$2A, {
                size: "icon",
                variant: "destructive-textonly",
                title: _ctx.$t("g.delete"),
                disabled: !unref(colorPaletteStore).isCustomPalette(unref(activePaletteId)),
                onClick: _cache[2] || (_cache[2] = ($event) => unref(colorPaletteService).deleteCustomColorPalette(unref(activePaletteId)))
              }, {
                default: withCtx(() => _cache[5] || (_cache[5] = [
                  createBaseVNode("i", { class: "pi pi-trash" }, null, -1)
                ])),
                _: 1
              }, 8, ["title", "disabled"])
            ])
          ])
        ]),
        _: 1
      });
    };
  }
});
const _hoisted_1$H = {
  viewBox: "0 0 24 24",
  width: "1.2em",
  height: "1.2em"
};
function render(_ctx, _cache) {
  return openBlock(), createElementBlock("svg", _hoisted_1$H, [..._cache[0] || (_cache[0] = [
    createBaseVNode("path", {
      fill: "currentColor",
      d: "M5 21q-1.275 0-1.812-1.137t.262-2.113L9 11V5H8q-.425 0-.712-.288T7 4t.288-.712T8 3h8q.425 0 .713.288T17 4t-.288.713T16 5h-1v6l5.55 6.75q.8.975.263 2.113T19 21zm2-3h10l-3.4-4h-3.2zm-2 1h14l-6-7.3V5h-2v6.7zm7-7"
    }, null, -1)
  ])]);
}
__name(render, "render");
const __unplugin_components_0 = markRaw({ name: "material-symbols-experiment-outline", render });
const _hoisted_1$G = { class: "flex gap-2" };
const _sfc_main$Q = /* @__PURE__ */ defineComponent({
  __name: "BackgroundImageUpload",
  props: {
    "modelValue": {},
    "modelModifiers": {}
  },
  emits: ["update:modelValue"],
  setup(__props) {
    const modelValue = useModel(__props, "modelValue");
    const fileInput = ref(null);
    const isUploading = ref(false);
    const triggerFileInput = /* @__PURE__ */ __name(() => {
      fileInput.value?.click();
    }, "triggerFileInput");
    const uploadFile2 = /* @__PURE__ */ __name(async (file) => {
      const body = new FormData();
      body.append("image", file);
      body.append("subfolder", "backgrounds");
      const resp = await api.fetchApi("/upload/image", {
        method: "POST",
        body
      });
      if (resp.status !== 200) {
        useToastStore().addAlert(
          `Upload failed: ${resp.status} - ${resp.statusText}`
        );
        return null;
      }
      const data = await resp.json();
      return data.subfolder ? `${data.subfolder}/${data.name}` : data.name;
    }, "uploadFile");
    const handleFileUpload = /* @__PURE__ */ __name(async (event) => {
      const target = event.target;
      if (target.files && target.files[0]) {
        const file = target.files[0];
        isUploading.value = true;
        try {
          const uploadedPath = await uploadFile2(file);
          if (uploadedPath) {
            const params = new URLSearchParams({
              filename: uploadedPath,
              type: "input",
              subfolder: "backgrounds"
            });
            modelValue.value = `/api/view?${params.toString()}`;
          }
        } catch (error) {
          useToastStore().addAlert(`Upload error: ${String(error)}`);
        } finally {
          isUploading.value = false;
        }
      }
    }, "handleFileUpload");
    const clearImage = /* @__PURE__ */ __name(() => {
      modelValue.value = "";
      if (fileInput.value) {
        fileInput.value.value = "";
      }
    }, "clearImage");
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", _hoisted_1$G, [
        createVNode(unref(script$5), {
          modelValue: modelValue.value,
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => modelValue.value = $event),
          class: "flex-1",
          placeholder: _ctx.$t("g.imageUrl")
        }, null, 8, ["modelValue", "placeholder"]),
        withDirectives((openBlock(), createBlock(_sfc_main$2A, {
          variant: "secondary",
          size: "sm",
          "aria-label": _ctx.$t("g.upload"),
          disabled: isUploading.value,
          onClick: triggerFileInput
        }, {
          default: withCtx(() => [
            createBaseVNode("i", {
              class: normalizeClass(isUploading.value ? "pi pi-spin pi-spinner" : "pi pi-upload")
            }, null, 2)
          ]),
          _: 1
        }, 8, ["aria-label", "disabled"])), [
          [_directive_tooltip, _ctx.$t("g.upload")]
        ]),
        withDirectives((openBlock(), createBlock(_sfc_main$2A, {
          variant: "destructive",
          size: "sm",
          "aria-label": _ctx.$t("g.clear"),
          disabled: !modelValue.value,
          onClick: clearImage
        }, {
          default: withCtx(() => _cache[1] || (_cache[1] = [
            createBaseVNode("i", { class: "pi pi-trash" }, null, -1)
          ])),
          _: 1
        }, 8, ["aria-label", "disabled"])), [
          [_directive_tooltip, _ctx.$t("g.clear")]
        ]),
        createBaseVNode("input", {
          ref_key: "fileInput",
          ref: fileInput,
          type: "file",
          class: "hidden",
          accept: "image/*",
          onChange: handleFileUpload
        }, null, 544)
      ]);
    };
  }
});
const _sfc_main$P = /* @__PURE__ */ defineComponent({
  __name: "CustomFormValue",
  props: {
    renderFunction: { type: Function }
  },
  setup(__props) {
    const props = __props;
    const container = ref(null);
    function renderContent() {
      if (container.value) {
        container.value.innerHTML = "";
        const element = props.renderFunction();
        container.value.appendChild(element);
      }
    }
    __name(renderContent, "renderContent");
    onMounted(renderContent);
    watch(() => props.renderFunction, renderContent);
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        ref_key: "container",
        ref: container
      }, null, 512);
    };
  }
});
const _hoisted_1$F = { class: "color-picker-wrapper flex items-center gap-2" };
const _sfc_main$O = /* @__PURE__ */ defineComponent({
  ...{
    inheritAttrs: false
  },
  __name: "FormColorPicker",
  props: /* @__PURE__ */ mergeModels({
    label: {}
  }, {
    "modelValue": {},
    "modelModifiers": {}
  }),
  emits: ["update:modelValue"],
  setup(__props) {
    const modelValue = useModel(__props, "modelValue");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$F, [
        createVNode(unref(script$m), mergeProps({
          modelValue: modelValue.value,
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => modelValue.value = $event)
        }, _ctx.$attrs), null, 16, ["modelValue"]),
        createVNode(unref(script$5), {
          modelValue: modelValue.value,
          "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => modelValue.value = $event),
          class: "w-28",
          placeholder: _ctx.label
        }, null, 8, ["modelValue", "placeholder"])
      ]);
    };
  }
});
const _hoisted_1$E = { class: "image-upload-wrapper" };
const _hoisted_2$w = { class: "flex items-center gap-2" };
const _hoisted_3$n = ["src"];
const _hoisted_4$i = {
  key: 1,
  class: "pi pi-image text-xl text-smoke-400"
};
const _hoisted_5$g = { class: "flex flex-col gap-2" };
const _sfc_main$N = /* @__PURE__ */ defineComponent({
  __name: "FormImageUpload",
  props: {
    modelValue: {}
  },
  emits: ["update:modelValue"],
  setup(__props, { emit: __emit }) {
    const emit = __emit;
    const fileInput = ref(null);
    const triggerFileInput = /* @__PURE__ */ __name(() => {
      fileInput.value?.click();
    }, "triggerFileInput");
    const handleFileUpload = /* @__PURE__ */ __name((event) => {
      const target = event.target;
      if (target.files && target.files[0]) {
        const file = target.files[0];
        const reader = new FileReader();
        reader.onload = (e2) => {
          emit("update:modelValue", e2.target?.result);
        };
        reader.readAsDataURL(file);
      }
    }, "handleFileUpload");
    const clearImage = /* @__PURE__ */ __name(() => {
      emit("update:modelValue", "");
      if (fileInput.value) {
        fileInput.value.value = "";
      }
    }, "clearImage");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$E, [
        createBaseVNode("div", _hoisted_2$w, [
          createBaseVNode("div", {
            class: normalizeClass(["preview-box flex h-16 w-16 items-center justify-center rounded border p-2", { "bg-base-background": !_ctx.modelValue }])
          }, [
            _ctx.modelValue ? (openBlock(), createElementBlock("img", {
              key: 0,
              src: _ctx.modelValue,
              class: "max-h-full max-w-full object-contain"
            }, null, 8, _hoisted_3$n)) : (openBlock(), createElementBlock("i", _hoisted_4$i))
          ], 2),
          createBaseVNode("div", _hoisted_5$g, [
            createVNode(_sfc_main$2A, {
              size: "sm",
              onClick: triggerFileInput
            }, {
              default: withCtx(() => [
                _cache[0] || (_cache[0] = createBaseVNode("i", { class: "pi pi-upload" }, null, -1)),
                createTextVNode(" " + toDisplayString(_ctx.$t("g.upload")), 1)
              ]),
              _: 1
            }),
            _ctx.modelValue ? (openBlock(), createBlock(_sfc_main$2A, {
              key: 0,
              class: "w-full",
              variant: "destructive",
              size: "sm",
              "aria-label": _ctx.$t("g.delete"),
              onClick: clearImage
            }, {
              default: withCtx(() => _cache[1] || (_cache[1] = [
                createBaseVNode("i", { class: "pi pi-trash" }, null, -1)
              ])),
              _: 1
            }, 8, ["aria-label"])) : createCommentVNode("", true)
          ])
        ]),
        createBaseVNode("input", {
          ref_key: "fileInput",
          ref: fileInput,
          type: "file",
          class: "hidden",
          accept: "image/*",
          onChange: handleFileUpload
        }, null, 544)
      ]);
    };
  }
});
const _hoisted_1$D = { class: "flex flex-row gap-4" };
const _hoisted_2$v = ["for"];
const _sfc_main$M = /* @__PURE__ */ defineComponent({
  __name: "FormRadioGroup",
  props: {
    modelValue: {},
    options: {},
    optionLabel: {},
    optionValue: {},
    id: {}
  },
  emits: ["update:modelValue"],
  setup(__props) {
    const props = __props;
    const normalizedOptions = computed(() => {
      if (!props.options) return [];
      return props.options.map((option) => {
        if (typeof option === "string") {
          return { text: option, value: option };
        }
        if ("text" in option) {
          return {
            text: option.text,
            value: option.value ?? option.text
          };
        }
        return {
          text: option[props.optionLabel || "text"] || "Unknown",
          value: option[props.optionValue || "value"]
        };
      });
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$D, [
        (openBlock(true), createElementBlock(Fragment, null, renderList(normalizedOptions.value, (option) => {
          return openBlock(), createElementBlock("div", {
            key: option.value,
            class: "flex items-center"
          }, [
            createVNode(unref(script$v), {
              "input-id": `${_ctx.id}-${option.value}`,
              name: _ctx.id,
              value: option.value,
              "model-value": _ctx.modelValue,
              "aria-describedby": `${option.text}-label`,
              "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.$emit("update:modelValue", $event))
            }, null, 8, ["input-id", "name", "value", "model-value", "aria-describedby"]),
            createBaseVNode("label", {
              for: `${_ctx.id}-${option.value}`,
              class: "ml-2 cursor-pointer"
            }, toDisplayString(option.text), 9, _hoisted_2$v)
          ]);
        }), 128))
      ]);
    };
  }
});
const _hoisted_1$C = { class: "input-knob flex flex-row items-center gap-2" };
const _sfc_main$L = /* @__PURE__ */ defineComponent({
  ...{
    inheritAttrs: false
  },
  __name: "InputKnob",
  props: {
    modelValue: {},
    inputClass: {},
    knobClass: {},
    min: {},
    max: {},
    step: {},
    resolution: {}
  },
  emits: ["update:modelValue"],
  setup(__props, { emit: __emit }) {
    const props = __props;
    const emit = __emit;
    const localValue = ref(props.modelValue);
    watch(
      () => props.modelValue,
      (newValue2) => {
        localValue.value = newValue2;
      }
    );
    const updateValue = /* @__PURE__ */ __name((newValue2) => {
      if (newValue2 === null) {
        newValue2 = Number(props.min) || 0;
      }
      const min = Number(props.min ?? Number.NEGATIVE_INFINITY);
      const max = Number(props.max ?? Number.POSITIVE_INFINITY);
      const step = Number(props.step) || 1;
      newValue2 = Math.max(min, Math.min(max, newValue2));
      newValue2 = Math.round(newValue2 / step) * step;
      localValue.value = newValue2;
      emit("update:modelValue", newValue2);
    }, "updateValue");
    const displayValue = /* @__PURE__ */ __name((value) => {
      updateValue(value);
      const stepString = (props.step ?? 1).toString();
      const resolution = stepString.includes(".") ? stepString.split(".")[1].length : 0;
      return value.toFixed(props.resolution ?? resolution);
    }, "displayValue");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$C, [
        createVNode(unref(script$w), mergeProps({
          "model-value": _ctx.modelValue,
          "value-template": displayValue,
          class: ["knob-part", _ctx.knobClass],
          min: _ctx.min,
          max: _ctx.max,
          step: _ctx.step
        }, _ctx.$attrs, { "onUpdate:modelValue": updateValue }), null, 16, ["model-value", "class", "min", "max", "step"]),
        createVNode(unref(script$x), {
          "model-value": _ctx.modelValue,
          class: normalizeClass(["input-part", _ctx.inputClass]),
          "max-fraction-digits": 3,
          min: _ctx.min,
          max: _ctx.max,
          step: _ctx.step,
          "allow-empty": false,
          "onUpdate:modelValue": updateValue
        }, null, 8, ["model-value", "class", "min", "max", "step"])
      ]);
    };
  }
});
const _hoisted_1$B = { class: "input-slider flex flex-row items-center gap-2" };
const _sfc_main$K = /* @__PURE__ */ defineComponent({
  ...{
    inheritAttrs: false
  },
  __name: "InputSlider",
  props: {
    modelValue: {},
    inputClass: {},
    sliderClass: {},
    min: {},
    max: {},
    step: {}
  },
  emits: ["update:modelValue"],
  setup(__props, { emit: __emit }) {
    const props = __props;
    const emit = __emit;
    const localValue = ref(props.modelValue);
    watch(
      () => props.modelValue,
      (newValue2) => {
        localValue.value = newValue2;
      }
    );
    const updateValue = /* @__PURE__ */ __name((newValue2) => {
      if (newValue2 === null) {
        newValue2 = Number(props.min) || 0;
      }
      const min = Number(props.min ?? Number.NEGATIVE_INFINITY);
      const max = Number(props.max ?? Number.POSITIVE_INFINITY);
      const step = Number(props.step) || 1;
      newValue2 = Math.max(min, Math.min(max, newValue2));
      newValue2 = Math.round(newValue2 / step) * step;
      localValue.value = newValue2;
      emit("update:modelValue", newValue2);
    }, "updateValue");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$B, [
        createVNode(unref(script$a), mergeProps({
          "model-value": _ctx.modelValue,
          class: ["slider-part", _ctx.sliderClass],
          min: _ctx.min,
          max: _ctx.max,
          step: _ctx.step
        }, _ctx.$attrs, {
          "onUpdate:modelValue": _cache[0] || (_cache[0] = (value) => updateValue(value))
        }), null, 16, ["model-value", "class", "min", "max", "step"]),
        createVNode(unref(script$x), {
          "model-value": _ctx.modelValue,
          class: normalizeClass(["input-part", _ctx.inputClass]),
          "max-fraction-digits": 3,
          min: _ctx.min,
          max: _ctx.max,
          step: _ctx.step,
          "allow-empty": false,
          "onUpdate:modelValue": updateValue
        }, null, 8, ["model-value", "class", "min", "max", "step"])
      ]);
    };
  }
});
var ValidationState = /* @__PURE__ */ ((ValidationState2) => {
  ValidationState2["IDLE"] = "IDLE";
  ValidationState2["LOADING"] = "LOADING";
  ValidationState2["VALID"] = "VALID";
  ValidationState2["INVALID"] = "INVALID";
  return ValidationState2;
})(ValidationState || {});
const _sfc_main$J = /* @__PURE__ */ defineComponent({
  ...{
    inheritAttrs: false
  },
  __name: "UrlInput",
  props: {
    modelValue: {},
    validateUrlFn: { type: Function }
  },
  emits: ["update:modelValue", "state-change"],
  setup(__props, { emit: __emit }) {
    const props = __props;
    const emit = __emit;
    const validationState = ref(ValidationState.IDLE);
    const cleanInput = /* @__PURE__ */ __name((value) => value ? value.replace(/\s+/g, "") : "", "cleanInput");
    const internalValue = ref(cleanInput(props.modelValue));
    watch(
      () => props.modelValue,
      async (newValue2) => {
        internalValue.value = cleanInput(newValue2);
        await validateUrl(newValue2);
      }
    );
    watch(validationState, (newState) => {
      emit("state-change", newState);
    });
    onMounted(async () => {
      await validateUrl(props.modelValue);
    });
    const handleInput = /* @__PURE__ */ __name((value) => {
      internalValue.value = cleanInput(value ?? "");
      validationState.value = ValidationState.IDLE;
    }, "handleInput");
    const handleBlur = /* @__PURE__ */ __name(async () => {
      const input = cleanInput(internalValue.value);
      let normalizedUrl = input;
      try {
        const url = new URL(input);
        normalizedUrl = url.toString();
      } catch {
      }
      emit("update:modelValue", normalizedUrl);
    }, "handleBlur");
    const defaultValidateUrl = /* @__PURE__ */ __name(async (url) => {
      if (!isValidUrl(url)) return false;
      try {
        return await checkUrlReachable(url);
      } catch {
        return false;
      }
    }, "defaultValidateUrl");
    const validateUrl = /* @__PURE__ */ __name(async (value) => {
      if (validationState.value === ValidationState.LOADING) return;
      const url = cleanInput(value);
      validationState.value = ValidationState.IDLE;
      if (!url) return;
      validationState.value = ValidationState.LOADING;
      try {
        const isValid = await (props.validateUrlFn ?? defaultValidateUrl)(url);
        validationState.value = isValid ? ValidationState.VALID : ValidationState.INVALID;
      } catch {
        validationState.value = ValidationState.INVALID;
      }
    }, "validateUrl");
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$y), { class: "w-full" }, {
        default: withCtx(() => [
          createVNode(unref(script$5), mergeProps(_ctx.$attrs, {
            "model-value": internalValue.value,
            class: "w-full",
            invalid: validationState.value === unref(ValidationState).INVALID,
            "onUpdate:modelValue": handleInput,
            onBlur: handleBlur
          }), null, 16, ["model-value", "invalid"]),
          createVNode(unref(script$6), {
            class: normalizeClass({
              "pi pi-spin pi-spinner text-neutral-400": validationState.value === unref(ValidationState).LOADING,
              "pi pi-check cursor-pointer text-green-500": validationState.value === unref(ValidationState).VALID,
              "pi pi-times cursor-pointer text-red-500": validationState.value === unref(ValidationState).INVALID
            }),
            onClick: _cache[0] || (_cache[0] = ($event) => validateUrl(props.modelValue))
          }, null, 8, ["class"])
        ]),
        _: 1
      });
    };
  }
});
const _hoisted_1$A = { class: "flex flex-row items-center gap-2" };
const _hoisted_2$u = { class: "form-label flex grow items-center" };
const _hoisted_3$m = ["id"];
const _hoisted_4$h = {
  key: 0,
  class: "pi pi-info-circle bg-transparent"
};
const _hoisted_5$f = { class: "form-input flex justify-end" };
const _sfc_main$I = /* @__PURE__ */ defineComponent({
  __name: "FormItem",
  props: /* @__PURE__ */ mergeModels({
    item: {},
    id: {},
    labelClass: {}
  }, {
    "formValue": {},
    "formValueModifiers": {}
  }),
  emits: ["update:formValue"],
  setup(__props) {
    const formValue = useModel(__props, "formValue");
    const props = __props;
    function getFormAttrs(item) {
      const attrs = { ...item.attrs || {} };
      const inputType = item.type;
      if (typeof inputType === "function") {
        attrs["renderFunction"] = () => inputType(
          props.item.name,
          (v2) => formValue.value = v2,
          formValue.value,
          item.attrs
        );
      }
      switch (item.type) {
        case "combo":
        case "radio":
          attrs["options"] = typeof item.options === "function" ? (
            // @ts-expect-error: Audit and deprecate usage of legacy options type:
            // (value) => [string | {text: string, value: string}]
            item.options(formValue.value)
          ) : item.options;
          if (typeof item.options?.[0] !== "string") {
            attrs["optionLabel"] = "text";
            attrs["optionValue"] = "value";
          }
          break;
      }
      return attrs;
    }
    __name(getFormAttrs, "getFormAttrs");
    function getFormComponent(item) {
      if (typeof item.type === "function") {
        return _sfc_main$P;
      }
      switch (item.type) {
        case "boolean":
          return script$z;
        case "number":
          return script$x;
        case "slider":
          return _sfc_main$K;
        case "knob":
          return _sfc_main$L;
        case "combo":
          return script$9;
        case "radio":
          return _sfc_main$M;
        case "image":
          return _sfc_main$N;
        case "color":
          return _sfc_main$O;
        case "url":
          return _sfc_main$J;
        case "backgroundImage":
          return _sfc_main$Q;
        default:
          return script$5;
      }
    }
    __name(getFormComponent, "getFormComponent");
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", _hoisted_1$A, [
        createBaseVNode("div", _hoisted_2$u, [
          createBaseVNode("span", {
            id: `${props.id}-label`,
            class: normalizeClass(["text-muted", props.labelClass])
          }, [
            renderSlot(_ctx.$slots, "name-prefix", {}, void 0, true),
            createTextVNode(" " + toDisplayString(props.item.name) + " ", 1),
            props.item.tooltip ? withDirectives((openBlock(), createElementBlock("i", _hoisted_4$h, null, 512)), [
              [_directive_tooltip, props.item.tooltip]
            ]) : createCommentVNode("", true),
            renderSlot(_ctx.$slots, "name-suffix", {}, void 0, true)
          ], 10, _hoisted_3$m)
        ]),
        createBaseVNode("div", _hoisted_5$f, [
          (openBlock(), createBlock(resolveDynamicComponent(markRaw(getFormComponent(props.item))), mergeProps({
            id: props.id,
            "model-value": formValue.value,
            "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => formValue.value = $event),
            "aria-labelledby": `${props.id}-label`
          }, getFormAttrs(props.item)), null, 16, ["id", "model-value", "aria-labelledby"]))
        ])
      ]);
    };
  }
});
const FormItem = /* @__PURE__ */ _export_sfc(_sfc_main$I, [["__scopeId", "data-v-cd19ee1f"]]);
const _sfc_main$H = /* @__PURE__ */ defineComponent({
  __name: "SettingItem",
  props: {
    setting: {}
  },
  setup(__props) {
    const props = __props;
    const { t: t2 } = useI18n();
    function translateOptions(options) {
      if (typeof options === "function") {
        return translateOptions(options(props.setting.value ?? ""));
      }
      return options.map((option) => {
        const optionLabel = typeof option === "string" ? option : option.text;
        const optionValue = typeof option === "string" ? option : option.value;
        return {
          text: t2(
            `settings.${normalizeI18nKey(props.setting.id)}.options.${normalizeI18nKey(optionLabel)}`,
            optionLabel
          ),
          value: optionValue
        };
      });
    }
    __name(translateOptions, "translateOptions");
    const formItem = computed(() => {
      const normalizedId = normalizeI18nKey(props.setting.id);
      return {
        ...props.setting,
        name: t2(`settings.${normalizedId}.name`, props.setting.name),
        tooltip: props.setting.tooltip ? st(`settings.${normalizedId}.tooltip`, props.setting.tooltip) : void 0,
        options: props.setting.options ? translateOptions(props.setting.options) : void 0
      };
    });
    const settingStore = useSettingStore();
    const settingValue = computed(() => settingStore.get(props.setting.id));
    const updateSettingValue = /* @__PURE__ */ __name(async (newValue2) => {
      const settingId = props.setting.id;
      settingValue.value;
      await settingStore.set(settingId, newValue2);
      settingStore.get(settingId);
    }, "updateSettingValue");
    return (_ctx, _cache) => {
      const _component_i_material_symbols58experiment_outline = __unplugin_components_0;
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createBlock(FormItem, {
        id: _ctx.setting.id,
        item: formItem.value,
        "form-value": settingValue.value,
        "onUpdate:formValue": updateSettingValue
      }, {
        "name-prefix": withCtx(() => [
          _ctx.setting.id === "Comfy.Locale" ? (openBlock(), createBlock(unref(script$l), {
            key: 0,
            class: "pi pi-language"
          })) : createCommentVNode("", true),
          _ctx.setting.experimental ? withDirectives((openBlock(), createBlock(unref(script$l), { key: 1 }, {
            icon: withCtx(() => [
              createVNode(_component_i_material_symbols58experiment_outline)
            ]),
            _: 1
          })), [
            [_directive_tooltip, {
              value: _ctx.$t("g.experimental"),
              showDelay: 600
            }]
          ]) : createCommentVNode("", true)
        ]),
        _: 1
      }, 8, ["id", "item", "form-value"]);
    };
  }
});
const _hoisted_1$z = { class: "setting-group" };
const _sfc_main$G = /* @__PURE__ */ defineComponent({
  __name: "SettingGroup",
  props: {
    group: {},
    divider: { type: Boolean }
  },
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$z, [
        _ctx.divider ? (openBlock(), createBlock(unref(script$h), { key: 0 })) : createCommentVNode("", true),
        createBaseVNode("h3", null, toDisplayString(_ctx.$t(`settingsCategories.${unref(normalizeI18nKey)(_ctx.group.label)}`, _ctx.group.label)), 1),
        (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.group.settings.filter((s) => !s.deprecated), (setting) => {
          return openBlock(), createElementBlock("div", {
            key: setting.id,
            class: "setting-item mb-4"
          }, [
            createVNode(_sfc_main$H, { setting }, null, 8, ["setting"])
          ]);
        }), 128))
      ]);
    };
  }
});
const _hoisted_1$y = { key: 0 };
const _sfc_main$F = /* @__PURE__ */ defineComponent({
  __name: "SettingsPanel",
  props: {
    settingGroups: {}
  },
  setup(__props) {
    const props = __props;
    return (_ctx, _cache) => {
      return props.settingGroups.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_1$y, [
        (openBlock(true), createElementBlock(Fragment, null, renderList(props.settingGroups, (group, i) => {
          return openBlock(), createBlock(_sfc_main$G, {
            key: group.label,
            divider: i !== 0,
            group
          }, null, 8, ["divider", "group"]);
        }), 128))
      ])) : (openBlock(), createBlock(NoResultsPlaceholder, {
        key: 1,
        icon: "pi pi-search",
        title: _ctx.$t("g.noResultsFound"),
        message: _ctx.$t("g.searchFailedMessage")
      }, null, 8, ["title", "message"]));
    };
  }
});
function useSettingSearch() {
  const settingStore = useSettingStore();
  const { shouldRenderVueNodes } = useVueFeatureFlags();
  const searchQuery = ref("");
  const filteredSettingIds = ref([]);
  const searchInProgress = ref(false);
  watch(searchQuery, () => searchInProgress.value = true);
  const searchResultsCategories = computed(() => {
    return new Set(
      filteredSettingIds.value.map(
        (id2) => getSettingInfo(settingStore.settingsById[id2]).category
      )
    );
  });
  const queryIsEmpty = computed(() => searchQuery.value.length === 0);
  const inSearch = computed(
    () => !queryIsEmpty.value && !searchInProgress.value
  );
  const handleSearch = /* @__PURE__ */ __name((query) => {
    if (!query) {
      filteredSettingIds.value = [];
      return;
    }
    const queryLower = query.toLocaleLowerCase();
    const allSettings = Object.values(settingStore.settingsById);
    const filteredSettings = allSettings.filter((setting) => {
      if (setting.type === "hidden" || setting.deprecated || shouldRenderVueNodes.value && setting.hideInVueNodes) {
        return false;
      }
      const idLower = setting.id.toLowerCase();
      const nameLower = setting.name.toLowerCase();
      const translatedName = st(
        `settings.${normalizeI18nKey(setting.id)}.name`,
        setting.name
      ).toLocaleLowerCase();
      const info = getSettingInfo(setting);
      const translatedCategory = st(
        `settingsCategories.${normalizeI18nKey(info.category)}`,
        info.category
      ).toLocaleLowerCase();
      const translatedSubCategory = st(
        `settingsCategories.${normalizeI18nKey(info.subCategory)}`,
        info.subCategory
      ).toLocaleLowerCase();
      return idLower.includes(queryLower) || nameLower.includes(queryLower) || translatedName.includes(queryLower) || translatedCategory.includes(queryLower) || translatedSubCategory.includes(queryLower);
    });
    filteredSettingIds.value = filteredSettings.map((x2) => x2.id);
    searchInProgress.value = false;
  }, "handleSearch");
  const getSearchResults = /* @__PURE__ */ __name((activeCategory) => {
    const groupedSettings = {};
    filteredSettingIds.value.forEach((id2) => {
      const setting = settingStore.settingsById[id2];
      const info = getSettingInfo(setting);
      const groupLabel = info.subCategory;
      if (activeCategory === null || activeCategory.label === info.category) {
        if (!groupedSettings[groupLabel]) {
          groupedSettings[groupLabel] = [];
        }
        groupedSettings[groupLabel].push(setting);
      }
    });
    return Object.entries(groupedSettings).map(([label, settings]) => ({
      label,
      settings
    }));
  }, "getSearchResults");
  return {
    searchQuery,
    filteredSettingIds,
    searchInProgress,
    searchResultsCategories,
    queryIsEmpty,
    inSearch,
    handleSearch,
    getSearchResults
  };
}
__name(useSettingSearch, "useSettingSearch");
function useSettingUI(defaultPanel) {
  const { t: t2 } = useI18n();
  const { isLoggedIn } = useCurrentUser();
  const settingStore = useSettingStore();
  const activeCategory = ref(null);
  const { shouldRenderVueNodes } = useVueFeatureFlags();
  const { isActiveSubscription } = useSubscription();
  const settingRoot = computed(() => {
    const root = buildTree(
      Object.values(settingStore.settingsById).filter(
        (setting) => setting.type !== "hidden" && !(shouldRenderVueNodes.value && setting.hideInVueNodes)
      ),
      (setting) => setting.category || setting.id.split(".")
    );
    const floatingSettings = (root.children ?? []).filter((node2) => node2.leaf);
    if (floatingSettings.length) {
      root.children = (root.children ?? []).filter((node2) => !node2.leaf);
      root.children.push({
        key: "Other",
        label: "Other",
        leaf: false,
        children: floatingSettings
      });
    }
    return root;
  });
  const settingCategories = computed(
    () => settingRoot.value.children ?? []
  );
  const aboutPanel = {
    node: {
      key: "about",
      label: "About",
      children: []
    },
    component: defineAsyncComponent(
      () => __vitePreload(() => import("./AboutPanel-EiS1z7jb.js"), true ? __vite__mapDeps([49,4,1,2,8,9,10,11,12]) : void 0, import.meta.url)
    )
  };
  const creditsPanel = {
    node: {
      key: "credits",
      label: "Credits",
      children: []
    },
    component: defineAsyncComponent(
      () => __vitePreload(() => import("./LegacyCreditsPanel-DdzYlNTV.js"), true ? __vite__mapDeps([50,4,1,2,8,9,10,11,12]) : void 0, import.meta.url)
    )
  };
  const subscriptionPanel = null;
  const shouldShowPlanCreditsPanel = computed(() => {
    return false;
  });
  const userPanel = {
    node: {
      key: "user",
      label: "User",
      children: []
    },
    component: defineAsyncComponent(
      () => __vitePreload(() => import("./UserPanel-D45LmBad.js"), true ? __vite__mapDeps([51,4,1,2,52,8,9,10,11,12]) : void 0, import.meta.url)
    )
  };
  const keybindingPanel = {
    node: {
      key: "keybinding",
      label: "Keybinding",
      children: []
    },
    component: defineAsyncComponent(
      () => __vitePreload(() => import("./KeybindingPanel-1oAuqHzd.js"), true ? __vite__mapDeps([53,1,2,4,8,54,9,10,11,12,55]) : void 0, import.meta.url)
    )
  };
  const extensionPanel = {
    node: {
      key: "extension",
      label: "Extension",
      children: []
    },
    component: defineAsyncComponent(
      () => __vitePreload(() => import("./ExtensionPanel-Ce96aMFv.js"), true ? __vite__mapDeps([56,4,1,2,8,9,10,11,12]) : void 0, import.meta.url)
    )
  };
  const serverConfigPanel = {
    node: {
      key: "server-config",
      label: "Server-Config",
      children: []
    },
    component: defineAsyncComponent(
      () => __vitePreload(() => import("./ServerConfigPanel-FGSzg5tP.js"), true ? __vite__mapDeps([57,8,1,2,4,58,9,10,11,12]) : void 0, import.meta.url)
    )
  };
  const panels = computed(
    () => [
      aboutPanel,
      creditsPanel,
      userPanel,
      keybindingPanel,
      extensionPanel,
      ...isElectron() ? [serverConfigPanel] : [],
      ...shouldShowPlanCreditsPanel.value && subscriptionPanel ? [subscriptionPanel] : []
    ].filter((panel) => panel.component)
  );
  const defaultCategory = computed(() => {
    if (!defaultPanel) return settingCategories.value[0];
    for (const group of groupedMenuTreeNodes.value) {
      const found = group.children?.find((node2) => node2.key === defaultPanel);
      if (found) return found;
    }
    return settingCategories.value[0];
  });
  const translateCategory = /* @__PURE__ */ __name((node2) => ({
    ...node2,
    translatedLabel: t2(
      `settingsCategories.${normalizeI18nKey(node2.label)}`,
      node2.label
    )
  }), "translateCategory");
  const groupedMenuTreeNodes = computed(() => [
    // Account settings - show different panels based on distribution and auth state
    {
      key: "account",
      label: "Account",
      children: [
        userPanel.node,
        ...isLoggedIn.value && shouldShowPlanCreditsPanel.value && subscriptionPanel ? [subscriptionPanel.node] : [],
        ...isLoggedIn.value && true ? [creditsPanel.node] : []
      ].map(translateCategory)
    },
    // Normal settings stored in the settingStore
    {
      key: "settings",
      label: "Application Settings",
      children: settingCategories.value.map(translateCategory)
    },
    // Special settings such as about, keybinding, extension, server-config
    {
      key: "specialSettings",
      label: "Special Settings",
      children: [
        keybindingPanel.node,
        extensionPanel.node,
        aboutPanel.node,
        ...isElectron() ? [serverConfigPanel.node] : []
      ].map(translateCategory)
    }
  ]);
  onMounted(() => {
    activeCategory.value = defaultCategory.value;
  });
  return {
    panels,
    activeCategory,
    defaultCategory,
    groupedMenuTreeNodes,
    settingCategories
  };
}
__name(useSettingUI, "useSettingUI");
const _hoisted_1$x = { class: "settings-container" };
const _sfc_main$E = /* @__PURE__ */ defineComponent({
  __name: "SettingDialogContent",
  props: {
    defaultPanel: {}
  },
  setup(__props) {
    const {
      activeCategory,
      defaultCategory,
      settingCategories,
      groupedMenuTreeNodes,
      panels
    } = useSettingUI(__props.defaultPanel);
    const {
      searchQuery,
      searchResultsCategories,
      queryIsEmpty,
      inSearch,
      handleSearch: handleSearchBase,
      getSearchResults
    } = useSettingSearch();
    const authActions = useFirebaseAuthActions();
    const getGroupSortOrder = /* @__PURE__ */ __name((group) => Math.max(0, ...flattenTree(group).map((s) => s.sortOrder ?? 0)), "getGroupSortOrder");
    const sortedGroups = /* @__PURE__ */ __name((category) => {
      return [...category.children ?? []].sort((a, b) => {
        const orderDiff = getGroupSortOrder(b) - getGroupSortOrder(a);
        return orderDiff !== 0 ? orderDiff : a.label.localeCompare(b.label);
      }).map((group) => ({
        label: group.label,
        settings: flattenTree(group).sort((a, b) => {
          const sortOrderA = a.sortOrder ?? 0;
          const sortOrderB = b.sortOrder ?? 0;
          return sortOrderB - sortOrderA;
        })
      }));
    }, "sortedGroups");
    const handleSearch = /* @__PURE__ */ __name((query) => {
      handleSearchBase(query.trim());
      activeCategory.value = query ? null : defaultCategory.value;
    }, "handleSearch");
    const searchResults = computed(
      () => getSearchResults(activeCategory.value)
    );
    const tabValue = computed(
      () => inSearch.value ? "Search Results" : activeCategory.value?.label ?? ""
    );
    watch(activeCategory, (_, oldValue) => {
      if (!tabValue.value) {
        activeCategory.value = oldValue;
      }
      if (activeCategory.value?.key === "credits") {
        void authActions.fetchBalance();
      }
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$x, [
        createVNode(unref(script$d), { class: "settings-sidebar w-48 shrink-0 p-2 2xl:w-64" }, {
          default: withCtx(() => [
            createVNode(SearchBox, {
              "model-value": unref(searchQuery),
              "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => isRef(searchQuery) ? searchQuery.value = $event : null),
              class: "settings-search-box mb-2 w-full",
              placeholder: _ctx.$t("g.searchSettings") + "...",
              "debounce-time": 128,
              autofocus: "",
              onSearch: handleSearch
            }, null, 8, ["model-value", "placeholder"]),
            createVNode(unref(script$p), {
              modelValue: unref(activeCategory),
              "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => isRef(activeCategory) ? activeCategory.value = $event : null),
              options: unref(groupedMenuTreeNodes),
              "option-label": "translatedLabel",
              "option-group-label": "label",
              "option-group-children": "children",
              "scroll-height": "100%",
              "option-disabled": /* @__PURE__ */ __name((option) => !unref(queryIsEmpty) && !unref(searchResultsCategories).has(option.label ?? ""), "option-disabled"),
              class: "w-full border-none"
            }, {
              optiongroup: withCtx(() => [
                createVNode(unref(script$h), { class: "my-0" })
              ]),
              _: 1
            }, 8, ["modelValue", "options", "option-disabled"])
          ]),
          _: 1
        }),
        createVNode(unref(script$h), {
          layout: "vertical",
          class: "mx-1 hidden md:flex 2xl:mx-4"
        }),
        createVNode(unref(script$h), {
          layout: "horizontal",
          class: "flex md:hidden"
        }),
        createVNode(unref(script$A), {
          value: tabValue.value,
          lazy: true,
          class: "settings-content h-full w-full"
        }, {
          default: withCtx(() => [
            createVNode(unref(script$B), { class: "settings-tab-panels h-full w-full pr-0" }, {
              default: withCtx(() => [
                createVNode(_sfc_main$S, { value: "Search Results" }, {
                  default: withCtx(() => [
                    createVNode(_sfc_main$F, { "setting-groups": searchResults.value }, null, 8, ["setting-groups"])
                  ]),
                  _: 1
                }),
                (openBlock(true), createElementBlock(Fragment, null, renderList(unref(settingCategories), (category) => {
                  return openBlock(), createBlock(_sfc_main$S, {
                    key: category.key,
                    value: category.label ?? ""
                  }, {
                    header: withCtx(() => [
                      tabValue.value === "Comfy" ? (openBlock(), createBlock(_sfc_main$T, { key: 0 })) : createCommentVNode("", true),
                      tabValue.value === "Appearance" ? (openBlock(), createBlock(_sfc_main$R, { key: 1 })) : createCommentVNode("", true)
                    ]),
                    default: withCtx(() => [
                      createVNode(_sfc_main$F, {
                        "setting-groups": sortedGroups(category)
                      }, null, 8, ["setting-groups"])
                    ]),
                    _: 2
                  }, 1032, ["value"]);
                }), 128)),
                (openBlock(true), createElementBlock(Fragment, null, renderList(unref(panels), (panel) => {
                  return openBlock(), createBlock(Suspense, {
                    key: panel.node.key
                  }, {
                    fallback: withCtx(() => [
                      createBaseVNode("div", null, toDisplayString(_ctx.$t("g.loadingPanel", { panel: panel.node.label })), 1)
                    ]),
                    default: withCtx(() => [
                      (openBlock(), createBlock(resolveDynamicComponent(panel.component)))
                    ]),
                    _: 2
                  }, 1024);
                }), 128))
              ]),
              _: 1
            })
          ]),
          _: 1
        }, 8, ["value"])
      ]);
    };
  }
});
const SettingDialogContent = /* @__PURE__ */ _export_sfc(_sfc_main$E, [["__scopeId", "data-v-54ba910a"]]);
const _hoisted_1$w = { class: "flex w-full items-center justify-between py-2" };
const _hoisted_2$t = { class: "flex flex-col text-sm leading-normal font-medium" };
const _hoisted_3$l = { class: "text-muted" };
const _hoisted_4$g = { class: "h-full" };
const _hoisted_5$e = { class: "break-words whitespace-pre-wrap" };
const _sfc_main$D = /* @__PURE__ */ defineComponent({
  __name: "ManagerProgressDialogContent",
  setup(__props) {
    const comfyManagerStore = useComfyManagerStore();
    const progressDialogContent = useManagerProgressDialogStore();
    const isInProgress = /* @__PURE__ */ __name((index2) => {
      const log2 = focusedLogs.value[index2];
      if (!log2) return false;
      const taskQueue = comfyManagerStore.taskQueue;
      if (!taskQueue) return false;
      const allQueueTasks = [
        ...taskQueue.running_queue || [],
        ...taskQueue.pending_queue || []
      ];
      return allQueueTasks.some((task) => task.ui_id === log2.taskId);
    }, "isInProgress");
    const focusedLogs = computed(() => {
      if (progressDialogContent.getActiveTabIndex() === 0) {
        return comfyManagerStore.succeededTasksLogs;
      }
      return comfyManagerStore.failedTasksLogs;
    });
    const isExpanded = computed(() => progressDialogContent.isExpanded);
    const isCollapsed = computed(() => !isExpanded.value);
    const collapsedPanels = ref({});
    const togglePanel = /* @__PURE__ */ __name((index2) => {
      collapsedPanels.value[index2] = !collapsedPanels.value[index2];
    }, "togglePanel");
    const sectionsContainerRef = ref(null);
    const { y: scrollY } = useScroll(sectionsContainerRef, {
      eventListenerOptions: {
        passive: true
      }
    });
    const lastPanelRef = ref(null);
    const isUserScrolling = ref(false);
    const lastPanelLogs = computed(() => focusedLogs.value?.at(-1)?.logs);
    const isAtBottom = /* @__PURE__ */ __name((el) => {
      if (!el) return false;
      const threshold = 20;
      return Math.abs(el.scrollHeight - el.scrollTop - el.clientHeight) < threshold;
    }, "isAtBottom");
    const scrollLastPanelToBottom = /* @__PURE__ */ __name(() => {
      if (!lastPanelRef.value || isUserScrolling.value) return;
      lastPanelRef.value.scrollTop = lastPanelRef.value.scrollHeight;
    }, "scrollLastPanelToBottom");
    const scrollContentToBottom = /* @__PURE__ */ __name(() => {
      scrollY.value = sectionsContainerRef.value?.scrollHeight ?? 0;
    }, "scrollContentToBottom");
    const resetUserScrolling = /* @__PURE__ */ __name(() => {
      isUserScrolling.value = false;
    }, "resetUserScrolling");
    const handleScroll = /* @__PURE__ */ __name((e2) => {
      const target = e2.target;
      if (target !== lastPanelRef.value) return;
      isUserScrolling.value = !isAtBottom(target);
    }, "handleScroll");
    const onLogsAdded = /* @__PURE__ */ __name(() => {
      if (isUserScrolling.value) return;
      scrollLastPanelToBottom();
    }, "onLogsAdded");
    whenever(lastPanelLogs, onLogsAdded, { flush: "post", deep: true });
    whenever(() => isExpanded.value, scrollContentToBottom);
    whenever(isCollapsed, resetUserScrolling);
    onMounted(() => {
      scrollContentToBottom();
    });
    onBeforeUnmount(() => {
      progressDialogContent.collapse();
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(["overflow-hidden transition-all duration-300", {
          "max-h-[500px]": isExpanded.value,
          "m-0 max-h-0 p-0": !isExpanded.value
        }])
      }, [
        createBaseVNode("div", {
          ref_key: "sectionsContainerRef",
          ref: sectionsContainerRef,
          class: normalizeClass(["scroll-container max-h-[450px] overflow-y-auto px-6 py-4", {
            "max-h-[450px]": isExpanded.value,
            "max-h-0": !isExpanded.value
          }]),
          style: {
            scrollbarWidth: "thin",
            scrollbarColor: "rgba(156, 163, 175, 0.5) transparent"
          }
        }, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(focusedLogs.value, (log2, index2) => {
            return openBlock(), createElementBlock("div", { key: index2 }, [
              createVNode(unref(script$C), {
                expanded: collapsedPanels.value[index2] === true,
                toggleable: "",
                class: "shadow-elevation-1 mt-2 rounded-lg"
              }, {
                header: withCtx(() => [
                  createBaseVNode("div", _hoisted_1$w, [
                    createBaseVNode("div", _hoisted_2$t, [
                      createBaseVNode("span", null, toDisplayString(log2.taskName), 1),
                      createBaseVNode("span", _hoisted_3$l, toDisplayString(isInProgress(index2) ? _ctx.$t("g.inProgress") : _ctx.$t("g.completed") + " ✓"), 1)
                    ])
                  ])
                ]),
                toggleicon: withCtx(() => [
                  createVNode(_sfc_main$2A, {
                    variant: "textonly",
                    class: "text-neutral-300",
                    onClick: /* @__PURE__ */ __name(($event) => togglePanel(index2), "onClick")
                  }, {
                    default: withCtx(() => [
                      createBaseVNode("i", {
                        class: normalizeClass(
                          collapsedPanels.value[index2] ? "pi pi-chevron-right" : "pi pi-chevron-down"
                        )
                      }, null, 2)
                    ]),
                    _: 2
                  }, 1032, ["onClick"])
                ]),
                default: withCtx(() => [
                  createBaseVNode("div", {
                    ref_for: true,
                    ref: index2 === focusedLogs.value.length - 1 ? (el) => lastPanelRef.value = el : void 0,
                    class: normalizeClass(["h-64 overflow-y-auto rounded-lg bg-black", {
                      "h-64": index2 !== focusedLogs.value.length - 1,
                      grow: index2 === focusedLogs.value.length - 1
                    }]),
                    onScroll: handleScroll
                  }, [
                    createBaseVNode("div", _hoisted_4$g, [
                      (openBlock(true), createElementBlock(Fragment, null, renderList(log2.logs, (logLine, logIndex) => {
                        return openBlock(), createElementBlock("div", {
                          key: logIndex,
                          class: "text-muted"
                        }, [
                          createBaseVNode("pre", _hoisted_5$e, toDisplayString(logLine), 1)
                        ]);
                      }), 128))
                    ])
                  ], 34)
                ]),
                _: 2
              }, 1032, ["expanded"])
            ]);
          }), 128))
        ], 2)
      ], 2);
    };
  }
});
const _hoisted_1$v = { class: "flex items-center text-base leading-none" };
const _hoisted_2$s = { class: "flex items-center" };
const _hoisted_3$k = { class: "flex items-center gap-4" };
const _hoisted_4$f = {
  key: 0,
  class: "text-sm text-neutral-700"
};
const _hoisted_5$d = { class: "flex items-center" };
const _sfc_main$C = /* @__PURE__ */ defineComponent({
  __name: "ManagerProgressFooter",
  setup(__props) {
    const { t: t2 } = useI18n();
    const dialogStore = useDialogStore();
    const progressDialogContent = useManagerProgressDialogStore();
    const comfyManagerStore = useComfyManagerStore();
    const settingStore = useSettingStore();
    const { runFullConflictAnalysis } = useConflictDetection();
    const isRestarting = ref(false);
    const isRestartCompleted = ref(false);
    const isInProgress = computed(
      () => comfyManagerStore.isProcessingTasks || isRestarting.value
    );
    const completedTasksCount = computed(() => {
      return comfyManagerStore.succeededTasksIds.length + comfyManagerStore.failedTasksIds.length;
    });
    const totalTasksCount = computed(() => {
      const completedTasks = Object.keys(comfyManagerStore.taskHistory).length;
      const taskQueue = comfyManagerStore.taskQueue;
      const queuedTasks = taskQueue ? (taskQueue.running_queue?.length || 0) + (taskQueue.pending_queue?.length || 0) : 0;
      return completedTasks + queuedTasks;
    });
    const closeDialog = /* @__PURE__ */ __name(() => {
      dialogStore.closeDialog({ key: "global-manager-progress-dialog" });
    }, "closeDialog");
    const fallbackTaskName = t2("manager.installingDependencies");
    const currentTaskName = computed(() => {
      if (isRestarting.value) {
        return t2("manager.restartingBackend");
      }
      if (isRestartCompleted.value) {
        return t2("manager.extensionsSuccessfullyInstalled");
      }
      if (!comfyManagerStore.taskLogs.length) return fallbackTaskName;
      const task = comfyManagerStore.taskLogs.at(-1);
      return task?.taskName ?? fallbackTaskName;
    });
    const handleRestart = /* @__PURE__ */ __name(async () => {
      const originalToastSetting = settingStore.get(
        "Comfy.Toast.DisableReconnectingToast"
      );
      try {
        await settingStore.set("Comfy.Toast.DisableReconnectingToast", true);
        isRestarting.value = true;
        const onReconnect = /* @__PURE__ */ __name(async () => {
          try {
            comfyManagerStore.setStale();
            await useCommandStore().execute("Comfy.RefreshNodeDefinitions");
            await useWorkflowService().reloadCurrentWorkflow();
            void runFullConflictAnalysis();
          } finally {
            await settingStore.set(
              "Comfy.Toast.DisableReconnectingToast",
              originalToastSetting
            );
            isRestarting.value = false;
            isRestartCompleted.value = true;
            setTimeout(() => {
              closeDialog();
              comfyManagerStore.resetTaskState();
            }, 3e3);
          }
        }, "onReconnect");
        useEventListener(api, "reconnected", onReconnect, { once: true });
        await useComfyManagerService().rebootComfyUI();
      } catch (error) {
        await settingStore.set(
          "Comfy.Toast.DisableReconnectingToast",
          originalToastSetting
        );
        isRestarting.value = false;
        isRestartCompleted.value = false;
        closeDialog();
        throw error;
      }
    }, "handleRestart");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass(["flex w-full items-center justify-between px-6 py-2 shadow-lg", {
          "rounded-t-none": unref(progressDialogContent).isExpanded,
          "rounded-lg": !unref(progressDialogContent).isExpanded
        }])
      }, [
        createBaseVNode("div", _hoisted_1$v, [
          createBaseVNode("div", _hoisted_2$s, [
            isInProgress.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
              createVNode(DotSpinner, {
                duration: "1s",
                class: "mr-2"
              }),
              createBaseVNode("span", null, toDisplayString(currentTaskName.value), 1)
            ], 64)) : isRestartCompleted.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
              _cache[0] || (_cache[0] = createBaseVNode("span", { class: "mr-2" }, "🎉", -1)),
              createBaseVNode("span", null, toDisplayString(currentTaskName.value), 1)
            ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [
              _cache[1] || (_cache[1] = createBaseVNode("span", { class: "mr-2" }, "✅", -1)),
              createBaseVNode("span", null, toDisplayString(_ctx.$t("manager.restartToApplyChanges")), 1)
            ], 64))
          ])
        ]),
        createBaseVNode("div", _hoisted_3$k, [
          isInProgress.value ? (openBlock(), createElementBlock("span", _hoisted_4$f, toDisplayString(completedTasksCount.value) + " " + toDisplayString(_ctx.$t("g.progressCountOf")) + " " + toDisplayString(totalTasksCount.value), 1)) : createCommentVNode("", true),
          createBaseVNode("div", _hoisted_5$d, [
            !isInProgress.value && !isRestartCompleted.value ? (openBlock(), createBlock(_sfc_main$2A, {
              key: 0,
              variant: "secondary",
              class: "mr-4 rounded-full border-2 border-base-foreground px-3 text-base-foreground hover:bg-secondary-background-hover",
              onClick: handleRestart
            }, {
              default: withCtx(() => [
                createTextVNode(toDisplayString(_ctx.$t("manager.applyChanges")), 1)
              ]),
              _: 1
            })) : !isRestartCompleted.value ? (openBlock(), createBlock(_sfc_main$2A, {
              key: 1,
              variant: "muted-textonly",
              size: "sm",
              class: "rounded-full font-bold",
              "aria-label": _ctx.$t(
                unref(progressDialogContent).isExpanded ? "contextMenu.Collapse" : "contextMenu.Expand"
              ),
              onClick: withModifiers(unref(progressDialogContent).toggle, ["stop"])
            }, {
              default: withCtx(() => [
                createBaseVNode("i", {
                  class: normalizeClass(
                    unref(progressDialogContent).isExpanded ? "pi pi-chevron-up" : "pi pi-chevron-down"
                  )
                }, null, 2)
              ]),
              _: 1
            }, 8, ["aria-label", "onClick"])) : createCommentVNode("", true),
            createVNode(_sfc_main$2A, {
              variant: "muted-textonly",
              size: "sm",
              class: "rounded-full font-bold",
              "aria-label": _ctx.$t("g.close"),
              onClick: withModifiers(closeDialog, ["stop"])
            }, {
              default: withCtx(() => _cache[2] || (_cache[2] = [
                createBaseVNode("i", { class: "pi pi-times" }, null, -1)
              ])),
              _: 1
            }, 8, ["aria-label"])
          ])
        ])
      ], 2);
    };
  }
});
const _hoisted_1$u = {
  key: 0,
  class: "flex items-center px-4 py-2"
};
const _sfc_main$B = /* @__PURE__ */ defineComponent({
  __name: "ManagerProgressHeader",
  setup(__props) {
    const progressDialogContent = useManagerProgressDialogStore();
    const comfyManagerStore = useComfyManagerStore();
    const activeTabIndex = computed({
      get: /* @__PURE__ */ __name(() => progressDialogContent.getActiveTabIndex(), "get"),
      set: /* @__PURE__ */ __name((value) => progressDialogContent.setActiveTabIndex(value), "set")
    });
    const { t: t2 } = useI18n();
    const tabs = computed(() => [
      { label: t2("manager.installationQueue") },
      {
        label: t2("manager.failed", {
          count: comfyManagerStore.failedTasksIds.length
        })
      }
    ]);
    return (_ctx, _cache) => {
      return unref(progressDialogContent).isExpanded ? (openBlock(), createElementBlock("div", _hoisted_1$u, [
        createVNode(unref(script$D), {
          "active-index": activeTabIndex.value,
          "onUpdate:activeIndex": _cache[0] || (_cache[0] = ($event) => activeTabIndex.value = $event),
          model: tabs.value,
          class: "w-full border-none",
          pt: {
            menu: { class: "border-none" },
            menuitem: { class: "font-medium" },
            action: { class: "px-4 py-2" }
          }
        }, null, 8, ["active-index", "model"])
      ])) : createCommentVNode("", true);
    };
  }
});
const _sfc_main$A = /* @__PURE__ */ defineComponent({
  __name: "ContentDivider",
  props: {
    orientation: { default: "horizontal" },
    width: { default: 0.3 }
  },
  setup(__props) {
    useCssVars((_ctx) => ({
      "4bb7c524": _ctx.width + "px"
    }));
    const colorPaletteStore = useColorPaletteStore();
    const isLightTheme = computed(
      () => colorPaletteStore.completedActivePalette.light_theme
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: normalizeClass({
          "content-divider": true,
          "content-divider--horizontal": _ctx.orientation === "horizontal",
          "content-divider--vertical": _ctx.orientation === "vertical"
        }),
        style: normalizeStyle({
          backgroundColor: isLightTheme.value ? "#DCDAE1" : "#2C2C2C"
        })
      }, null, 6);
    };
  }
});
const ContentDivider = /* @__PURE__ */ _export_sfc(_sfc_main$A, [["__scopeId", "data-v-1d39213b"]]);
const useResponsiveCollapse = /* @__PURE__ */ __name((breakpointThreshold = "lg") => {
  const breakpoints = useBreakpoints(breakpointsTailwind);
  const isSmallScreen = breakpoints.smallerOrEqual(breakpointThreshold);
  const isOpen = ref(!isSmallScreen.value);
  const onIsSmallScreenChange = /* @__PURE__ */ __name(() => {
    if (isSmallScreen.value && isOpen.value) {
      isOpen.value = false;
    } else if (!isSmallScreen.value && !isOpen.value) {
      isOpen.value = true;
    }
  }, "onIsSmallScreenChange");
  watch(isSmallScreen, onIsSmallScreenChange);
  return {
    breakpoints,
    isOpen,
    isSmallScreen,
    open: /* @__PURE__ */ __name(() => isOpen.value = true, "open"),
    close: /* @__PURE__ */ __name(() => isOpen.value = false, "close"),
    toggle: /* @__PURE__ */ __name(() => isOpen.value = !isOpen.value, "toggle")
  };
}, "useResponsiveCollapse");
const _hoisted_1$t = { class: "z-5 flex w-3/12 max-w-[250px] translate-x-0 transition-transform duration-300 ease-in-out" };
const _hoisted_2$r = { class: "flex items-center text-left" };
const _hoisted_3$j = { class: "text-sm" };
const _sfc_main$z = /* @__PURE__ */ defineComponent({
  __name: "ManagerNavSidebar",
  props: /* @__PURE__ */ mergeModels({
    tabs: {}
  }, {
    "selectedTab": {},
    "selectedTabModifiers": {}
  }),
  emits: ["update:selectedTab"],
  setup(__props) {
    const selectedTab = useModel(__props, "selectedTab");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("aside", _hoisted_1$t, [
        createVNode(unref(script$d), { class: "flex-1" }, {
          default: withCtx(() => [
            createVNode(unref(script$p), {
              modelValue: selectedTab.value,
              "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedTab.value = $event),
              options: _ctx.tabs,
              "option-label": "label",
              "list-style": "max-height:unset",
              class: "w-full border-0 bg-transparent shadow-none",
              pt: {
                list: { class: "p-3 gap-2" },
                option: { class: "px-4 py-2 text-lg rounded-lg" },
                optionGroup: { class: "p-0 text-left text-inherit" }
              }
            }, {
              option: withCtx((slotProps) => [
                createBaseVNode("div", _hoisted_2$r, [
                  createBaseVNode("i", {
                    class: normalizeClass(["pi", slotProps.option.icon, "mr-2 text-sm"])
                  }, null, 2),
                  createBaseVNode("span", _hoisted_3$j, toDisplayString(slotProps.option.label), 1)
                ])
              ]),
              _: 1
            }, 8, ["modelValue", "options"])
          ]),
          _: 1
        }),
        createVNode(ContentDivider, {
          orientation: "vertical",
          width: 0.3
        })
      ]);
    };
  }
});
const ImportFailedKey = /* @__PURE__ */ Symbol("ImportFailed");
const _sfc_main$y = /* @__PURE__ */ defineComponent({
  __name: "PackStatusMessage",
  props: {
    statusType: {},
    hasCompatibilityIssues: { type: Boolean }
  },
  setup(__props) {
    const importFailedContext = inject(ImportFailedKey);
    const importFailed = importFailedContext?.importFailed;
    const statusPropsMap = {
      NodeStatusActive: {
        label: "active",
        severity: "success"
      },
      NodeStatusDeleted: {
        label: "deleted",
        severity: "warn"
      },
      NodeStatusBanned: {
        label: "banned",
        severity: "error"
      },
      NodeVersionStatusActive: {
        label: "active",
        severity: "success"
      },
      NodeVersionStatusPending: {
        label: "pending",
        severity: "warn"
      },
      NodeVersionStatusDeleted: {
        label: "deleted",
        severity: "warn"
      },
      NodeVersionStatusFlagged: {
        label: "flagged",
        severity: "error"
      },
      NodeVersionStatusBanned: {
        label: "banned",
        severity: "error"
      }
    };
    const statusLabel = computed(() => {
      if (importFailed?.value) return "importFailed";
      if (__props.hasCompatibilityIssues) return "conflicting";
      return statusPropsMap[__props.statusType]?.label || "unknown";
    });
    const statusSeverity = computed(() => {
      if (__props.hasCompatibilityIssues || importFailed?.value) return "error";
      return statusPropsMap[__props.statusType]?.severity || "secondary";
    });
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script), {
        severity: statusSeverity.value,
        class: "flex w-fit items-center rounded-xl p-0 break-words",
        pt: {
          text: { class: "text-xs" },
          content: { class: "px-2 py-0.5" }
        }
      }, {
        default: withCtx(() => [
          _cache[0] || (_cache[0] = createBaseVNode("i", {
            class: "pi pi-circle-fill mr-1.5 p-0 text-[0.6rem]",
            style: { opacity: 0.8 }
          }, null, -1)),
          createTextVNode(" " + toDisplayString(_ctx.$t(`manager.status.${statusLabel.value}`)), 1)
        ]),
        _: 1
      }, 8, ["severity"]);
    };
  }
});
const _hoisted_1$s = ["width", "height"];
const _hoisted_2$q = ["fill"];
const _sfc_main$x = /* @__PURE__ */ defineComponent({
  __name: "VerifiedIcon",
  props: {
    size: { default: 16 },
    color: { default: "#60A5FA" },
    class: {}
  },
  setup(__props) {
    const iconClass = computed(() => __props.class || "");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("svg", {
        xmlns: "http://www.w3.org/2000/svg",
        width: _ctx.size,
        height: _ctx.size,
        viewBox: "0 0 16 16",
        fill: "none",
        class: normalizeClass(iconClass.value)
      }, [
        createBaseVNode("path", {
          d: "M8.00049 1.3335C8.73661 1.33367 9.33332 1.93038 9.3335 2.6665V2.83447C9.82278 2.96041 10.2851 3.15405 10.7095 3.40479L10.8286 3.28564C11.3493 2.76525 12.1937 2.76519 12.7144 3.28564C13.235 3.80626 13.2348 4.65067 12.7144 5.17139L12.5952 5.29053C12.846 5.71486 13.0396 6.17725 13.1655 6.6665H13.3335C14.0699 6.6665 14.6665 7.26411 14.6665 8.00049C14.6663 8.73672 14.0698 9.3335 13.3335 9.3335H13.1655C13.0396 9.82284 12.846 10.2851 12.5952 10.7095L12.7144 10.8286C13.235 11.3493 13.235 12.1937 12.7144 12.7144C12.1937 13.235 11.3493 13.235 10.8286 12.7144L10.7095 12.5952C10.2851 12.846 9.82284 13.0396 9.3335 13.1655V13.3335C9.3335 14.0698 8.73672 14.6663 8.00049 14.6665C7.26411 14.6665 6.6665 14.0699 6.6665 13.3335V13.1655C6.17725 13.0396 5.71486 12.846 5.29053 12.5952L5.17139 12.7144C4.65067 13.2348 3.80626 13.235 3.28564 12.7144C2.76519 12.1937 2.76525 11.3493 3.28564 10.8286L3.40479 10.7095C3.15405 10.2851 2.96041 9.82278 2.83447 9.3335H2.6665C1.93038 9.33332 1.33367 8.73661 1.3335 8.00049C1.3335 7.26422 1.93027 6.66668 2.6665 6.6665H2.83447C2.96043 6.17722 3.15403 5.71488 3.40479 5.29053L3.28564 5.17139C2.76536 4.65065 2.76508 3.80621 3.28564 3.28564C3.80621 2.76508 4.65065 2.76536 5.17139 3.28564L5.29053 3.40479C5.71488 3.15403 6.17722 2.96043 6.6665 2.83447V2.6665C6.66668 1.93027 7.26422 1.3335 8.00049 1.3335ZM7.3335 8.00049L6.00049 6.6665L4.6665 8.00049L7.3335 10.6665L11.3335 6.6665L10.0005 5.3335L7.3335 8.00049Z",
          fill: _ctx.color
        }, null, 8, _hoisted_2$q)
      ], 10, _hoisted_1$s);
    };
  }
});
function getConflictMessage(conflict, t2) {
  const messageKey = `manager.conflicts.conflictMessages.${conflict.type}`;
  if (conflict.type === "comfyui_version" || conflict.type === "frontend_version" || conflict.type === "os" || conflict.type === "accelerator") {
    return t2(messageKey, {
      current: conflict.current_value,
      required: conflict.required_value
    });
  }
  if (conflict.type === "banned" || conflict.type === "pending" || conflict.type === "import_failed") {
    return t2(messageKey);
  }
  return t2("manager.conflicts.conflictMessages.generic", {
    current: conflict.current_value,
    required: conflict.required_value
  });
}
__name(getConflictMessage, "getConflictMessage");
function getJoinedConflictMessages(conflicts, t2, separator = "; ") {
  return conflicts.map((conflict) => getConflictMessage(conflict, t2)).join(separator);
}
__name(getJoinedConflictMessages, "getJoinedConflictMessages");
const _hoisted_1$r = { class: "w-64 pt-1" };
const _hoisted_2$p = { class: "py-2" };
const _hoisted_3$i = { class: "text-md pl-3 font-semibold text-neutral-500" };
const _hoisted_4$e = {
  key: 0,
  class: "flex flex-col items-center py-4 text-center text-muted"
};
const _hoisted_5$c = {
  key: 1,
  class: "py-2"
};
const _hoisted_6$9 = { class: "flex w-full items-center justify-between p-1" };
const _hoisted_7$7 = { class: "flex items-center gap-2" };
const _hoisted_8$6 = {
  key: 0,
  class: "w-4"
};
const _hoisted_9$5 = {
  key: 0,
  class: "pi pi-exclamation-triangle text-yellow-500"
};
const _hoisted_10$4 = {
  key: 0,
  class: "pi pi-check text-highlight"
};
const _hoisted_11$2 = { class: "flex justify-end gap-2 px-3 py-1" };
const _sfc_main$w = /* @__PURE__ */ defineComponent({
  __name: "PackVersionSelectorPopover",
  props: {
    nodePack: {}
  },
  emits: ["cancel", "submit"],
  setup(__props, { emit: __emit }) {
    const SelectedVersionValues = {
      LATEST: "latest",
      NIGHTLY: "nightly"
    };
    const ManagerChannelValues = {
      DEFAULT: "default"
    };
    const ManagerDatabaseSourceValues = {
      CACHE: "cache"
    };
    const emit = __emit;
    const { t: t2 } = useI18n();
    const registryService = useComfyRegistryService();
    const managerStore = useComfyManagerStore();
    const { checkNodeCompatibility } = useConflictDetection();
    const isQueueing = ref(false);
    const selectedVersion = ref(SelectedVersionValues.LATEST);
    onMounted(() => {
      const initialVersion = getInitialSelectedVersion() ?? SelectedVersionValues.LATEST;
      selectedVersion.value = // Use NIGHTLY when version is a Git hash
      semverExports.valid(initialVersion) ? initialVersion : SelectedVersionValues.NIGHTLY;
    });
    const getInitialSelectedVersion = /* @__PURE__ */ __name(() => {
      if (!__props.nodePack.id) return;
      if (__props.nodePack.publisher?.name === "Unclaimed")
        return SelectedVersionValues.NIGHTLY;
      if (managerStore.isPackInstalled(__props.nodePack.id))
        return managerStore.getInstalledPackVersion(__props.nodePack.id);
      return __props.nodePack.latest_version?.version;
    }, "getInitialSelectedVersion");
    const fetchVersions = /* @__PURE__ */ __name(async () => {
      if (!__props.nodePack?.id) return [];
      return await registryService.getPackVersions(__props.nodePack.id) || [];
    }, "fetchVersions");
    const versionOptions = ref([]);
    const fetchedVersions = ref([]);
    const isLoadingVersions = ref(false);
    const onNodePackChange = /* @__PURE__ */ __name(async () => {
      isLoadingVersions.value = true;
      const versions = await fetchVersions();
      fetchedVersions.value = versions;
      const latestVersionNumber = __props.nodePack.latest_version?.version;
      const availableVersionOptions = versions.map((version) => ({
        value: version.version ?? "",
        label: version.version ?? ""
      })).filter((option) => option.value && option.value !== latestVersionNumber);
      const latestLabel = latestVersionNumber ? `${t2("manager.latestVersion")} (${latestVersionNumber})` : t2("manager.latestVersion");
      const defaultVersions = [
        {
          value: SelectedVersionValues.LATEST,
          label: latestLabel
        }
      ];
      if (__props.nodePack.repository?.length) {
        defaultVersions.push({
          value: SelectedVersionValues.NIGHTLY,
          label: t2("manager.nightlyVersion")
        });
      }
      versionOptions.value = [...defaultVersions, ...availableVersionOptions];
      isLoadingVersions.value = false;
    }, "onNodePackChange");
    whenever(
      () => __props.nodePack.id,
      (nodePackId, oldNodePackId) => {
        if (nodePackId !== oldNodePackId) {
          void onNodePackChange();
        }
      },
      { deep: true, immediate: true }
    );
    const handleSubmit = /* @__PURE__ */ __name(async () => {
      isQueueing.value = true;
      if (!__props.nodePack.id) {
        throw new Error("Node ID is required for installation");
      }
      const actualVersion = selectedVersion.value === "latest" ? __props.nodePack.latest_version?.version ?? "latest" : selectedVersion.value;
      await managerStore.installPack.call({
        id: __props.nodePack.id,
        repository: __props.nodePack.repository ?? "",
        channel: ManagerChannelValues.DEFAULT,
        mode: ManagerDatabaseSourceValues.CACHE,
        version: actualVersion,
        selected_version: selectedVersion.value
      });
      isQueueing.value = false;
      emit("submit");
    }, "handleSubmit");
    const getVersionData = /* @__PURE__ */ __name((version) => {
      const latestVersionNumber = __props.nodePack.latest_version?.version;
      const useLatestVersionData = version === "latest" || version === latestVersionNumber;
      if (useLatestVersionData) {
        const latestVersionData = __props.nodePack.latest_version;
        return {
          ...latestVersionData
        };
      }
      const versionData = fetchedVersions.value.find((v2) => v2.version === version);
      if (versionData) {
        return {
          ...versionData
        };
      }
      return {
        ...__props.nodePack
      };
    }, "getVersionData");
    const getVersionCompatibility = /* @__PURE__ */ __name((version) => {
      const versionData = getVersionData(version);
      const compatibility = checkNodeCompatibility(versionData);
      const conflictMessage = compatibility.hasConflict ? getJoinedConflictMessages(compatibility.conflicts, t2) : "";
      return {
        hasConflict: compatibility.hasConflict,
        conflictMessage
      };
    }, "getVersionCompatibility");
    const isOptionSelected = /* @__PURE__ */ __name((optionValue) => {
      if (selectedVersion.value === optionValue) {
        return true;
      }
      if (optionValue === "latest" && selectedVersion.value === __props.nodePack.latest_version?.version) {
        return true;
      }
      return false;
    }, "isOptionSelected");
    const processedVersionOptions = computed(() => {
      return versionOptions.value.map((option) => {
        const compatibility = getVersionCompatibility(option.value);
        const isSelected = isOptionSelected(option.value);
        return {
          ...option,
          hasConflict: compatibility.hasConflict,
          conflictMessage: compatibility.conflictMessage,
          isSelected
        };
      });
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", _hoisted_1$r, [
        createBaseVNode("div", _hoisted_2$p, [
          createBaseVNode("span", _hoisted_3$i, toDisplayString(_ctx.$t("manager.selectVersion")), 1)
        ]),
        isLoadingVersions.value || isQueueing.value ? (openBlock(), createElementBlock("div", _hoisted_4$e, [
          createVNode(unref(script$7), { class: "mb-2 h-8 w-8" }),
          createTextVNode(" " + toDisplayString(_ctx.$t("manager.loadingVersions")), 1)
        ])) : versionOptions.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_5$c, [
          createVNode(NoResultsPlaceholder, {
            title: _ctx.$t("g.noResultsFound"),
            message: _ctx.$t("manager.tryAgainLater"),
            icon: "pi pi-exclamation-circle",
            class: "p-0"
          }, null, 8, ["title", "message"])
        ])) : (openBlock(), createBlock(unref(script$p), {
          key: 2,
          modelValue: selectedVersion.value,
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => selectedVersion.value = $event),
          "option-label": "label",
          "option-value": "value",
          options: processedVersionOptions.value,
          "highlight-on-select": false,
          class: "max-h-[50vh] w-full rounded-md border-none shadow-none",
          pt: {
            listContainer: { class: "scrollbar-hide" }
          }
        }, {
          option: withCtx((slotProps) => [
            createBaseVNode("div", _hoisted_6$9, [
              createBaseVNode("div", _hoisted_7$7, [
                slotProps.option.value === "nightly" ? (openBlock(), createElementBlock("div", _hoisted_8$6)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
                  slotProps.option.hasConflict ? withDirectives((openBlock(), createElementBlock("i", _hoisted_9$5, null, 512)), [
                    [_directive_tooltip, {
                      value: slotProps.option.conflictMessage,
                      showDelay: 300
                    }]
                  ]) : (openBlock(), createBlock(_sfc_main$x, {
                    key: 1,
                    size: 20,
                    class: "relative right-0.5"
                  }))
                ], 64)),
                createBaseVNode("span", null, toDisplayString(slotProps.option.label), 1)
              ]),
              slotProps.option.isSelected ? (openBlock(), createElementBlock("i", _hoisted_10$4)) : createCommentVNode("", true)
            ])
          ]),
          _: 1
        }, 8, ["modelValue", "options"])),
        createVNode(ContentDivider, { class: "my-2" }),
        createBaseVNode("div", _hoisted_11$2, [
          createVNode(_sfc_main$2A, {
            variant: "muted-textonly",
            class: "text-sm",
            disabled: isQueueing.value,
            onClick: _cache[1] || (_cache[1] = ($event) => emit("cancel"))
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(_ctx.$t("g.cancel")), 1)
            ]),
            _: 1
          }, 8, ["disabled"]),
          createVNode(_sfc_main$2A, {
            variant: "secondary",
            class: "rounded-lg bg-secondary-background px-4 py-2.5 text-sm text-base-foreground",
            disabled: isQueueing.value,
            onClick: handleSubmit
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(_ctx.$t("g.install")), 1)
            ]),
            _: 1
          }, 8, ["disabled"])
        ])
      ]);
    };
  }
});
const usePackUpdateStatus = /* @__PURE__ */ __name((nodePackSource) => {
  const { isPackInstalled, isPackEnabled, getInstalledPackVersion } = useComfyManagerStore();
  const nodePack = computed(() => toValue$1(nodePackSource));
  const isInstalled = computed(() => isPackInstalled(nodePack.value?.id));
  const isEnabled = computed(() => isPackEnabled(nodePack.value?.id));
  const installedVersion = computed(
    () => getInstalledPackVersion(nodePack.value?.id ?? "")
  );
  const latestVersion = computed(() => nodePack.value?.latest_version?.version);
  const isNightlyPack = computed(
    () => !!installedVersion.value && !semverExports.valid(installedVersion.value)
  );
  const isUpdateAvailable = computed(() => {
    if (!isInstalled.value || isNightlyPack.value || !latestVersion.value || !installedVersion.value) {
      return false;
    }
    return semverExports.compare(latestVersion.value, installedVersion.value) > 0;
  });
  const canTryNightlyUpdate = computed(
    () => isInstalled.value && isEnabled.value && isNightlyPack.value
  );
  return {
    isUpdateAvailable,
    isNightlyPack,
    canTryNightlyUpdate,
    installedVersion,
    latestVersion
  };
}, "usePackUpdateStatus");
const _hoisted_1$q = ["aria-haspopup", "role", "tabindex"];
const _hoisted_2$o = {
  key: 0,
  class: "pi pi-arrow-circle-up text-xs text-blue-600"
};
const _hoisted_3$h = {
  key: 1,
  class: "pi pi-chevron-right text-xxs"
};
const TRUNCATED_HASH_LENGTH = 7;
const _sfc_main$v = /* @__PURE__ */ defineComponent({
  __name: "PackVersionBadge",
  props: {
    nodePack: {},
    isSelected: { type: Boolean },
    fill: { type: Boolean, default: true }
  },
  setup(__props) {
    const { isUpdateAvailable } = usePackUpdateStatus(() => __props.nodePack);
    const popoverRef = ref();
    const managerStore = useComfyManagerStore();
    const isInstalled = computed(() => managerStore.isPackInstalled(__props.nodePack?.id));
    const isDisabled = computed(
      () => isInstalled.value && !managerStore.isPackEnabled(__props.nodePack?.id)
    );
    const installedVersion = computed(() => {
      if (!__props.nodePack.id) return "nightly";
      const version = managerStore.installedPacks[__props.nodePack.id]?.ver ?? __props.nodePack.latest_version?.version ?? "nightly";
      return semverExports.valid(version) ? version : version.slice(0, TRUNCATED_HASH_LENGTH);
    });
    const toggleVersionSelector = /* @__PURE__ */ __name((event) => {
      popoverRef.value.toggle(event);
    }, "toggleVersionSelector");
    const closeVersionSelector = /* @__PURE__ */ __name(() => {
      popoverRef.value.hide();
    }, "closeVersionSelector");
    watch(
      () => __props.isSelected,
      (isSelected, wasSelected) => {
        if (wasSelected && !isSelected) {
          closeVersionSelector();
        }
      }
    );
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", null, [
        withDirectives((openBlock(), createElementBlock("div", {
          class: normalizeClass(["inline-flex items-center gap-1 rounded-2xl py-1 text-xs", {
            "bg-dialog-surface px-1.5": _ctx.fill,
            "cursor-pointer": !isDisabled.value,
            "cursor-not-allowed opacity-60": isDisabled.value
          }]),
          "aria-haspopup": !isDisabled.value,
          role: isDisabled.value ? "text" : "button",
          tabindex: isDisabled.value ? -1 : 0,
          onClick: _cache[0] || (_cache[0] = ($event) => !isDisabled.value && toggleVersionSelector($event)),
          onKeydown: [
            _cache[1] || (_cache[1] = withKeys(($event) => !isDisabled.value && toggleVersionSelector($event), ["enter"])),
            _cache[2] || (_cache[2] = withKeys(($event) => !isDisabled.value && toggleVersionSelector($event), ["space"]))
          ]
        }, [
          unref(isUpdateAvailable) ? (openBlock(), createElementBlock("i", _hoisted_2$o)) : createCommentVNode("", true),
          createBaseVNode("span", null, toDisplayString(installedVersion.value), 1),
          !isDisabled.value ? (openBlock(), createElementBlock("i", _hoisted_3$h)) : createCommentVNode("", true)
        ], 42, _hoisted_1$q)), [
          [
            _directive_tooltip,
            isDisabled.value ? _ctx.$t("manager.enablePackToChangeVersion") : null,
            void 0,
            { top: true }
          ]
        ]),
        createVNode(unref(script$g), {
          ref_key: "popoverRef",
          ref: popoverRef,
          pt: {
            content: { class: "p-0 shadow-lg" }
          }
        }, {
          default: withCtx(() => [
            createVNode(_sfc_main$w, {
              "installed-version": installedVersion.value,
              "node-pack": _ctx.nodePack,
              onCancel: closeVersionSelector,
              onSubmit: closeVersionSelector
            }, null, 8, ["installed-version", "node-pack"])
          ]),
          _: 1
        }, 512)
      ]);
    };
  }
});
const _hoisted_1$p = { class: "flex items-center gap-2" };
const TOGGLE_DEBOUNCE_MS = 256;
const _sfc_main$u = /* @__PURE__ */ defineComponent({
  __name: "PackEnableToggle",
  props: {
    nodePack: {}
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const { isPackEnabled, enablePack, disablePack, installedPacks } = useComfyManagerStore();
    const { getConflictsForPackageByID } = useConflictDetectionStore();
    const { showNodeConflictDialog } = useDialogService();
    const { acknowledgmentState, markConflictsAsSeen } = useConflictAcknowledgment();
    const isLoading = ref(false);
    const isEnabled = computed(() => isPackEnabled(__props.nodePack.id));
    const version = computed(() => {
      const id2 = __props.nodePack.id;
      if (!id2) return "nightly";
      return installedPacks[id2]?.ver ?? __props.nodePack.latest_version?.version ?? "nightly";
    });
    const packageConflict = computed(() => {
      if (!__props.nodePack.id) return void 0;
      return getConflictsForPackageByID(__props.nodePack.id);
    });
    const canToggleDirectly = computed(() => {
      return !(packageConflict.value?.has_conflict && !acknowledgmentState.value.modal_dismissed);
    });
    const showConflictModal = /* @__PURE__ */ __name((skipModalDismissed) => {
      let modal_dismissed = acknowledgmentState.value.modal_dismissed;
      if (skipModalDismissed) modal_dismissed = false;
      if (packageConflict.value && !modal_dismissed) {
        showNodeConflictDialog({
          conflictedPackages: [packageConflict.value],
          buttonText: !isEnabled.value ? t2("manager.conflicts.enableAnyway") : t2("manager.conflicts.understood"),
          onButtonClick: /* @__PURE__ */ __name(async () => {
            if (!isEnabled.value) {
              await handleEnable();
            }
          }, "onButtonClick"),
          dialogComponentProps: {
            onClose: /* @__PURE__ */ __name(() => {
              markConflictsAsSeen();
            }, "onClose")
          }
        });
      }
    }, "showConflictModal");
    const handleEnable = /* @__PURE__ */ __name(() => {
      if (!__props.nodePack.id) {
        throw new Error("Node ID is required for enabling");
      }
      return enablePack({
        id: __props.nodePack.id,
        version: version.value ?? "latest"
      });
    }, "handleEnable");
    const handleDisable = /* @__PURE__ */ __name(() => {
      if (!__props.nodePack.id) {
        throw new Error("Node ID is required for disabling");
      }
      return disablePack({
        id: __props.nodePack.id,
        version: version.value ?? "latest"
      });
    }, "handleDisable");
    const handleToggle = /* @__PURE__ */ __name(async (enable) => {
      if (isLoading.value) return;
      isLoading.value = true;
      if (enable) {
        await handleEnable();
      } else {
        await handleDisable();
      }
      isLoading.value = false;
    }, "handleToggle");
    const onToggle = debounce(
      (enable) => {
        void handleToggle(enable);
      },
      TOGGLE_DEBOUNCE_MS,
      { trailing: true }
    );
    const handleToggleInteraction = /* @__PURE__ */ __name(async (event) => {
      if (!canToggleDirectly.value) {
        event.preventDefault();
        showConflictModal(false);
      }
    }, "handleToggleInteraction");
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return openBlock(), createElementBlock("div", _hoisted_1$p, [
        packageConflict.value?.has_conflict ? withDirectives((openBlock(), createElementBlock("div", {
          key: 0,
          class: "flex h-6 w-6 cursor-pointer items-center justify-center",
          onClick: _cache[0] || (_cache[0] = ($event) => showConflictModal(true))
        }, _cache[1] || (_cache[1] = [
          createBaseVNode("i", { class: "pi pi-exclamation-triangle text-xl text-yellow-500" }, null, -1)
        ]))), [
          [_directive_tooltip, {
            value: _ctx.$t("manager.conflicts.warningTooltip"),
            showDelay: 300
          }]
        ]) : createCommentVNode("", true),
        !canToggleDirectly.value ? (openBlock(), createBlock(unref(script$z), {
          key: 1,
          "model-value": isEnabled.value,
          disabled: isLoading.value,
          readonly: !canToggleDirectly.value,
          "aria-label": _ctx.$t("g.enableOrDisablePack"),
          onFocus: handleToggleInteraction
        }, null, 8, ["model-value", "disabled", "readonly", "aria-label"])) : (openBlock(), createBlock(unref(script$z), {
          key: 2,
          "model-value": isEnabled.value,
          disabled: isLoading.value,
          "aria-label": _ctx.$t("g.enableOrDisablePack"),
          "onUpdate:modelValue": unref(onToggle)
        }, null, 8, ["model-value", "disabled", "aria-label", "onUpdate:modelValue"]))
      ]);
    };
  }
});
const _sfc_main$t = /* @__PURE__ */ defineComponent({
  __name: "PackTryUpdateButton",
  props: {
    nodePack: {},
    size: {}
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const managerStore = useComfyManagerStore();
    const isUpdating = ref(false);
    async function tryUpdate() {
      if (!__props.nodePack.id) {
        console.warn("Pack missing required id:", __props.nodePack);
        return;
      }
      isUpdating.value = true;
      try {
        await managerStore.updatePack.call({
          id: __props.nodePack.id,
          version: "nightly"
        });
        managerStore.updatePack.clear();
      } catch (error) {
        console.error("Nightly update failed:", error);
      } finally {
        isUpdating.value = false;
      }
    }
    __name(tryUpdate, "tryUpdate");
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return withDirectives((openBlock(), createBlock(_sfc_main$2A, {
        variant: "textonly",
        size: _ctx.size,
        disabled: isUpdating.value,
        onClick: tryUpdate
      }, {
        default: withCtx(() => [
          isUpdating.value ? (openBlock(), createBlock(DotSpinner, {
            key: 0,
            duration: "1s",
            size: _ctx.size === "sm" ? 12 : 16
          }, null, 8, ["size"])) : createCommentVNode("", true),
          createBaseVNode("span", null, toDisplayString(isUpdating.value ? unref(t2)("g.updating") : unref(t2)("manager.tryUpdate")), 1)
        ]),
        _: 1
      }, 8, ["size", "disabled"])), [
        [
          _directive_tooltip,
          _ctx.$t("manager.tryUpdateTooltip"),
          void 0,
          { top: true }
        ]
      ]);
    };
  }
});
const _sfc_main$s = /* @__PURE__ */ defineComponent({
  __name: "PackUninstallButton",
  props: {
    nodePacks: {},
    size: {}
  },
  setup(__props) {
    const managerStore = useComfyManagerStore();
    const { t: t2 } = useI18n();
    const createPayload = /* @__PURE__ */ __name((uninstallItem) => {
      if (!uninstallItem.id) {
        throw new Error("Node ID is required for uninstallation");
      }
      return {
        id: uninstallItem.id,
        version: uninstallItem.latest_version?.version || "unknown"
      };
    }, "createPayload");
    const uninstallPack = /* @__PURE__ */ __name((item) => managerStore.uninstallPack(createPayload(item)), "uninstallPack");
    const uninstallItems = /* @__PURE__ */ __name(async () => {
      if (!__props.nodePacks?.length) return;
      await Promise.all(__props.nodePacks.map(uninstallPack));
    }, "uninstallItems");
    return (_ctx, _cache) => {
      return openBlock(), createBlock(_sfc_main$2A, {
        variant: "textonly",
        size: _ctx.size,
        class: "border border-red-500",
        onClick: uninstallItems
      }, {
        default: withCtx(() => [
          createTextVNode(toDisplayString(_ctx.nodePacks.length > 1 ? unref(t2)("manager.uninstallSelected") : unref(t2)("manager.uninstall")), 1)
        ]),
        _: 1
      }, 8, ["size"]);
    };
  }
});
const _hoisted_1$o = { class: "aspect-[2/1] w-full max-w-[204] overflow-hidden rounded-lg" };
const _hoisted_2$n = {
  key: 0,
  class: "h-full w-full"
};
const _hoisted_3$g = ["alt"];
const _hoisted_4$d = {
  key: 1,
  class: "relative h-full w-full"
};
const _hoisted_5$b = ["src", "alt"];
const DEFAULT_BANNER$1 = "/assets/images/fallback-gradient-avatar.svg";
const _sfc_main$r = /* @__PURE__ */ defineComponent({
  __name: "PackIcon",
  props: {
    nodePack: {}
  },
  setup(__props) {
    const isImageError = ref(false);
    const showDefaultBanner = computed(() => !__props.nodePack.banner_url && !__props.nodePack.icon);
    const imgSrc = computed(() => __props.nodePack.banner_url || __props.nodePack.icon);
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$o, [
        showDefaultBanner.value ? (openBlock(), createElementBlock("div", _hoisted_2$n, [
          createBaseVNode("img", {
            src: DEFAULT_BANNER$1,
            alt: _ctx.$t("g.defaultBanner"),
            class: "h-full w-full object-cover"
          }, null, 8, _hoisted_3$g)
        ])) : (openBlock(), createElementBlock("div", _hoisted_4$d, [
          imgSrc.value ? (openBlock(), createElementBlock("div", {
            key: 0,
            class: "absolute inset-0 bg-cover bg-center bg-no-repeat",
            style: normalizeStyle({
              backgroundImage: `url(${imgSrc.value})`,
              filter: "blur(10px)"
            })
          }, null, 4)) : createCommentVNode("", true),
          createBaseVNode("img", {
            src: isImageError.value ? DEFAULT_BANNER$1 : imgSrc.value,
            alt: _ctx.nodePack.name + " banner",
            class: normalizeClass(
              isImageError.value ? "relative w-full h-full object-cover z-10" : "relative w-full h-full object-contain z-10"
            ),
            onError: _cache[0] || (_cache[0] = ($event) => isImageError.value = true)
          }, null, 42, _hoisted_5$b)
        ]))
      ]);
    };
  }
});
const _hoisted_1$n = {
  key: 0,
  class: "flex flex-col items-center"
};
const _hoisted_2$m = {
  class: "mt-4 mb-2 text-center text-2xl font-bold",
  style: { "word-break": "break-all" }
};
const _hoisted_3$f = { class: "inline-block text-base" };
const _hoisted_4$c = {
  key: 0,
  class: "mt-2 mb-4 flex w-full max-w-xs justify-center"
};
const _hoisted_5$a = {
  key: 1,
  class: "flex flex-col items-center"
};
const _sfc_main$q = /* @__PURE__ */ defineComponent({
  __name: "InfoPanelHeader",
  props: {
    nodePacks: {},
    hasConflict: { type: Boolean }
  },
  setup(__props) {
    const managerStore = useComfyManagerStore();
    const importFailedContext = inject(ImportFailedKey);
    const importFailed = importFailedContext?.importFailed;
    const isAllInstalled = ref(false);
    watch(
      [() => __props.nodePacks, () => managerStore.installedPacks],
      () => {
        isAllInstalled.value = __props.nodePacks.every(
          (nodePack) => managerStore.isPackInstalled(nodePack.id)
        );
      },
      { immediate: true }
    );
    const { checkNodeCompatibility } = useConflictDetection();
    const conflictInfo = computed(() => {
      if (!__props.nodePacks?.length) return [];
      const allConflicts = [];
      for (const nodePack of __props.nodePacks) {
        const compatibilityCheck = checkNodeCompatibility(nodePack);
        if (compatibilityCheck.conflicts) {
          allConflicts.push(...compatibilityCheck.conflicts);
        }
      }
      return allConflicts;
    });
    const computedHasConflict = computed(() => conflictInfo.value.length > 0);
    return (_ctx, _cache) => {
      return _ctx.nodePacks?.length ? (openBlock(), createElementBlock("div", _hoisted_1$n, [
        renderSlot(_ctx.$slots, "thumbnail", {}, () => [
          createVNode(_sfc_main$r, {
            "node-pack": _ctx.nodePacks[0],
            width: "204",
            height: "106"
          }, null, 8, ["node-pack"])
        ]),
        createBaseVNode("h2", _hoisted_2$m, [
          renderSlot(_ctx.$slots, "title", {}, () => [
            createBaseVNode("span", _hoisted_3$f, toDisplayString(_ctx.nodePacks[0].name), 1)
          ])
        ]),
        !unref(importFailed) ? (openBlock(), createElementBlock("div", _hoisted_4$c, [
          renderSlot(_ctx.$slots, "install-button", {}, () => [
            isAllInstalled.value ? (openBlock(), createBlock(_sfc_main$s, mergeProps({ key: 0 }, _ctx.$attrs, {
              size: "md",
              "node-packs": _ctx.nodePacks
            }), null, 16, ["node-packs"])) : (openBlock(), createBlock(_sfc_main$1b, mergeProps({ key: 1 }, _ctx.$attrs, {
              size: "md",
              "node-packs": _ctx.nodePacks,
              "has-conflict": _ctx.hasConflict || computedHasConflict.value,
              "conflict-info": conflictInfo.value
            }), null, 16, ["node-packs", "has-conflict", "conflict-info"]))
          ])
        ])) : createCommentVNode("", true)
      ])) : (openBlock(), createElementBlock("div", _hoisted_5$a, [
        createVNode(NoResultsPlaceholder, {
          message: _ctx.$t("manager.status.unknown"),
          title: _ctx.$t("manager.tryAgainLater")
        }, null, 8, ["message", "title"])
      ]));
    };
  }
});
const _hoisted_1$m = ["textContent"];
const _hoisted_2$l = {
  key: 1,
  class: "break-words"
};
const _hoisted_3$e = ["href"];
const _hoisted_4$b = { class: "text-blue-600" };
const _hoisted_5$9 = { key: 1 };
const _hoisted_6$8 = { key: 2 };
const _hoisted_7$6 = {
  key: 3,
  class: "rounded px-1 py-0.5 text-xs"
};
const _hoisted_8$5 = { key: 4 };
const _sfc_main$p = /* @__PURE__ */ defineComponent({
  __name: "MarkdownText",
  props: {
    text: {}
  },
  setup(__props) {
    const hasMarkdown = computed(() => {
      const hasMarkdown2 = /(\[.*?\]\(.*?\)|(\*\*|__)(.*?)(\*\*|__)|(\*|_)(.*?)(\*|_)|`(.*?)`)/.test(
        __props.text
      );
      return hasMarkdown2;
    });
    const parsedSegments = computed(() => {
      if (!hasMarkdown.value) return [{ type: "text", text: __props.text }];
      const segments = [];
      const remainingText = __props.text;
      let lastIndex = 0;
      const linkRegex = /\[(.*?)\]\((.*?)\)/g;
      let linkMatch;
      while ((linkMatch = linkRegex.exec(remainingText)) !== null) {
        if (linkMatch.index > lastIndex) {
          segments.push({
            type: "text",
            text: remainingText.substring(lastIndex, linkMatch.index)
          });
        }
        segments.push({
          type: "link",
          text: linkMatch[1],
          url: linkMatch[2]
        });
        lastIndex = linkMatch.index + linkMatch[0].length;
      }
      if (lastIndex < remainingText.length) {
        let rest = remainingText.substring(lastIndex);
        rest = rest.replace(/(\*\*|__)(.*?)(\*\*|__)/g, (_, __, p2) => {
          segments.push({ type: "bold", text: p2 });
          return "";
        });
        rest = rest.replace(/(\*|_)(.*?)(\*|_)/g, (_, __, p2) => {
          segments.push({ type: "italic", text: p2 });
          return "";
        });
        rest = rest.replace(/`(.*?)`/g, (_, p1) => {
          segments.push({ type: "code", text: p1 });
          return "";
        });
        if (rest) {
          segments.push({ type: "text", text: rest });
        }
      }
      return segments;
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", null, [
        !hasMarkdown.value ? (openBlock(), createElementBlock("div", {
          key: 0,
          class: "break-words",
          textContent: toDisplayString(_ctx.text)
        }, null, 8, _hoisted_1$m)) : (openBlock(), createElementBlock("div", _hoisted_2$l, [
          (openBlock(true), createElementBlock(Fragment, null, renderList(parsedSegments.value, (segment, index2) => {
            return openBlock(), createElementBlock(Fragment, { key: index2 }, [
              segment.type === "link" && "url" in segment ? (openBlock(), createElementBlock("a", {
                key: 0,
                href: segment.url,
                target: "_blank",
                rel: "noopener noreferrer",
                class: "hover:underline"
              }, [
                createBaseVNode("span", _hoisted_4$b, toDisplayString(segment.text), 1)
              ], 8, _hoisted_3$e)) : segment.type === "bold" ? (openBlock(), createElementBlock("strong", _hoisted_5$9, toDisplayString(segment.text), 1)) : segment.type === "italic" ? (openBlock(), createElementBlock("em", _hoisted_6$8, toDisplayString(segment.text), 1)) : segment.type === "code" ? (openBlock(), createElementBlock("code", _hoisted_7$6, toDisplayString(segment.text), 1)) : (openBlock(), createElementBlock("span", _hoisted_8$5, toDisplayString(segment.text), 1))
            ], 64);
          }), 128))
        ]))
      ]);
    };
  }
});
const _hoisted_1$l = { class: "flex flex-col gap-4 text-sm" };
const _hoisted_2$k = { class: "mb-3" };
const _hoisted_3$d = { class: "break-words text-muted" };
const _hoisted_4$a = ["href"];
const _hoisted_5$8 = {
  key: 0,
  class: "pi pi-github text-base"
};
const _hoisted_6$7 = { class: "break-all" };
const _sfc_main$o = /* @__PURE__ */ defineComponent({
  __name: "InfoTextSection",
  props: {
    sections: {}
  },
  setup(__props) {
    const isGitHubLink = /* @__PURE__ */ __name((url) => url.includes("github.com"), "isGitHubLink");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$l, [
        (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.sections, (section, index2) => {
          return openBlock(), createElementBlock("div", {
            key: index2,
            class: "mb-4"
          }, [
            createBaseVNode("div", _hoisted_2$k, toDisplayString(section.title), 1),
            createBaseVNode("div", _hoisted_3$d, [
              section.isUrl ? (openBlock(), createElementBlock("a", {
                key: 0,
                href: section.text,
                target: "_blank",
                rel: "noopener noreferrer",
                class: "flex items-center gap-2"
              }, [
                isGitHubLink(section.text) ? (openBlock(), createElementBlock("i", _hoisted_5$8)) : createCommentVNode("", true),
                createBaseVNode("span", _hoisted_6$7, toDisplayString(section.text), 1)
              ], 8, _hoisted_4$a)) : (openBlock(), createBlock(_sfc_main$p, {
                key: 1,
                text: section.text,
                class: "text-muted"
              }, null, 8, ["text"]))
            ])
          ]);
        }), 128))
      ]);
    };
  }
});
const _hoisted_1$k = { class: "overflow-hidden" };
const _hoisted_2$j = {
  key: 1,
  class: "text-sm text-muted italic"
};
const _hoisted_3$c = { key: 2 };
const _hoisted_4$9 = { class: "mb-1" };
const _sfc_main$n = /* @__PURE__ */ defineComponent({
  __name: "DescriptionTabPanel",
  props: {
    nodePack: {}
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const isLicenseFile = /* @__PURE__ */ __name((filename) => {
      const licensePattern = /^license(\.md|\.txt)?$/i;
      return licensePattern.test(filename);
    }, "isLicenseFile");
    const extractBaseRepoUrl = /* @__PURE__ */ __name((repoUrl) => {
      const githubRepoPattern = /^(https?:\/\/github\.com\/[^/]+\/[^/]+)/i;
      const match = repoUrl.match(githubRepoPattern);
      return match ? match[1] : repoUrl;
    }, "extractBaseRepoUrl");
    const createLicenseUrl = /* @__PURE__ */ __name((filename, repoUrl) => {
      if (!repoUrl || !filename) return "";
      const licenseFile = isLicenseFile(filename) ? filename : "LICENSE";
      const baseRepoUrl = extractBaseRepoUrl(repoUrl);
      return `${baseRepoUrl}/blob/main/${licenseFile}`;
    }, "createLicenseUrl");
    const parseLicenseObject = /* @__PURE__ */ __name((licenseObj) => {
      const licenseFile = licenseObj.file || licenseObj.text;
      if (typeof licenseFile === "string" && isLicenseFile(licenseFile) && __props.nodePack.repository) {
        const url = createLicenseUrl(licenseFile, __props.nodePack.repository);
        return {
          text: url,
          isUrl: !!url && isValidUrl(url)
        };
      } else if (licenseObj.text) {
        return {
          text: licenseObj.text,
          isUrl: false
        };
      } else if (typeof licenseFile === "string") {
        return {
          text: licenseFile,
          isUrl: false
        };
      }
      return {
        text: JSON.stringify(licenseObj),
        isUrl: false
      };
    }, "parseLicenseObject");
    const formatLicense = /* @__PURE__ */ __name((license) => {
      if (license === "{}") return null;
      try {
        const licenseObj = JSON.parse(license);
        if (Object.keys(licenseObj).length === 0) {
          return null;
        }
        return parseLicenseObject(licenseObj);
      } catch (e2) {
        if (isLicenseFile(license) && __props.nodePack.repository) {
          const url = createLicenseUrl(license, __props.nodePack.repository);
          return {
            text: url,
            isUrl: !!url && isValidUrl(url)
          };
        }
        return {
          text: license,
          isUrl: false
        };
      }
    }, "formatLicense");
    const descriptionSections = computed(() => {
      const sections = [
        {
          title: t2("g.description"),
          text: __props.nodePack.description || t2("manager.noDescription")
        }
      ];
      if (__props.nodePack.repository) {
        sections.push({
          title: t2("manager.repository"),
          text: __props.nodePack.repository,
          isUrl: isValidUrl(__props.nodePack.repository)
        });
      }
      if (__props.nodePack.license) {
        const licenseInfo = formatLicense(__props.nodePack.license);
        if (licenseInfo && licenseInfo.text) {
          sections.push({
            title: t2("manager.license"),
            text: licenseInfo.text,
            isUrl: licenseInfo.isUrl
          });
        }
      }
      return sections;
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$k, [
        _ctx.nodePack?.description ? (openBlock(), createBlock(_sfc_main$o, {
          key: 0,
          sections: descriptionSections.value
        }, null, 8, ["sections"])) : (openBlock(), createElementBlock("p", _hoisted_2$j, toDisplayString(_ctx.$t("manager.noDescription")), 1)),
        _ctx.nodePack?.latest_version?.dependencies?.length ? (openBlock(), createElementBlock("div", _hoisted_3$c, [
          createBaseVNode("p", _hoisted_4$9, toDisplayString(_ctx.$t("manager.dependencies")), 1),
          (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.nodePack.latest_version.dependencies, (dep, index2) => {
            return openBlock(), createElementBlock("div", {
              key: index2,
              class: "break-words text-muted"
            }, toDisplayString(dep), 1);
          }), 128))
        ])) : createCommentVNode("", true)
      ]);
    };
  }
});
const registryToFrontendV2NodeOutputs = /* @__PURE__ */ __name((registryDef) => {
  const returnTypes = JSON.parse(registryDef.return_types ?? "{}");
  if (!returnTypes.length) return [];
  const returnNames = JSON.parse(registryDef.return_names ?? "{}");
  const outputsIsList = registryDef.output_is_list ?? [];
  const outputs = [];
  for (let i = 0; i < returnTypes.length; i++) {
    outputs.push({
      type: returnTypes[i],
      name: returnNames[i] || returnTypes[i],
      is_list: outputsIsList[i] ?? false,
      index: i
    });
  }
  return outputs;
}, "registryToFrontendV2NodeOutputs");
const registryToFrontendV2NodeInputs = /* @__PURE__ */ __name((registryDef) => {
  const inputTypes = JSON.parse(
    registryDef.input_types ?? "{}"
  );
  if (!inputTypes || !Object.keys(inputTypes).length) return {};
  const inputsV2 = {};
  if (inputTypes.required) {
    Object.entries(inputTypes.required).forEach(([name2, inputSpecV1]) => {
      inputsV2[name2] = transformInputSpecV1ToV2(inputSpecV1, {
        name: name2,
        isOptional: false
      });
    });
  }
  if (inputTypes.optional) {
    Object.entries(inputTypes.optional).forEach(([name2, inputSpecV1]) => {
      inputsV2[name2] = transformInputSpecV1ToV2(inputSpecV1, {
        name: name2,
        isOptional: true
      });
    });
  }
  return inputsV2;
}, "registryToFrontendV2NodeInputs");
const registryToFrontendV2NodeDef = /* @__PURE__ */ __name((nodeDef, nodePack) => {
  const name2 = nodeDef.comfy_node_name ?? "Node Name";
  return {
    category: nodeDef.category ?? "unknown",
    description: nodeDef.description ?? "",
    display_name: name2,
    name: name2,
    inputs: registryToFrontendV2NodeInputs(nodeDef),
    outputs: registryToFrontendV2NodeOutputs(nodeDef),
    output_node: false,
    python_module: nodePack.name ?? nodePack.id ?? "unknown"
  };
}, "registryToFrontendV2NodeDef");
const _hoisted_1$j = { class: "flex flex-col gap-4 text-sm" };
const _hoisted_2$i = { class: "[zoom:0.6]" };
const _sfc_main$m = /* @__PURE__ */ defineComponent({
  __name: "NodesTabPanel",
  props: {
    nodePack: {},
    nodeNames: {}
  },
  setup(__props) {
    const { getNodeDefs } = useComfyRegistryStore();
    const isLoading = ref(false);
    const registryNodeDefs = shallowRef(null);
    const fetchNodeDefs = /* @__PURE__ */ __name(async () => {
      getNodeDefs.cancel();
      isLoading.value = true;
      const { id: packId } = __props.nodePack;
      const version = __props.nodePack.latest_version?.version;
      if (!packId || !version) {
        registryNodeDefs.value = null;
      } else {
        const response = await getNodeDefs.call({
          packId,
          version,
          page: 1,
          limit: 256
        });
        registryNodeDefs.value = response?.comfy_nodes ?? null;
      }
      isLoading.value = false;
    }, "fetchNodeDefs");
    whenever(() => __props.nodePack, fetchNodeDefs, { immediate: true, deep: true });
    const toFrontendNodeDef = /* @__PURE__ */ __name((nodeDef) => {
      try {
        return registryToFrontendV2NodeDef(nodeDef, __props.nodePack);
      } catch (error) {
        return null;
      }
    }, "toFrontendNodeDef");
    const mappedNodeDefs = computed(() => {
      if (!registryNodeDefs.value) return null;
      return registryNodeDefs.value.map(toFrontendNodeDef).filter((nodeDef) => nodeDef !== null);
    });
    const createNodeDefKey = /* @__PURE__ */ __name((nodeDef) => `${nodeDef.category}${nodeDef.comfy_node_name ?? useId()}`, "createNodeDefKey");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$j, [
        mappedNodeDefs.value?.length ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(mappedNodeDefs.value, (nodeDef) => {
          return openBlock(), createElementBlock("div", {
            key: createNodeDefKey(nodeDef),
            class: "rounded-lg border p-4"
          }, [
            createBaseVNode("div", _hoisted_2$i, [
              createVNode(NodePreview, {
                "node-def": nodeDef,
                position: "relative",
                class: "min-w-full! text-[.625rem]!"
              }, null, 8, ["node-def"])
            ])
          ]);
        }), 128)) : isLoading.value ? (openBlock(), createBlock(unref(script$7), { key: 1 })) : _ctx.nodeNames.length ? (openBlock(true), createElementBlock(Fragment, { key: 2 }, renderList(_ctx.nodeNames, (node2) => {
          return openBlock(), createElementBlock("div", {
            key: node2,
            class: "truncate text-muted"
          }, toDisplayString(node2), 1);
        }), 128)) : (openBlock(), createBlock(NoResultsPlaceholder, {
          key: 3,
          title: _ctx.$t("manager.noNodesFound"),
          message: _ctx.$t("manager.noNodesFoundDescription")
        }, null, 8, ["title", "message"]))
      ]);
    };
  }
});
function extractImportFailedConflicts(conflicts) {
  if (!conflicts) return null;
  const importFailedConflicts = conflicts.filter(
    (item) => item.type === "import_failed"
  );
  return importFailedConflicts.length > 0 ? importFailedConflicts : null;
}
__name(extractImportFailedConflicts, "extractImportFailedConflicts");
function createImportFailedDialog() {
  const { t: t2 } = useI18n();
  const { showErrorDialog } = useDialogService();
  return (importFailedInfo) => {
    if (importFailedInfo) {
      const errorMessage = importFailedInfo.map((conflict) => conflict.required_value).filter(Boolean).join("\n") || t2("manager.importFailedGenericError");
      const error = new Error(errorMessage);
      showErrorDialog(error, {
        title: t2("manager.failedToInstall"),
        reportType: "importFailedError"
      });
    }
  };
}
__name(createImportFailedDialog, "createImportFailedDialog");
function useImportFailedDetection(packageId) {
  const { isPackInstalled } = useComfyManagerStore();
  const { getConflictsForPackageByID } = useConflictDetectionStore();
  const isInstalled = computed(
    () => packageId ? isPackInstalled(unref(packageId)) : false
  );
  const conflicts = computed(() => {
    const currentPackageId = unref(packageId);
    if (!currentPackageId || !isInstalled.value) return null;
    return getConflictsForPackageByID(currentPackageId) || null;
  });
  const importFailedInfo = computed(() => {
    return extractImportFailedConflicts(conflicts.value?.conflicts);
  });
  const importFailed = computed(() => {
    return importFailedInfo.value !== null;
  });
  const showImportFailedDialog = createImportFailedDialog();
  return {
    importFailedInfo,
    importFailed,
    showImportFailedDialog: /* @__PURE__ */ __name(() => showImportFailedDialog(importFailedInfo.value), "showImportFailedDialog"),
    isInstalled
  };
}
__name(useImportFailedDetection, "useImportFailedDetection");
const _hoisted_1$i = { class: "flex flex-col gap-3" };
const _hoisted_2$h = { class: "text-sm text-base-foreground" };
const _hoisted_3$b = { class: "flex items-center justify-between" };
const _hoisted_4$8 = { class: "flex-1 text-sm break-words" };
const _sfc_main$l = /* @__PURE__ */ defineComponent({
  __name: "WarningTabPanel",
  props: {
    nodePack: {},
    conflictResult: {}
  },
  setup(__props) {
    const packageId = computed(() => __props.nodePack?.id || "");
    const { importFailedInfo, showImportFailedDialog } = useImportFailedDetection(packageId);
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$i, [
        unref(importFailedInfo) ? (openBlock(), createElementBlock("button", {
          key: 0,
          class: "inline-flex cursor-pointer items-center justify-end gap-1 border-none bg-transparent outline-none",
          onClick: _cache[0] || (_cache[0] = //@ts-ignore
          (...args) => unref(showImportFailedDialog) && unref(showImportFailedDialog)(...args))
        }, [
          _cache[1] || (_cache[1] = createBaseVNode("i", { class: "pi pi-code text-base" }, null, -1)),
          createBaseVNode("span", _hoisted_2$h, toDisplayString(unref(t)("serverStart.openLogs")), 1)
        ])) : createCommentVNode("", true),
        (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.conflictResult?.conflicts || [], (conflict, index2) => {
          return openBlock(), createElementBlock("div", {
            key: index2,
            class: "rounded-md bg-yellow-800/20 p-3"
          }, [
            createBaseVNode("div", _hoisted_3$b, [
              createBaseVNode("div", _hoisted_4$8, toDisplayString(unref(getConflictMessage)(conflict, _ctx.$t)), 1)
            ])
          ]);
        }), 128))
      ]);
    };
  }
});
const _hoisted_1$h = { class: "overflow-hidden" };
const _hoisted_2$g = { class: "flex items-center gap-1" };
const _sfc_main$k = /* @__PURE__ */ defineComponent({
  __name: "InfoTabs",
  props: {
    nodePack: {},
    hasCompatibilityIssues: { type: Boolean },
    conflictResult: {}
  },
  setup(__props) {
    const importFailedContext = inject(ImportFailedKey);
    const importFailed = importFailedContext?.importFailed;
    const nodeNames = computed(() => {
      const { comfy_nodes } = __props.nodePack;
      return comfy_nodes ?? [];
    });
    const activeTab = ref("description");
    watchEffect(
      () => {
        if (__props.hasCompatibilityIssues) {
          activeTab.value = "warning";
        } else if (activeTab.value === "warning") {
          activeTab.value = "description";
        }
      },
      { flush: "post" }
    );
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$h, [
        createVNode(unref(script$A), { value: activeTab.value }, {
          default: withCtx(() => [
            createVNode(unref(script$E), { class: "scrollbar-hide overflow-x-auto" }, {
              default: withCtx(() => [
                _ctx.hasCompatibilityIssues ? (openBlock(), createBlock(unref(script$F), {
                  key: 0,
                  value: "warning",
                  class: "mr-6 p-2 font-inter"
                }, {
                  default: withCtx(() => [
                    createBaseVNode("div", _hoisted_2$g, [
                      _cache[0] || (_cache[0] = createBaseVNode("span", null, "⚠️", -1)),
                      createTextVNode(" " + toDisplayString(unref(importFailed) ? _ctx.$t("g.error") : _ctx.$t("g.warning")), 1)
                    ])
                  ]),
                  _: 1
                })) : createCommentVNode("", true),
                createVNode(unref(script$F), {
                  value: "description",
                  class: "mr-6 p-2 font-inter"
                }, {
                  default: withCtx(() => [
                    createTextVNode(toDisplayString(_ctx.$t("g.description")), 1)
                  ]),
                  _: 1
                }),
                createVNode(unref(script$F), {
                  value: "nodes",
                  class: "p-2 font-inter"
                }, {
                  default: withCtx(() => [
                    createTextVNode(toDisplayString(_ctx.$t("g.nodes")), 1)
                  ]),
                  _: 1
                })
              ]),
              _: 1
            }),
            createVNode(unref(script$B), { class: "overflow-auto px-2 py-4" }, {
              default: withCtx(() => [
                _ctx.hasCompatibilityIssues ? (openBlock(), createBlock(unref(script$u), {
                  key: 0,
                  value: "warning",
                  class: "bg-transparent"
                }, {
                  default: withCtx(() => [
                    createVNode(_sfc_main$l, {
                      "node-pack": _ctx.nodePack,
                      "conflict-result": _ctx.conflictResult
                    }, null, 8, ["node-pack", "conflict-result"])
                  ]),
                  _: 1
                })) : createCommentVNode("", true),
                createVNode(unref(script$u), { value: "description" }, {
                  default: withCtx(() => [
                    createVNode(_sfc_main$n, { "node-pack": _ctx.nodePack }, null, 8, ["node-pack"])
                  ]),
                  _: 1
                }),
                createVNode(unref(script$u), { value: "nodes" }, {
                  default: withCtx(() => [
                    createVNode(_sfc_main$m, {
                      "node-pack": _ctx.nodePack,
                      "node-names": nodeNames.value
                    }, null, 8, ["node-pack", "node-names"])
                  ]),
                  _: 1
                })
              ]),
              _: 1
            })
          ]),
          _: 1
        }, 8, ["value"])
      ]);
    };
  }
});
const _hoisted_1$g = { class: "flex py-1.5 text-xs" };
const _hoisted_2$f = { class: "w-1/3 truncate pr-2 text-muted" };
const _hoisted_3$a = { class: "w-2/3" };
const _sfc_main$j = /* @__PURE__ */ defineComponent({
  __name: "MetadataRow",
  props: {
    label: {},
    value: { default: "N/A" }
  },
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$g, [
        createBaseVNode("div", _hoisted_2$f, toDisplayString(_ctx.label), 1),
        createBaseVNode("div", _hoisted_3$a, [
          renderSlot(_ctx.$slots, "default", {}, () => [
            createTextVNode(toDisplayString(_ctx.value), 1)
          ])
        ])
      ]);
    };
  }
});
const _hoisted_1$f = {
  key: 0,
  class: "relative z-40 flex h-full flex-col overflow-hidden"
};
const _hoisted_2$e = { class: "top-0 z-10 w-full px-6 pt-6" };
const _hoisted_3$9 = { class: "flex w-full justify-center gap-2" };
const _hoisted_4$7 = { class: "mb-6" };
const _hoisted_5$7 = { class: "mb-6 overflow-hidden" };
const _hoisted_6$6 = {
  key: 1,
  class: "flex-1 overflow-hidden px-8 pt-4 text-sm"
};
const _sfc_main$i = /* @__PURE__ */ defineComponent({
  __name: "InfoPanel",
  props: {
    nodePack: {}
  },
  setup(__props) {
    const scrollContainer = ref(null);
    const { isPackInstalled } = useComfyManagerStore();
    const isInstalled = computed(() => isPackInstalled(__props.nodePack.id));
    const isInstalling = ref(false);
    provide(IsInstallingKey, isInstalling);
    whenever(isInstalled, () => {
      isInstalling.value = false;
    });
    const { canTryNightlyUpdate } = usePackUpdateStatus(() => __props.nodePack);
    const { checkNodeCompatibility } = useConflictDetection();
    const { getConflictsForPackageByID } = useConflictDetectionStore();
    const { t: t2, d: d2, n } = useI18n();
    const conflictResult = computed(() => {
      if (isInstalled.value && __props.nodePack.id) {
        return getConflictsForPackageByID(__props.nodePack.id) || null;
      }
      const compatibility = checkNodeCompatibility(__props.nodePack);
      if (compatibility.hasConflict) {
        return {
          package_id: __props.nodePack.id || "",
          package_name: __props.nodePack.name || "",
          has_conflict: true,
          conflicts: compatibility.conflicts,
          is_compatible: false
        };
      }
      return null;
    });
    const hasCompatibilityIssues = computed(() => {
      return conflictResult.value?.has_conflict;
    });
    const packageId = computed(() => __props.nodePack.id || "");
    const { importFailed, showImportFailedDialog } = useImportFailedDetection(packageId);
    provide(ImportFailedKey, {
      importFailed,
      showImportFailedDialog
    });
    const infoItems = computed(() => [
      {
        key: "publisher",
        label: t2("manager.createdBy"),
        value: __props.nodePack.publisher?.name ?? __props.nodePack.publisher?.id
      },
      {
        key: "downloads",
        label: t2("manager.downloads"),
        value: __props.nodePack.downloads ? n(__props.nodePack.downloads) : void 0
      },
      {
        key: "lastUpdated",
        label: t2("manager.lastUpdated"),
        value: __props.nodePack.latest_version?.createdAt ? d2(__props.nodePack.latest_version.createdAt, {
          dateStyle: "medium"
        }) : void 0
      }
    ]);
    const { y } = useScroll(scrollContainer, {
      eventListenerOptions: {
        passive: true
      }
    });
    const onNodePackChange = /* @__PURE__ */ __name(() => {
      y.value = 0;
    }, "onNodePackChange");
    whenever(
      () => __props.nodePack.id,
      (nodePackId, oldNodePackId) => {
        if (nodePackId !== oldNodePackId) {
          onNodePackChange();
        }
      },
      { immediate: true }
    );
    return (_ctx, _cache) => {
      return _ctx.nodePack ? (openBlock(), createElementBlock("div", _hoisted_1$f, [
        createBaseVNode("div", _hoisted_2$e, [
          createVNode(_sfc_main$q, {
            "node-packs": [_ctx.nodePack],
            "has-conflict": hasCompatibilityIssues.value
          }, createSlots({ _: 2 }, [
            unref(canTryNightlyUpdate) ? {
              name: "install-button",
              fn: withCtx(() => [
                createBaseVNode("div", _hoisted_3$9, [
                  createVNode(_sfc_main$t, {
                    "node-pack": _ctx.nodePack,
                    size: "md"
                  }, null, 8, ["node-pack"]),
                  createVNode(_sfc_main$s, {
                    "node-packs": [_ctx.nodePack],
                    size: "md"
                  }, null, 8, ["node-packs"])
                ])
              ]),
              key: "0"
            } : void 0
          ]), 1032, ["node-packs", "has-conflict"])
        ]),
        createBaseVNode("div", {
          ref_key: "scrollContainer",
          ref: scrollContainer,
          class: "scrollbar-hide flex-1 overflow-y-auto p-6 pt-2 text-sm"
        }, [
          createBaseVNode("div", _hoisted_4$7, [
            !unref(importFailed) && unref(isPackInstalled)(_ctx.nodePack.id) ? (openBlock(), createBlock(_sfc_main$j, {
              key: 0,
              label: unref(t2)("manager.filter.enabled"),
              class: "flex",
              style: { "align-items": "center" }
            }, {
              default: withCtx(() => [
                createVNode(_sfc_main$u, {
                  "node-pack": _ctx.nodePack,
                  "has-conflict": hasCompatibilityIssues.value
                }, null, 8, ["node-pack", "has-conflict"])
              ]),
              _: 1
            }, 8, ["label"])) : createCommentVNode("", true),
            (openBlock(true), createElementBlock(Fragment, null, renderList(infoItems.value, (item) => {
              return withDirectives((openBlock(), createBlock(_sfc_main$j, {
                key: item.key,
                label: item.label,
                value: item.value
              }, null, 8, ["label", "value"])), [
                [vShow, item.value !== void 0 && item.value !== null]
              ]);
            }), 128)),
            createVNode(_sfc_main$j, {
              label: unref(t2)("g.status")
            }, {
              default: withCtx(() => [
                createVNode(_sfc_main$y, {
                  "status-type": _ctx.nodePack.status,
                  "has-compatibility-issues": hasCompatibilityIssues.value
                }, null, 8, ["status-type", "has-compatibility-issues"])
              ]),
              _: 1
            }, 8, ["label"]),
            createVNode(_sfc_main$j, {
              label: unref(t2)("manager.version")
            }, {
              default: withCtx(() => [
                createVNode(_sfc_main$v, {
                  "node-pack": _ctx.nodePack,
                  "is-selected": true
                }, null, 8, ["node-pack"])
              ]),
              _: 1
            }, 8, ["label"])
          ]),
          createBaseVNode("div", _hoisted_5$7, [
            createVNode(_sfc_main$k, {
              "node-pack": _ctx.nodePack,
              "has-compatibility-issues": hasCompatibilityIssues.value,
              "conflict-result": conflictResult.value
            }, null, 8, ["node-pack", "has-compatibility-issues", "conflict-result"])
          ])
        ], 512)
      ])) : (openBlock(), createElementBlock("div", _hoisted_6$6, toDisplayString(_ctx.$t("manager.infoPanelEmpty")), 1));
    };
  }
});
const _hoisted_1$e = { class: "relative h-[104px] w-[224px] shadow-xl" };
const _hoisted_2$d = { class: "rounded-lg border p-0.5 shadow-lg" };
const _sfc_main$h = /* @__PURE__ */ defineComponent({
  __name: "PackIconStacked",
  props: {
    nodePacks: {},
    maxVisible: { default: 3 },
    offset: { default: 8 }
  },
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$e, [
        (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.nodePacks.slice(0, _ctx.maxVisible), (pack, index2) => {
          return openBlock(), createElementBlock("div", {
            key: pack.id,
            class: "absolute h-[90px] w-[210px]",
            style: normalizeStyle({
              bottom: `${index2 * _ctx.offset}px`,
              right: `${index2 * _ctx.offset}px`,
              zIndex: _ctx.maxVisible - index2
            })
          }, [
            createBaseVNode("div", _hoisted_2$d, [
              createVNode(_sfc_main$r, { "node-pack": pack }, null, 8, ["node-pack"])
            ])
          ], 4);
        }), 128))
      ]);
    };
  }
});
function usePacksSelection(nodePacks) {
  const managerStore = useComfyManagerStore();
  const installedPacks = computed(
    () => nodePacks.value.filter((pack) => managerStore.isPackInstalled(pack.id))
  );
  const notInstalledPacks = computed(
    () => nodePacks.value.filter((pack) => !managerStore.isPackInstalled(pack.id))
  );
  const isAllInstalled = computed(
    () => installedPacks.value.length === nodePacks.value.length
  );
  const isNoneInstalled = computed(
    () => notInstalledPacks.value.length === nodePacks.value.length
  );
  const isMixed = computed(
    () => installedPacks.value.length > 0 && notInstalledPacks.value.length > 0
  );
  const selectionState = computed(() => {
    if (isAllInstalled.value) return "all-installed";
    if (isNoneInstalled.value) return "none-installed";
    return "mixed";
  });
  const nightlyPacks = computed(
    () => installedPacks.value.filter((pack) => {
      if (!pack.id) return false;
      const version = managerStore.getInstalledPackVersion(pack.id);
      const isNightly = !!version && !semverExports.valid(version);
      const isEnabled = managerStore.isPackEnabled(pack.id);
      return isNightly && isEnabled;
    })
  );
  const hasNightlyPacks = computed(() => nightlyPacks.value.length > 0);
  return {
    installedPacks,
    notInstalledPacks,
    isAllInstalled,
    isNoneInstalled,
    isMixed,
    selectionState,
    nightlyPacks,
    hasNightlyPacks
  };
}
__name(usePacksSelection, "usePacksSelection");
const STATUS_PRIORITY = [
  "NodeStatusBanned",
  "NodeVersionStatusBanned",
  "NodeStatusDeleted",
  "NodeVersionStatusDeleted",
  "NodeVersionStatusFlagged",
  "NodeVersionStatusPending",
  "NodeStatusActive",
  "NodeVersionStatusActive"
];
function usePacksStatus(nodePacks) {
  const conflictDetectionStore = useConflictDetectionStore();
  const hasImportFailed = computed(() => {
    return nodePacks.value.some((pack) => {
      if (!pack.id) return false;
      const conflicts = conflictDetectionStore.getConflictsForPackageByID(
        pack.id
      );
      return conflicts?.conflicts?.some((c2) => c2.type === "import_failed") || false;
    });
  });
  const overallStatus = computed(() => {
    if (hasImportFailed.value) {
      return "NodeVersionStatusActive";
    }
    for (const priorityStatus of STATUS_PRIORITY) {
      if (nodePacks.value.some((pack) => pack.status === priorityStatus)) {
        return priorityStatus;
      }
    }
    return "NodeVersionStatusActive";
  });
  return {
    hasImportFailed,
    overallStatus
  };
}
__name(usePacksStatus, "usePacksStatus");
const _hoisted_1$d = {
  key: 0,
  class: "flex h-full flex-col"
};
const _hoisted_2$c = { class: "flex-1 overflow-auto p-6" };
const _hoisted_3$8 = { class: "mt-5" };
const _hoisted_4$6 = { class: "mr-2 inline-block text-base text-blue-500" };
const _hoisted_5$6 = { class: "text-base" };
const _hoisted_6$5 = {
  key: 0,
  class: "text-sm text-neutral-500"
};
const _hoisted_7$5 = {
  key: 1,
  class: "flex w-full justify-center gap-2"
};
const _hoisted_8$4 = { class: "mb-6" };
const _hoisted_9$4 = {
  key: 1,
  class: "mx-8 mt-4 flex-1 overflow-hidden text-sm"
};
const _sfc_main$g = /* @__PURE__ */ defineComponent({
  __name: "InfoPanelMultiItem",
  props: {
    nodePacks: {}
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const managerStore = useComfyManagerStore();
    const nodePacksRef = toRef$2(() => __props.nodePacks);
    const {
      installedPacks,
      notInstalledPacks,
      isAllInstalled,
      isNoneInstalled,
      isMixed,
      nightlyPacks,
      hasNightlyPacks
    } = usePacksSelection(nodePacksRef);
    const { hasImportFailed, overallStatus } = usePacksStatus(nodePacksRef);
    const isUpdatingSelected = ref(false);
    async function updateSelectedNightlyPacks() {
      if (nightlyPacks.value.length === 0) return;
      isUpdatingSelected.value = true;
      try {
        for (const pack of nightlyPacks.value) {
          if (!pack.id) continue;
          await managerStore.updatePack.call({
            id: pack.id,
            version: "nightly"
          });
        }
        managerStore.updatePack.clear();
      } catch (error) {
        console.error("Batch nightly update failed:", error);
      } finally {
        isUpdatingSelected.value = false;
      }
    }
    __name(updateSelectedNightlyPacks, "updateSelectedNightlyPacks");
    const updateSelectedLabel = computed(
      () => isUpdatingSelected.value ? t2("g.updating") : t2("manager.updateSelected")
    );
    const { checkNodeCompatibility } = useConflictDetection();
    const { getNodeDefs } = useComfyRegistryStore();
    provide(ImportFailedKey, {
      importFailed: hasImportFailed,
      showImportFailedDialog: /* @__PURE__ */ __name(() => {
      }, "showImportFailedDialog")
      // No-op for multi-selection
    });
    const packageConflicts = computed(() => {
      const conflictsByPackage = /* @__PURE__ */ new Map();
      for (const pack of notInstalledPacks.value) {
        const compatibilityCheck = checkNodeCompatibility(pack);
        if (compatibilityCheck.hasConflict && pack.id) {
          conflictsByPackage.set(pack.id, compatibilityCheck.conflicts);
        }
      }
      return conflictsByPackage;
    });
    const conflictInfo = computed(() => {
      const conflictMap = /* @__PURE__ */ new Map();
      packageConflicts.value.forEach((conflicts) => {
        conflicts.forEach((conflict) => {
          const key = `${conflict.type}-${conflict.current_value}-${conflict.required_value}`;
          if (!conflictMap.has(key)) {
            conflictMap.set(key, conflict);
          }
        });
      });
      return Array.from(conflictMap.values());
    });
    const hasConflicts = computed(() => conflictInfo.value.length > 0);
    const getPackNodes = /* @__PURE__ */ __name(async (pack) => {
      if (!pack.latest_version?.version) return [];
      const nodeDefs = await getNodeDefs.call({
        packId: pack.id,
        version: pack.latest_version?.version,
        // Fetch all nodes.
        // TODO: Render all nodes previews and handle pagination.
        // For determining length, use the `totalNumberOfPages` field of response
        limit: 8192
      });
      return nodeDefs?.comfy_nodes ?? [];
    }, "getPackNodes");
    const { state: allNodeDefs } = useAsyncState(
      () => Promise.all(__props.nodePacks.map(getPackNodes)),
      [],
      {
        immediate: true
      }
    );
    const totalNodesCount = computed(
      () => allNodeDefs.value.reduce(
        (total, nodeDefs) => total + (nodeDefs?.length || 0),
        0
      )
    );
    onUnmounted(() => {
      getNodeDefs.cancel();
    });
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return _ctx.nodePacks?.length ? (openBlock(), createElementBlock("div", _hoisted_1$d, [
        createBaseVNode("div", _hoisted_2$c, [
          createVNode(_sfc_main$q, { "node-packs": _ctx.nodePacks }, {
            thumbnail: withCtx(() => [
              createVNode(_sfc_main$h, { "node-packs": _ctx.nodePacks }, null, 8, ["node-packs"])
            ]),
            title: withCtx(() => [
              createBaseVNode("div", _hoisted_3$8, [
                createBaseVNode("span", _hoisted_4$6, toDisplayString(_ctx.nodePacks.length), 1),
                createBaseVNode("span", _hoisted_5$6, toDisplayString(_ctx.$t("manager.packsSelected")), 1)
              ])
            ]),
            "install-button": withCtx(() => [
              unref(isMixed) ? (openBlock(), createElementBlock("div", _hoisted_6$5, toDisplayString(_ctx.$t("manager.mixedSelectionMessage")), 1)) : unref(isAllInstalled) ? (openBlock(), createElementBlock("div", _hoisted_7$5, [
                unref(hasNightlyPacks) ? withDirectives((openBlock(), createBlock(_sfc_main$2A, {
                  key: 0,
                  variant: "textonly",
                  size: "md",
                  disabled: isUpdatingSelected.value,
                  onClick: updateSelectedNightlyPacks
                }, {
                  default: withCtx(() => [
                    isUpdatingSelected.value ? (openBlock(), createBlock(DotSpinner, {
                      key: 0,
                      duration: "1s",
                      size: 16
                    })) : createCommentVNode("", true),
                    createBaseVNode("span", null, toDisplayString(updateSelectedLabel.value), 1)
                  ]),
                  _: 1
                }, 8, ["disabled"])), [
                  [
                    _directive_tooltip,
                    _ctx.$t("manager.tryUpdateTooltip"),
                    void 0,
                    { top: true }
                  ]
                ]) : createCommentVNode("", true),
                createVNode(_sfc_main$s, {
                  size: "md",
                  "node-packs": unref(installedPacks)
                }, null, 8, ["node-packs"])
              ])) : unref(isNoneInstalled) ? (openBlock(), createBlock(_sfc_main$1b, {
                key: 2,
                size: "md",
                "node-packs": unref(notInstalledPacks),
                "has-conflict": hasConflicts.value,
                "conflict-info": conflictInfo.value
              }, null, 8, ["node-packs", "has-conflict", "conflict-info"])) : createCommentVNode("", true)
            ]),
            _: 1
          }, 8, ["node-packs"]),
          createBaseVNode("div", _hoisted_8$4, [
            createVNode(_sfc_main$j, {
              label: _ctx.$t("g.status")
            }, {
              default: withCtx(() => [
                createVNode(_sfc_main$y, {
                  "status-type": unref(overallStatus),
                  "has-compatibility-issues": hasConflicts.value
                }, null, 8, ["status-type", "has-compatibility-issues"])
              ]),
              _: 1
            }, 8, ["label"]),
            createVNode(_sfc_main$j, {
              label: _ctx.$t("manager.totalNodes"),
              value: totalNodesCount.value
            }, null, 8, ["label", "value"])
          ])
        ])
      ])) : (openBlock(), createElementBlock("div", _hoisted_9$4, toDisplayString(_ctx.$t("manager.infoPanelEmpty")), 1));
    };
  }
});
const _hoisted_1$c = { class: "aspect-7/3 w-full overflow-hidden" };
const _hoisted_2$b = {
  key: 0,
  class: "h-full w-full"
};
const _hoisted_3$7 = ["alt"];
const _hoisted_4$5 = {
  key: 1,
  class: "relative h-full w-full"
};
const _hoisted_5$5 = ["src", "alt"];
const DEFAULT_BANNER = "/assets/images/fallback-gradient-avatar.svg";
const _sfc_main$f = /* @__PURE__ */ defineComponent({
  __name: "PackBanner",
  props: {
    nodePack: {}
  },
  setup(__props) {
    const isImageError = ref(false);
    const showDefaultBanner = computed(() => !__props.nodePack.banner_url && !__props.nodePack.icon);
    const imgSrc = computed(() => __props.nodePack.banner_url || __props.nodePack.icon);
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$c, [
        showDefaultBanner.value ? (openBlock(), createElementBlock("div", _hoisted_2$b, [
          createBaseVNode("img", {
            src: DEFAULT_BANNER,
            alt: _ctx.$t("g.defaultBanner"),
            class: "h-full w-full object-cover"
          }, null, 8, _hoisted_3$7)
        ])) : (openBlock(), createElementBlock("div", _hoisted_4$5, [
          imgSrc.value ? (openBlock(), createElementBlock("div", {
            key: 0,
            class: "absolute inset-0 bg-cover bg-center bg-no-repeat opacity-30",
            style: normalizeStyle({
              backgroundImage: `url(${imgSrc.value})`,
              filter: "blur(10px)"
            })
          }, null, 4)) : createCommentVNode("", true),
          createBaseVNode("img", {
            src: isImageError.value ? DEFAULT_BANNER : imgSrc.value,
            alt: _ctx.nodePack.name + " banner",
            class: normalizeClass(
              isImageError.value ? "relative w-full h-full object-cover z-10" : "relative w-full h-full object-contain z-10"
            ),
            onError: _cache[0] || (_cache[0] = ($event) => isImageError.value = true)
          }, null, 42, _hoisted_5$5)
        ]))
      ]);
    };
  }
});
const _hoisted_1$b = { class: "flex min-h-12 items-center justify-between px-4 py-2 text-xs leading-3 font-medium text-muted" };
const _hoisted_2$a = {
  key: 0,
  class: "flex items-center gap-1.5"
};
const _sfc_main$e = /* @__PURE__ */ defineComponent({
  __name: "PackCardFooter",
  props: {
    nodePack: {}
  },
  setup(__props) {
    const { isPackInstalled } = useComfyManagerStore();
    const isInstalled = computed(() => isPackInstalled(__props.nodePack?.id));
    const isInstalling = inject(IsInstallingKey);
    const { n } = useI18n();
    const formattedDownloads = computed(
      () => __props.nodePack.downloads ? n(__props.nodePack.downloads) : ""
    );
    const { checkNodeCompatibility } = useConflictDetection();
    const conflictInfo = computed(() => {
      if (!__props.nodePack) return [];
      const compatibilityCheck = checkNodeCompatibility(__props.nodePack);
      return compatibilityCheck.conflicts || [];
    });
    const hasConflicts = computed(() => conflictInfo.value.length > 0);
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$b, [
        _ctx.nodePack.downloads ? (openBlock(), createElementBlock("div", _hoisted_2$a, [
          _cache[0] || (_cache[0] = createBaseVNode("i", { class: "pi pi-download text-muted" }, null, -1)),
          createBaseVNode("span", null, toDisplayString(formattedDownloads.value), 1)
        ])) : createCommentVNode("", true),
        !isInstalled.value ? (openBlock(), createBlock(_sfc_main$1b, {
          key: 1,
          "node-packs": [_ctx.nodePack],
          "is-installing": unref(isInstalling),
          "has-conflict": hasConflicts.value,
          "conflict-info": conflictInfo.value
        }, null, 8, ["node-packs", "is-installing", "has-conflict", "conflict-info"])) : (openBlock(), createBlock(_sfc_main$u, {
          key: 2,
          "node-pack": _ctx.nodePack
        }, null, 8, ["node-pack"]))
      ]);
    };
  }
});
const _hoisted_1$a = { class: "h-full w-full px-4 pt-4 pb-3" };
const _hoisted_2$9 = { class: "flex h-full w-full flex-col gap-y-1" };
const _hoisted_3$6 = { class: "truncate overflow-hidden text-sm font-bold text-ellipsis" };
const _hoisted_4$4 = {
  key: 0,
  class: "my-0 mb-1 line-clamp-3 min-h-12 flex-1 overflow-hidden text-xs leading-4 font-medium break-words text-muted"
};
const _hoisted_5$4 = { class: "flex flex-col gap-y-2" };
const _hoisted_6$4 = { class: "flex flex-1 items-center gap-2" };
const _hoisted_7$4 = {
  key: 0,
  class: "p-2 pl-0 text-xs"
};
const _hoisted_8$3 = {
  key: 1,
  class: "flex items-center justify-center gap-1 px-2 py-1 text-xs font-medium text-muted"
};
const _hoisted_9$3 = { class: "flex" };
const _hoisted_10$3 = {
  key: 0,
  class: "max-w-40 truncate text-xs leading-3 font-medium text-muted"
};
const _sfc_main$d = /* @__PURE__ */ defineComponent({
  __name: "PackCard",
  props: {
    nodePack: {},
    isSelected: { type: Boolean, default: false }
  },
  setup(__props) {
    const { d: d2 } = useI18n();
    const colorPaletteStore = useColorPaletteStore();
    const isLightTheme = computed(
      () => colorPaletteStore.completedActivePalette.light_theme
    );
    const { isPackInstalled, isPackEnabled, isPackInstalling } = useComfyManagerStore();
    const isInstalling = computed(() => isPackInstalling(__props.nodePack?.id));
    provide(IsInstallingKey, isInstalling);
    const isInstalled = computed(() => isPackInstalled(__props.nodePack?.id));
    const isDisabled = computed(
      () => isInstalled.value && !isPackEnabled(__props.nodePack?.id)
    );
    const nodesCount = computed(
      () => isMergedNodePack(__props.nodePack) ? __props.nodePack.comfy_nodes?.length : void 0
    );
    const publisherName = computed(() => {
      if (!__props.nodePack) return null;
      const { publisher, author } = __props.nodePack;
      return publisher?.name ?? publisher?.id ?? author;
    });
    const formattedLatestVersionDate = computed(() => {
      if (!__props.nodePack.latest_version?.createdAt) return null;
      return d2(new Date(__props.nodePack.latest_version.createdAt), {
        dateStyle: "medium"
      });
    });
    return (_ctx, _cache) => {
      return openBlock(), createBlock(unref(script$8), {
        class: normalizeClass(["shadow-elevation-3 inline-flex size-full flex-col items-start justify-between overflow-hidden rounded-lg transition-all duration-200", {
          "selected-card": _ctx.isSelected,
          "opacity-60": isDisabled.value
        }]),
        pt: {
          body: { class: "p-0 flex flex-col w-full h-full rounded-lg gap-0" },
          content: { class: "flex-1 flex flex-col rounded-lg min-h-0" },
          title: { class: "w-full h-full rounded-t-lg cursor-pointer" },
          footer: {
            class: "p-0 m-0 flex flex-col gap-0",
            style: {
              borderTop: isLightTheme.value ? "1px solid #f4f4f4" : "1px solid #2C2C2C"
            }
          }
        }
      }, {
        title: withCtx(() => [
          createVNode(_sfc_main$f, { "node-pack": _ctx.nodePack }, null, 8, ["node-pack"])
        ]),
        content: withCtx(() => [
          createBaseVNode("div", _hoisted_1$a, [
            createBaseVNode("div", _hoisted_2$9, [
              createBaseVNode("span", _hoisted_3$6, toDisplayString(_ctx.nodePack.name), 1),
              _ctx.nodePack.description ? (openBlock(), createElementBlock("p", _hoisted_4$4, toDisplayString(_ctx.nodePack.description), 1)) : createCommentVNode("", true),
              createBaseVNode("div", _hoisted_5$4, [
                createBaseVNode("div", _hoisted_6$4, [
                  nodesCount.value ? (openBlock(), createElementBlock("div", _hoisted_7$4, toDisplayString(nodesCount.value) + " " + toDisplayString(_ctx.$t("g.nodes")), 1)) : createCommentVNode("", true),
                  createVNode(_sfc_main$v, {
                    "node-pack": _ctx.nodePack,
                    "is-selected": _ctx.isSelected,
                    fill: false,
                    class: normalizeClass(isInstalling.value ? "pointer-events-none" : "")
                  }, null, 8, ["node-pack", "is-selected", "class"]),
                  formattedLatestVersionDate.value ? (openBlock(), createElementBlock("div", _hoisted_8$3, toDisplayString(formattedLatestVersionDate.value), 1)) : createCommentVNode("", true)
                ]),
                createBaseVNode("div", _hoisted_9$3, [
                  publisherName.value ? (openBlock(), createElementBlock("span", _hoisted_10$3, toDisplayString(publisherName.value), 1)) : createCommentVNode("", true)
                ])
              ])
            ])
          ])
        ]),
        footer: withCtx(() => [
          createVNode(_sfc_main$e, {
            "node-pack": _ctx.nodePack,
            "is-installing": isInstalling.value
          }, null, 8, ["node-pack", "is-installing"])
        ]),
        _: 1
      }, 8, ["class", "pt"]);
    };
  }
});
const PackCard = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["__scopeId", "data-v-d5c78bd4"]]);
const _sfc_main$c = /* @__PURE__ */ defineComponent({
  __name: "PackUpdateButton",
  props: {
    nodePacks: {},
    hasDisabledUpdatePacks: { type: Boolean }
  },
  setup(__props) {
    const isUpdating = ref(false);
    const managerStore = useComfyManagerStore();
    const createPayload = /* @__PURE__ */ __name((updateItem) => {
      return {
        id: updateItem.id,
        version: updateItem.latest_version.version
      };
    }, "createPayload");
    const updatePack = /* @__PURE__ */ __name(async (item) => {
      if (!item.id || !item.latest_version?.version) {
        console.warn("Pack missing required id or version:", item);
        return;
      }
      await managerStore.updatePack.call(createPayload(item));
    }, "updatePack");
    const updateAllPacks = /* @__PURE__ */ __name(async () => {
      if (!__props.nodePacks?.length) {
        console.warn("No packs provided for update");
        return;
      }
      isUpdating.value = true;
      const updatablePacks = __props.nodePacks.filter(
        (pack) => managerStore.isPackInstalled(pack.id)
      );
      if (!updatablePacks.length) {
        isUpdating.value = false;
        return;
      }
      try {
        await Promise.all(updatablePacks.map(updatePack));
        managerStore.updatePack.clear();
      } catch (error) {
        console.error("Pack update failed:", error);
        console.error(
          "Failed packs info:",
          updatablePacks.map((p) => p.id)
        );
      } finally {
        isUpdating.value = false;
      }
    }, "updateAllPacks");
    return (_ctx, _cache) => {
      const _directive_tooltip = resolveDirective("tooltip");
      return withDirectives((openBlock(), createBlock(_sfc_main$2A, {
        variant: "textonly",
        class: "border",
        size: "sm",
        disabled: isUpdating.value,
        onClick: updateAllPacks
      }, {
        default: withCtx(() => [
          isUpdating.value ? (openBlock(), createBlock(DotSpinner, {
            key: 0,
            duration: "1s",
            size: 12
          })) : createCommentVNode("", true),
          createBaseVNode("span", null, toDisplayString(_ctx.$t("manager.updateAll")), 1)
        ]),
        _: 1
      }, 8, ["disabled"])), [
        [
          _directive_tooltip,
          _ctx.hasDisabledUpdatePacks ? _ctx.$t("manager.disabledNodesWontUpdate") : null,
          void 0,
          { top: true }
        ]
      ]);
    };
  }
});
const _hoisted_1$9 = { class: "flex items-center gap-1" };
const _hoisted_2$8 = { class: "text-muted" };
const _sfc_main$b = /* @__PURE__ */ defineComponent({
  __name: "SearchFilterDropdown",
  props: {
    options: {},
    label: {},
    modelValue: {}
  },
  emits: ["update:modelValue"],
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$9, [
        createBaseVNode("span", _hoisted_2$8, toDisplayString(_ctx.label) + ":", 1),
        createVNode(unref(script$G), {
          "model-value": _ctx.modelValue,
          options: _ctx.options,
          "option-label": "label",
          "option-value": "id",
          class: "min-w-[6rem] border-none bg-transparent shadow-none",
          pt: {
            input: { class: "py-0 px-1 border-none" },
            trigger: { class: "hidden" },
            panel: { class: "shadow-md" },
            item: { class: "py-2 px-3 text-sm" }
          },
          "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.$emit("update:modelValue", $event))
        }, null, 8, ["model-value", "options"])
      ]);
    };
  }
});
const useUpdateAvailableNodes = /* @__PURE__ */ __name(() => {
  const comfyManagerStore = useComfyManagerStore();
  const { installedPacks, isLoading, error, startFetchInstalled } = useInstalledPacks();
  const isOutdatedPack = /* @__PURE__ */ __name((pack) => {
    const isInstalled = comfyManagerStore.isPackInstalled(pack?.id);
    if (!isInstalled) return false;
    const installedVersion = comfyManagerStore.getInstalledPackVersion(
      pack.id ?? ""
    );
    const latestVersion = pack.latest_version?.version;
    const isNightlyPack = !!installedVersion && !semverExports.valid(installedVersion);
    if (isNightlyPack || !latestVersion || !installedVersion) {
      return false;
    }
    return semverExports.compare(latestVersion, installedVersion) > 0;
  }, "isOutdatedPack");
  const filterOutdatedPacks = /* @__PURE__ */ __name((packs) => packs.filter(isOutdatedPack), "filterOutdatedPacks");
  const updateAvailableNodePacks = computed(() => {
    if (!installedPacks.value.length) return [];
    return filterOutdatedPacks(installedPacks.value);
  });
  const enabledUpdateAvailableNodePacks = computed(() => {
    return updateAvailableNodePacks.value.filter(
      (pack) => comfyManagerStore.isPackEnabled(pack.id)
    );
  });
  const hasUpdateAvailable = computed(() => {
    return enabledUpdateAvailableNodePacks.value.length > 0;
  });
  const hasDisabledUpdatePacks = computed(() => {
    return updateAvailableNodePacks.value.length > enabledUpdateAvailableNodePacks.value.length;
  });
  onMounted(async () => {
    if (!installedPacks.value.length && !isLoading.value) {
      await startFetchInstalled();
    }
  });
  return {
    updateAvailableNodePacks,
    enabledUpdateAvailableNodePacks,
    hasUpdateAvailable,
    hasDisabledUpdatePacks,
    isLoading,
    error
  };
}, "useUpdateAvailableNodes");
const _hoisted_1$8 = { class: "relative w-full p-6" };
const _hoisted_2$7 = { class: "flex h-12 items-center justify-between gap-1" };
const _hoisted_3$5 = { class: "flex w-5/12 items-center" };
const _hoisted_4$3 = { class: "mt-3 flex text-sm" };
const _hoisted_5$3 = { class: "ml-1 flex gap-6" };
const _hoisted_6$3 = { class: "ml-6 flex items-center gap-4" };
const _hoisted_7$3 = {
  key: 0,
  class: "text-color-secondary"
};
const _sfc_main$a = /* @__PURE__ */ defineComponent({
  __name: "RegistrySearchBar",
  props: /* @__PURE__ */ mergeModels({
    searchResults: {},
    suggestions: {},
    sortOptions: {},
    isMissingTab: { type: Boolean },
    isUpdateAvailableTab: { type: Boolean }
  }, {
    "searchQuery": {},
    "searchQueryModifiers": {},
    "searchMode": { default: "packs" },
    "searchModeModifiers": {},
    "sortField": {
      default: SortableAlgoliaField.Downloads
    },
    "sortFieldModifiers": {}
  }),
  emits: ["update:searchQuery", "update:searchMode", "update:sortField"],
  setup(__props) {
    const searchQuery = useModel(__props, "searchQuery");
    const searchMode = useModel(__props, "searchMode");
    const sortField = useModel(__props, "sortField");
    const { t: t2 } = useI18n();
    const { missingNodePacks, isLoading, error } = useMissingNodes();
    const {
      hasUpdateAvailable,
      enabledUpdateAvailableNodePacks,
      hasDisabledUpdatePacks
    } = useUpdateAvailableNodes();
    const hasResults = computed(
      () => searchQuery.value?.trim() && __props.searchResults?.length
    );
    const availableSortOptions = computed(() => {
      if (!__props.sortOptions) return [];
      return __props.sortOptions.map((field) => ({
        id: field.id,
        label: field.label
      }));
    });
    const filterOptions = [
      { id: "packs", label: t2("manager.filter.nodePack") },
      { id: "nodes", label: t2("g.nodes") }
    ];
    const onOptionSelect = /* @__PURE__ */ __name((event) => {
      searchQuery.value = event.value.query;
    }, "onOptionSelect");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$8, [
        createBaseVNode("div", _hoisted_2$7, [
          createBaseVNode("div", _hoisted_3$5, [
            createVNode(unref(script$H), {
              modelValue: searchQuery.value,
              "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchQuery.value = $event),
              modelModifiers: { lazy: true },
              suggestions: _ctx.suggestions || [],
              placeholder: _ctx.$t("manager.searchPlaceholder"),
              "complete-on-focus": false,
              delay: 8,
              "option-label": "query",
              class: "w-full",
              pt: {
                pcInputText: {
                  root: {
                    autofocus: true,
                    class: "w-full rounded-2xl"
                  }
                },
                loader: {
                  style: "display: none"
                }
              },
              "show-empty-message": false,
              onComplete: unref(stubTrue),
              onOptionSelect
            }, null, 8, ["modelValue", "suggestions", "placeholder", "onComplete"])
          ]),
          _ctx.isMissingTab && unref(missingNodePacks).length > 0 ? (openBlock(), createBlock(_sfc_main$1b, {
            key: 0,
            disabled: unref(isLoading) || !!unref(error),
            "node-packs": unref(missingNodePacks),
            label: _ctx.$t("manager.installAllMissingNodes")
          }, null, 8, ["disabled", "node-packs", "label"])) : createCommentVNode("", true),
          _ctx.isUpdateAvailableTab && unref(hasUpdateAvailable) ? (openBlock(), createBlock(_sfc_main$c, {
            key: 1,
            "node-packs": unref(enabledUpdateAvailableNodePacks),
            "has-disabled-update-packs": unref(hasDisabledUpdatePacks)
          }, null, 8, ["node-packs", "has-disabled-update-packs"])) : createCommentVNode("", true)
        ]),
        createBaseVNode("div", _hoisted_4$3, [
          createBaseVNode("div", _hoisted_5$3, [
            createVNode(_sfc_main$b, {
              "model-value": searchMode.value,
              "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => searchMode.value = $event),
              options: filterOptions,
              label: _ctx.$t("g.filter")
            }, null, 8, ["model-value", "label"]),
            createVNode(_sfc_main$b, {
              "model-value": sortField.value,
              "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => sortField.value = $event),
              options: availableSortOptions.value,
              label: _ctx.$t("g.sort")
            }, null, 8, ["model-value", "options", "label"])
          ]),
          createBaseVNode("div", _hoisted_6$3, [
            hasResults.value ? (openBlock(), createElementBlock("small", _hoisted_7$3, toDisplayString(_ctx.$t("g.resultsCount", { count: _ctx.searchResults?.length || 0 })), 1)) : createCommentVNode("", true)
          ])
        ])
      ]);
    };
  }
});
const _hoisted_1$7 = {
  class: "flex h-full flex-col overflow-hidden rounded-lg shadow-sm",
  "data-virtual-grid-item": ""
};
const _hoisted_2$6 = { class: "flex w-full items-center justify-between px-4 py-3" };
const _hoisted_3$4 = { class: "flex items-center" };
const _hoisted_4$2 = { class: "flex h-6 w-6 items-center justify-center" };
const _hoisted_5$2 = { class: "flex flex-1 p-4" };
const _hoisted_6$2 = { class: "mr-4 shrink-0" };
const _hoisted_7$2 = { class: "flex flex-1 flex-col overflow-hidden" };
const _hoisted_8$2 = { class: "mb-3" };
const _hoisted_9$2 = { class: "flex gap-2" };
const _hoisted_10$2 = { class: "flex w-full items-center justify-between px-5 py-4" };
const _sfc_main$9 = /* @__PURE__ */ defineComponent({
  __name: "PackCardSkeleton",
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$7, [
        createBaseVNode("div", _hoisted_2$6, [
          createBaseVNode("div", _hoisted_3$4, [
            createBaseVNode("div", _hoisted_4$2, [
              createVNode(unref(script$1), {
                shape: "circle",
                width: "1.5rem",
                height: "1.5rem"
              })
            ]),
            createVNode(unref(script$1), {
              width: "5rem",
              height: "1rem",
              class: "ml-2"
            })
          ]),
          createVNode(unref(script$1), {
            width: "4rem",
            height: "1.75rem",
            "border-radius": "0.75rem"
          })
        ]),
        createBaseVNode("div", _hoisted_5$2, [
          createBaseVNode("div", _hoisted_6$2, [
            createVNode(unref(script$1), {
              width: "4rem",
              height: "4rem",
              "border-radius": "0.5rem"
            })
          ]),
          createBaseVNode("div", _hoisted_7$2, [
            createVNode(unref(script$1), {
              width: "80%",
              height: "1rem",
              class: "mb-2"
            }),
            createBaseVNode("div", _hoisted_8$2, [
              createVNode(unref(script$1), {
                width: "100%",
                height: "0.75rem",
                class: "mb-1"
              }),
              createVNode(unref(script$1), {
                width: "95%",
                height: "0.75rem",
                class: "mb-1"
              }),
              createVNode(unref(script$1), {
                width: "90%",
                height: "0.75rem"
              })
            ]),
            createBaseVNode("div", _hoisted_9$2, [
              createVNode(unref(script$1), {
                width: "4rem",
                height: "1.5rem",
                "border-radius": "0.75rem"
              }),
              createVNode(unref(script$1), {
                width: "5rem",
                height: "1.5rem",
                "border-radius": "0.75rem"
              })
            ])
          ])
        ]),
        createBaseVNode("div", _hoisted_10$2, [
          createVNode(unref(script$1), {
            width: "4rem",
            height: "0.8rem"
          }),
          createVNode(unref(script$1), {
            width: "6rem",
            height: "0.8rem"
          })
        ])
      ]);
    };
  }
});
const _sfc_main$8 = /* @__PURE__ */ defineComponent({
  __name: "GridSkeleton",
  props: {
    skeletonCardCount: { default: 12 },
    gridStyle: {}
  },
  setup(__props) {
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        style: normalizeStyle(_ctx.gridStyle)
      }, [
        (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.skeletonCardCount, (n) => {
          return openBlock(), createBlock(_sfc_main$9, { key: n });
        }), 128))
      ], 4);
    };
  }
});
const STORAGE_KEY = "Comfy.Manager.UI.State";
const useManagerStatePersistence = /* @__PURE__ */ __name(() => {
  const loadStoredState = /* @__PURE__ */ __name(() => {
    try {
      const stored = localStorage.getItem(STORAGE_KEY);
      if (stored) {
        return JSON.parse(stored);
      }
    } catch (e2) {
      console.error("Failed to load manager UI state:", e2);
    }
    return {
      selectedTabId: ManagerTab.All,
      searchQuery: "",
      searchMode: "packs",
      sortField: SortableAlgoliaField.Downloads
    };
  }, "loadStoredState");
  const persistState = /* @__PURE__ */ __name((state2) => {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(state2));
  }, "persistState");
  const reset = /* @__PURE__ */ __name(() => {
    persistState({
      selectedTabId: ManagerTab.All,
      searchQuery: "",
      searchMode: "packs",
      sortField: SortableAlgoliaField.Downloads
    });
  }, "reset");
  return {
    loadStoredState,
    persistState,
    reset
  };
}, "useManagerStatePersistence");
const SEARCH_CACHE_MAX_SIZE = 64;
const DEFAULT_PAGE_SIZE = 64;
const MIN_CHARS_FOR_SUGGESTIONS_ALGOLIA = 2;
const RETRIEVE_ATTRIBUTES = [
  "comfy_nodes",
  "name",
  "description",
  "latest_version",
  "status",
  "publisher_id",
  "total_install",
  "create_time",
  "update_time",
  "license",
  "repository_url",
  "latest_version_status",
  "comfy_node_extract_status",
  "id",
  "icon_url",
  "github_stars",
  "supported_os",
  "supported_comfyui_version",
  "supported_comfyui_frontend_version",
  "supported_accelerators",
  "banner_url"
];
const searchPacksCache = new QuickLRU({
  maxSize: SEARCH_CACHE_MAX_SIZE
});
const toRegistryLatestVersion = /* @__PURE__ */ __name((algoliaNode) => {
  return {
    version: algoliaNode.latest_version,
    createdAt: algoliaNode.update_time,
    status: algoliaNode.latest_version_status,
    comfy_node_extract_status: algoliaNode.comfy_node_extract_status ?? void 0
  };
}, "toRegistryLatestVersion");
const toRegistryPublisher = /* @__PURE__ */ __name((algoliaNode) => {
  return {
    id: algoliaNode.publisher_id,
    name: algoliaNode.publisher_id
  };
}, "toRegistryPublisher");
const toRegistryPack = memoize(
  (algoliaNode) => {
    return {
      id: algoliaNode.id ?? algoliaNode.objectID,
      name: algoliaNode.name,
      description: algoliaNode.description,
      repository: algoliaNode.repository_url,
      license: algoliaNode.license,
      downloads: algoliaNode.total_install,
      status: algoliaNode.status,
      icon: algoliaNode.icon_url,
      latest_version: toRegistryLatestVersion(algoliaNode),
      publisher: toRegistryPublisher(algoliaNode),
      created_at: algoliaNode.create_time,
      category: algoliaNode.category,
      author: algoliaNode.author,
      tags: algoliaNode.tags,
      github_stars: algoliaNode.github_stars,
      supported_os: algoliaNode.supported_os,
      supported_comfyui_version: algoliaNode.supported_comfyui_version,
      supported_comfyui_frontend_version: algoliaNode.supported_comfyui_frontend_version,
      supported_accelerators: algoliaNode.supported_accelerators,
      banner_url: algoliaNode.banner_url,
      comfy_nodes: algoliaNode.comfy_nodes
    };
  },
  (algoliaNode) => algoliaNode.id
);
const useAlgoliaSearchProvider = /* @__PURE__ */ __name(() => {
  const searchClient = liteClient();
  const searchPacksInternal = /* @__PURE__ */ __name(async (query, params) => {
    const { pageSize, pageNumber } = params;
    const rest = omit(params, ["pageSize", "pageNumber"]);
    const requests = [
      {
        query,
        indexName: "nodes_index",
        attributesToRetrieve: RETRIEVE_ATTRIBUTES,
        ...rest,
        hitsPerPage: pageSize,
        page: pageNumber
      }
    ];
    const shouldQuerySuggestions = query.length >= MIN_CHARS_FOR_SUGGESTIONS_ALGOLIA;
    if (shouldQuerySuggestions) {
      requests.push({
        indexName: "nodes_index_query_suggestions",
        query
      });
    }
    const { results } = await searchClient.search({
      requests,
      strategy: "none"
    });
    const [nodePacks, querySuggestions = { hits: [] }] = results;
    const registryPacks = nodePacks.hits.map(toRegistryPack);
    const suggestions = querySuggestions.hits.map((suggestion) => ({
      query: suggestion.query,
      popularity: suggestion.popularity
    }));
    return {
      nodePacks: registryPacks,
      querySuggestions: suggestions
    };
  }, "searchPacksInternal");
  const searchPacks = /* @__PURE__ */ __name(async (query, params) => {
    const cacheKey = paramsToCacheKey({ query, ...params });
    const cachedResult = searchPacksCache.get(cacheKey);
    if (cachedResult !== void 0) return cachedResult;
    const result = await searchPacksInternal(query, params);
    searchPacksCache.set(cacheKey, result);
    return result;
  }, "searchPacks");
  const clearSearchCache = /* @__PURE__ */ __name(() => {
    searchPacksCache.clear();
  }, "clearSearchCache");
  const getSortValue = /* @__PURE__ */ __name((pack, sortField) => {
    switch (sortField) {
      case SortableAlgoliaField.Downloads:
        return pack.downloads ?? 0;
      case SortableAlgoliaField.Created: {
        const createTime = pack.created_at;
        return createTime ? new Date(createTime).getTime() : 0;
      }
      case SortableAlgoliaField.Updated:
        return pack.latest_version?.createdAt ? new Date(pack.latest_version.createdAt).getTime() : 0;
      case SortableAlgoliaField.Publisher:
        return pack.publisher?.name ?? "";
      case SortableAlgoliaField.Name:
        return pack.name ?? "";
      default:
        return 0;
    }
  }, "getSortValue");
  const getSortableFields = /* @__PURE__ */ __name(() => {
    return [
      {
        id: SortableAlgoliaField.Downloads,
        label: "Downloads",
        direction: "desc"
      },
      { id: SortableAlgoliaField.Created, label: "Created", direction: "desc" },
      { id: SortableAlgoliaField.Updated, label: "Updated", direction: "desc" },
      {
        id: SortableAlgoliaField.Publisher,
        label: "Publisher",
        direction: "asc"
      },
      { id: SortableAlgoliaField.Name, label: "Name", direction: "asc" }
    ];
  }, "getSortableFields");
  return {
    searchPacks,
    clearSearchCache,
    getSortValue,
    getSortableFields
  };
}, "useAlgoliaSearchProvider");
const useComfyRegistrySearchProvider = /* @__PURE__ */ __name(() => {
  const registryStore = useComfyRegistryStore();
  const searchPacks = /* @__PURE__ */ __name(async (query, params) => {
    const { pageSize, pageNumber, restrictSearchableAttributes } = params;
    const isNodeSearch = restrictSearchableAttributes?.includes("comfy_nodes");
    const searchParams = {
      search: isNodeSearch ? void 0 : query,
      comfy_node_search: isNodeSearch ? query : void 0,
      limit: pageSize,
      page: pageNumber + 1
      // Registry API uses 1-based pagination
    };
    const searchResult = await registryStore.search.call(searchParams);
    if (!searchResult || !searchResult.nodes) {
      return {
        nodePacks: [],
        querySuggestions: []
      };
    }
    return {
      nodePacks: searchResult.nodes,
      querySuggestions: []
      // Registry doesn't support query suggestions
    };
  }, "searchPacks");
  const clearSearchCache = /* @__PURE__ */ __name(() => {
    registryStore.search.clear();
  }, "clearSearchCache");
  const getSortValue = /* @__PURE__ */ __name((pack, sortField) => {
    switch (sortField) {
      case "downloads":
        return pack.downloads ?? 0;
      case "name":
        return pack.name ?? "";
      case "publisher":
        return pack.publisher?.name ?? "";
      case "updated":
        return pack.latest_version?.createdAt ? new Date(pack.latest_version.createdAt).getTime() : 0;
      default:
        return 0;
    }
  }, "getSortValue");
  const getSortableFields = /* @__PURE__ */ __name(() => {
    return [
      { id: "downloads", label: "Downloads", direction: "desc" },
      { id: "name", label: "Name", direction: "asc" },
      { id: "publisher", label: "Publisher", direction: "asc" },
      { id: "updated", label: "Updated", direction: "desc" }
    ];
  }, "getSortableFields");
  return {
    searchPacks,
    clearSearchCache,
    getSortValue,
    getSortableFields
  };
}, "useComfyRegistrySearchProvider");
const CIRCUIT_BREAKER_THRESHOLD = 3;
const CIRCUIT_BREAKER_TIMEOUT = 6e4;
const useRegistrySearchGateway = /* @__PURE__ */ __name(() => {
  const providers = [];
  let activeProviderIndex = 0;
  try {
    providers.push({
      provider: useAlgoliaSearchProvider(),
      name: "Algolia",
      isHealthy: true,
      consecutiveFailures: 0
    });
  } catch (error) {
    console.warn("Failed to initialize Algolia provider:", error);
  }
  providers.push({
    provider: useComfyRegistrySearchProvider(),
    name: "ComfyRegistry",
    isHealthy: true,
    consecutiveFailures: 0
  });
  const isCircuitClosed = /* @__PURE__ */ __name((providerState) => {
    if (providerState.consecutiveFailures < CIRCUIT_BREAKER_THRESHOLD) {
      return true;
    }
    if (providerState.lastAttempt) {
      const timeSinceLastAttempt = Date.now() - providerState.lastAttempt.getTime();
      if (timeSinceLastAttempt > CIRCUIT_BREAKER_TIMEOUT) {
        return true;
      }
    }
    return false;
  }, "isCircuitClosed");
  const recordSuccess = /* @__PURE__ */ __name((providerState) => {
    providerState.isHealthy = true;
    providerState.consecutiveFailures = 0;
    providerState.lastError = void 0;
  }, "recordSuccess");
  const recordFailure = /* @__PURE__ */ __name((providerState, error) => {
    providerState.consecutiveFailures++;
    providerState.lastError = error;
    providerState.lastAttempt = /* @__PURE__ */ new Date();
    if (providerState.consecutiveFailures >= CIRCUIT_BREAKER_THRESHOLD) {
      providerState.isHealthy = false;
      console.warn(
        `${providerState.name} provider circuit breaker opened after ${providerState.consecutiveFailures} failures`
      );
    }
  }, "recordFailure");
  const getActiveProvider = /* @__PURE__ */ __name(() => {
    const currentProvider = providers[activeProviderIndex];
    if (currentProvider && isCircuitClosed(currentProvider)) {
      return currentProvider.provider;
    }
    for (let i = 0; i < providers.length; i++) {
      const providerState = providers[i];
      if (isCircuitClosed(providerState)) {
        activeProviderIndex = i;
        return providerState.provider;
      }
    }
    throw new Error("No available search providers");
  }, "getActiveProvider");
  const updateActiveProviderOnFailure = /* @__PURE__ */ __name(() => {
    if (activeProviderIndex < providers.length - 1) {
      activeProviderIndex++;
    }
  }, "updateActiveProviderOnFailure");
  const searchPacks = /* @__PURE__ */ __name(async (query, params) => {
    let lastError = null;
    for (let attempts = 0; attempts < providers.length; attempts++) {
      try {
        const provider = getActiveProvider();
        const providerState = providers[activeProviderIndex];
        const result = await provider.searchPacks(query, params);
        recordSuccess(providerState);
        return result;
      } catch (error) {
        lastError = error;
        const providerState = providers[activeProviderIndex];
        recordFailure(providerState, lastError);
        console.warn(
          `${providerState.name} search provider failed (${providerState.consecutiveFailures} failures):`,
          error
        );
        updateActiveProviderOnFailure();
      }
    }
    throw new Error(
      `All search providers failed. Last error: ${lastError?.message || "Unknown error"}`
    );
  }, "searchPacks");
  const clearSearchCache = /* @__PURE__ */ __name(() => {
    for (const providerState of providers) {
      try {
        providerState.provider.clearSearchCache();
      } catch (error) {
        console.warn(
          `Failed to clear cache for ${providerState.name} provider:`,
          error
        );
      }
    }
  }, "clearSearchCache");
  const getSortValue = /* @__PURE__ */ __name((pack, sortField) => {
    return getActiveProvider().getSortValue(pack, sortField);
  }, "getSortValue");
  const getSortableFields = /* @__PURE__ */ __name(() => {
    return getActiveProvider().getSortableFields();
  }, "getSortableFields");
  return {
    searchPacks,
    clearSearchCache,
    getSortValue,
    getSortableFields
  };
}, "useRegistrySearchGateway");
const SEARCH_DEBOUNCE_TIME = 320;
const DEFAULT_SORT_FIELD = SortableAlgoliaField.Downloads;
function useRegistrySearch(options = {}) {
  const {
    initialSortField = DEFAULT_SORT_FIELD,
    initialSearchMode = "packs",
    initialSearchQuery = "",
    initialPageNumber = 0
  } = options;
  const isLoading = ref(false);
  const sortField = ref(initialSortField);
  const searchMode = ref(initialSearchMode);
  const pageSize = ref(DEFAULT_PAGE_SIZE);
  const pageNumber = ref(initialPageNumber);
  const searchQuery = ref(initialSearchQuery);
  const searchResults = ref([]);
  const suggestions = ref([]);
  const searchAttributes = computed(
    () => searchMode.value === "nodes" ? ["comfy_nodes"] : ["name", "description"]
  );
  const searchGateway = useRegistrySearchGateway();
  const { searchPacks, clearSearchCache, getSortValue, getSortableFields } = searchGateway;
  const updateSearchResults = /* @__PURE__ */ __name(async (options2) => {
    isLoading.value = true;
    if (!options2.append) {
      pageNumber.value = 0;
    }
    const { nodePacks, querySuggestions } = await searchPacks(
      searchQuery.value,
      {
        pageSize: pageSize.value,
        pageNumber: pageNumber.value,
        restrictSearchableAttributes: searchAttributes.value
      }
    );
    let sortedPacks = nodePacks;
    if (sortField.value && sortField.value !== DEFAULT_SORT_FIELD) {
      const sortableFields = getSortableFields();
      const fieldConfig = sortableFields.find((f) => f.id === sortField.value);
      const direction = fieldConfig?.direction || "desc";
      sortedPacks = orderBy(
        nodePacks,
        [(pack) => getSortValue(pack, sortField.value)],
        [direction]
      );
    }
    if (options2.append && searchResults.value?.length) {
      searchResults.value = searchResults.value.concat(sortedPacks);
    } else {
      searchResults.value = sortedPacks;
    }
    suggestions.value = querySuggestions;
    isLoading.value = false;
  }, "updateSearchResults");
  const onQueryChange = /* @__PURE__ */ __name(() => updateSearchResults({ append: false }), "onQueryChange");
  const onPageChange = /* @__PURE__ */ __name(() => updateSearchResults({ append: true }), "onPageChange");
  watch([sortField, searchMode], onQueryChange);
  watch(pageNumber, onPageChange);
  watchDebounced(searchQuery, onQueryChange, {
    debounce: SEARCH_DEBOUNCE_TIME,
    immediate: true
  });
  const sortOptions = computed(() => {
    return getSortableFields();
  });
  return {
    isLoading,
    pageNumber,
    pageSize,
    sortField,
    searchMode,
    searchQuery,
    suggestions,
    searchResults,
    sortOptions,
    clearCache: clearSearchCache
  };
}
__name(useRegistrySearch, "useRegistrySearch");
const _hoisted_1$6 = ["aria-label"];
const _hoisted_2$5 = { class: "relative flex flex-1 overflow-hidden" };
const _hoisted_3$3 = { class: "flex h-full flex-col px-6" };
const _hoisted_4$1 = {
  key: 0,
  class: "relative mt-3 mb-4 flex items-center gap-6 rounded-lg bg-yellow-500/20 p-4"
};
const _hoisted_5$1 = { class: "flex flex-1 flex-col gap-2" };
const _hoisted_6$1 = { class: "m-0 text-sm font-bold" };
const _hoisted_7$1 = { class: "m-0 text-xs" };
const _hoisted_8$1 = { class: "flex-1 overflow-auto" };
const _hoisted_9$1 = {
  key: 0,
  class: "h-full scrollbar-hide w-full overflow-auto"
};
const _hoisted_10$1 = { class: "z-20 flex w-[clamp(250px,33%,306px)] border-l-0" };
const _hoisted_11$1 = { class: "isolate flex w-full flex-col" };
const _sfc_main$7 = /* @__PURE__ */ defineComponent({
  __name: "ManagerDialogContent",
  props: {
    initialTab: {}
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const { buildDocsUrl } = useExternalLink();
    const comfyManagerStore = useComfyManagerStore();
    const { getPackById } = useComfyRegistryStore();
    const conflictAcknowledgment = useConflictAcknowledgment();
    const persistedState = useManagerStatePersistence();
    const initialState = persistedState.loadStoredState();
    const GRID_STYLE = {
      display: "grid",
      gridTemplateColumns: "repeat(auto-fill, minmax(17rem, 1fr))",
      padding: "0.5rem",
      gap: "1.5rem"
    };
    const {
      isSmallScreen,
      isOpen: isSideNavOpen,
      toggle: toggleSideNav
    } = useResponsiveCollapse();
    const {
      shouldShowManagerBanner,
      dismissWarningBanner,
      dismissRedDotNotification
    } = conflictAcknowledgment;
    const tabs = ref([
      { id: ManagerTab.All, label: t2("g.all"), icon: "pi-list" },
      { id: ManagerTab.Installed, label: t2("g.installed"), icon: "pi-box" },
      {
        id: ManagerTab.Workflow,
        label: t2("manager.inWorkflow"),
        icon: "pi-folder"
      },
      {
        id: ManagerTab.Missing,
        label: t2("g.missing"),
        icon: "pi-exclamation-circle"
      },
      {
        id: ManagerTab.UpdateAvailable,
        label: t2("g.updateAvailable"),
        icon: "pi-sync"
      }
    ]);
    const initialTabId = __props.initialTab ?? initialState.selectedTabId;
    const selectedTab = ref(
      tabs.value.find((tab) => tab.id === initialTabId) || tabs.value[0]
    );
    const {
      searchQuery,
      pageNumber,
      isLoading: isSearchLoading,
      searchResults,
      searchMode,
      sortField,
      suggestions,
      sortOptions
    } = useRegistrySearch({
      initialSortField: initialState.sortField,
      initialSearchMode: initialState.searchMode,
      initialSearchQuery: initialState.searchQuery
    });
    pageNumber.value = 0;
    const onApproachEnd = /* @__PURE__ */ __name(() => {
      pageNumber.value++;
    }, "onApproachEnd");
    const isInitialLoad = computed(
      () => searchResults.value.length === 0 && searchQuery.value === ""
    );
    const isEmptySearch = computed(() => searchQuery.value === "");
    const displayPacks = ref([]);
    const {
      startFetchInstalled,
      filterInstalledPack,
      installedPacks,
      isLoading: isLoadingInstalled,
      isReady: installedPacksReady
    } = useInstalledPacks();
    const {
      startFetchWorkflowPacks,
      filterWorkflowPack,
      workflowPacks,
      isLoading: isLoadingWorkflow,
      isReady: workflowPacksReady
    } = useWorkflowPacks();
    const filterMissingPacks = /* @__PURE__ */ __name((packs) => packs.filter((pack) => !comfyManagerStore.isPackInstalled(pack.id)), "filterMissingPacks");
    const isUpdateAvailableTab = computed(
      () => selectedTab.value?.id === ManagerTab.UpdateAvailable
    );
    const isInstalledTab = computed(
      () => selectedTab.value?.id === ManagerTab.Installed
    );
    const isMissingTab = computed(
      () => selectedTab.value?.id === ManagerTab.Missing
    );
    const isWorkflowTab = computed(
      () => selectedTab.value?.id === ManagerTab.Workflow
    );
    const isAllTab = computed(() => selectedTab.value?.id === ManagerTab.All);
    const isOutdatedPack = /* @__PURE__ */ __name((pack) => {
      const { isUpdateAvailable } = usePackUpdateStatus(pack);
      return isUpdateAvailable.value === true;
    }, "isOutdatedPack");
    const filterOutdatedPacks = /* @__PURE__ */ __name((packs) => packs.filter(isOutdatedPack), "filterOutdatedPacks");
    watch(
      [isUpdateAvailableTab, installedPacks],
      async () => {
        if (!isUpdateAvailableTab.value) return;
        if (!isEmptySearch.value) {
          displayPacks.value = filterOutdatedPacks(installedPacks.value);
        } else if (!installedPacks.value.length && !installedPacksReady.value && !isLoadingInstalled.value) {
          await startFetchInstalled();
        } else {
          displayPacks.value = filterOutdatedPacks(installedPacks.value);
        }
      },
      { immediate: true }
    );
    watch(
      [isInstalledTab, installedPacks],
      async () => {
        if (!isInstalledTab.value) return;
        if (!isEmptySearch.value) {
          displayPacks.value = filterInstalledPack(searchResults.value);
        } else if (!installedPacks.value.length && !installedPacksReady.value && !isLoadingInstalled.value) {
          await startFetchInstalled();
        } else {
          displayPacks.value = installedPacks.value;
        }
      },
      { immediate: true }
    );
    watch(
      [isMissingTab, isWorkflowTab, workflowPacks, installedPacks],
      async () => {
        if (!isWorkflowTab.value && !isMissingTab.value) return;
        if (!isEmptySearch.value) {
          displayPacks.value = isMissingTab.value ? filterMissingPacks(filterWorkflowPack(searchResults.value)) : filterWorkflowPack(searchResults.value);
        } else if (!workflowPacks.value.length && !isLoadingWorkflow.value && !workflowPacksReady.value) {
          await startFetchWorkflowPacks();
          if (isMissingTab.value) {
            await startFetchInstalled();
          }
        } else {
          displayPacks.value = isMissingTab.value ? filterMissingPacks(workflowPacks.value) : workflowPacks.value;
        }
      },
      { immediate: true }
    );
    watch([isAllTab, searchResults], () => {
      if (!isAllTab.value) return;
      displayPacks.value = searchResults.value;
    });
    const onClickWarningLink = /* @__PURE__ */ __name(() => {
      window.open(
        buildDocsUrl("/troubleshooting/custom-node-issues", {
          includeLocale: true
        }),
        "_blank"
      );
    }, "onClickWarningLink");
    const onResultsChange = /* @__PURE__ */ __name(() => {
      switch (selectedTab.value?.id) {
        case ManagerTab.Installed:
          displayPacks.value = isEmptySearch.value ? installedPacks.value : filterInstalledPack(searchResults.value);
          break;
        case ManagerTab.Workflow:
          displayPacks.value = isEmptySearch.value ? workflowPacks.value : filterWorkflowPack(searchResults.value);
          break;
        case ManagerTab.Missing:
          if (!isEmptySearch.value) {
            displayPacks.value = filterMissingPacks(
              filterWorkflowPack(searchResults.value)
            );
          }
          break;
        case ManagerTab.UpdateAvailable:
          displayPacks.value = isEmptySearch.value ? filterOutdatedPacks(installedPacks.value) : filterOutdatedPacks(searchResults.value);
          break;
        default:
          displayPacks.value = searchResults.value;
      }
    }, "onResultsChange");
    watch(searchResults, onResultsChange, { flush: "post" });
    watch(() => comfyManagerStore.installedPacksIds, onResultsChange);
    const isLoading = computed(() => {
      if (isSearchLoading.value) return searchResults.value.length === 0;
      if (selectedTab.value?.id === ManagerTab.Installed) {
        return isLoadingInstalled.value;
      }
      if (selectedTab.value?.id === ManagerTab.Workflow || selectedTab.value?.id === ManagerTab.Missing) {
        return isLoadingWorkflow.value;
      }
      return isInitialLoad.value;
    });
    const resultsWithKeys = computed(
      () => displayPacks.value.map((item) => ({
        ...item,
        key: item.id || item.name
      }))
    );
    const selectedNodePacks = ref([]);
    const selectedNodePack = computed(
      () => selectedNodePacks.value.length === 1 ? selectedNodePacks.value[0] : null
    );
    const getLoadingCount = /* @__PURE__ */ __name(() => {
      switch (selectedTab.value?.id) {
        case ManagerTab.Installed:
          return comfyManagerStore.installedPacksIds?.size;
        case ManagerTab.Workflow:
          return workflowPacks.value?.length;
        case ManagerTab.Missing:
          return workflowPacks.value?.filter?.(
            (pack) => !comfyManagerStore.isPackInstalled(pack.id)
          )?.length;
        default:
          return searchResults.value.length;
      }
    }, "getLoadingCount");
    const skeletonCardCount = computed(() => {
      const loadingCount = getLoadingCount();
      if (loadingCount) return loadingCount;
      return isSmallScreen.value ? 12 : 16;
    });
    const selectNodePack = /* @__PURE__ */ __name((nodePack, event) => {
      if (event.shiftKey || event.ctrlKey || event.metaKey) {
        const index2 = selectedNodePacks.value.findIndex(
          (pack) => pack.id === nodePack.id
        );
        if (index2 === -1) {
          selectedNodePacks.value.push(nodePack);
        } else {
          selectedNodePacks.value.splice(index2, 1);
        }
      } else {
        selectedNodePacks.value = [nodePack];
      }
    }, "selectNodePack");
    const unSelectItems = /* @__PURE__ */ __name(() => {
      selectedNodePacks.value = [];
    }, "unSelectItems");
    const handleGridContainerClick = /* @__PURE__ */ __name((event) => {
      const targetElement = event.target;
      if (targetElement && !targetElement.closest("[data-virtual-grid-item]")) {
        unSelectItems();
      }
    }, "handleGridContainerClick");
    const hasMultipleSelections = computed(() => selectedNodePacks.value.length > 1);
    const lastFetchedPackId = ref(null);
    whenever(selectedNodePack, async () => {
      getPackById.cancel();
      const pack = selectedNodePack.value;
      if (!pack?.id) return;
      if (hasMultipleSelections.value) return;
      if (lastFetchedPackId.value === pack.id) return;
      const data = await getPackById.call(pack.id);
      if (data?.id === pack.id) {
        lastFetchedPackId.value = pack.id;
        const mergedPack = merge({}, pack, data);
        const packIndex = selectedNodePacks.value.findIndex(
          (p) => p.id === mergedPack.id
        );
        if (packIndex !== -1) {
          selectedNodePacks.value.splice(packIndex, 1, mergedPack);
        }
        const idx = displayPacks.value.findIndex((p) => p.id === mergedPack.id);
        if (idx !== -1) {
          displayPacks.value.splice(idx, 1, mergedPack);
        }
      }
    });
    let gridContainer = null;
    onMounted(() => {
      gridContainer = document.getElementById("results-grid");
    });
    watch([searchQuery, selectedTab], () => {
      gridContainer ??= document.getElementById("results-grid");
      if (gridContainer) {
        pageNumber.value = 0;
        gridContainer.scrollTop = 0;
      }
    });
    watchEffect(() => {
      dismissRedDotNotification();
    });
    onBeforeUnmount(() => {
      persistedState.persistState({
        selectedTabId: selectedTab.value?.id,
        searchQuery: searchQuery.value,
        searchMode: searchMode.value,
        sortField: sortField.value
      });
    });
    onUnmounted(() => {
      getPackById.cancel();
    });
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", {
        class: "mx-auto flex h-full flex-col overflow-hidden",
        "aria-label": _ctx.$t("manager.title")
      }, [
        createVNode(ContentDivider, { width: 0.3 }),
        unref(isSmallScreen) ? (openBlock(), createBlock(_sfc_main$2A, {
          key: 0,
          variant: "secondary",
          size: "icon",
          class: normalizeClass(
            unref(cn)(
              "absolute top-1/2 z-10 -translate-y-1/2",
              unref(isSideNavOpen) ? "left-[12rem]" : "left-2"
            )
          ),
          onClick: unref(toggleSideNav)
        }, {
          default: withCtx(() => [
            createBaseVNode("i", {
              class: normalizeClass(unref(isSideNavOpen) ? "pi pi-chevron-left" : "pi pi-chevron-right")
            }, null, 2)
          ]),
          _: 1
        }, 8, ["class", "onClick"])) : createCommentVNode("", true),
        createBaseVNode("div", _hoisted_2$5, [
          unref(isSideNavOpen) ? (openBlock(), createBlock(_sfc_main$z, {
            key: 0,
            "selected-tab": selectedTab.value,
            "onUpdate:selectedTab": _cache[0] || (_cache[0] = ($event) => selectedTab.value = $event),
            tabs: tabs.value
          }, null, 8, ["selected-tab", "tabs"])) : createCommentVNode("", true),
          createBaseVNode("div", {
            class: normalizeClass(["flex-1 overflow-auto bg-base-background", {
              "transition-all duration-300": unref(isSmallScreen)
            }])
          }, [
            createBaseVNode("div", _hoisted_3$3, [
              unref(shouldShowManagerBanner) ? (openBlock(), createElementBlock("div", _hoisted_4$1, [
                _cache[5] || (_cache[5] = createBaseVNode("i", { class: "pi pi-exclamation-triangle text-lg text-yellow-600" }, null, -1)),
                createBaseVNode("div", _hoisted_5$1, [
                  createBaseVNode("p", _hoisted_6$1, toDisplayString(_ctx.$t("manager.conflicts.warningBanner.title")), 1),
                  createBaseVNode("p", _hoisted_7$1, toDisplayString(_ctx.$t("manager.conflicts.warningBanner.message")), 1),
                  createBaseVNode("p", {
                    class: "m-0 cursor-pointer text-sm font-bold",
                    onClick: onClickWarningLink
                  }, toDisplayString(_ctx.$t("manager.conflicts.warningBanner.button")), 1)
                ]),
                createVNode(_sfc_main$2A, {
                  class: "absolute top-0 right-0",
                  variant: "textonly",
                  size: "icon",
                  onClick: unref(dismissWarningBanner)
                }, {
                  default: withCtx(() => _cache[4] || (_cache[4] = [
                    createBaseVNode("i", { class: "pi pi-times text-xs text-base-foreground" }, null, -1)
                  ])),
                  _: 1
                }, 8, ["onClick"])
              ])) : createCommentVNode("", true),
              createVNode(_sfc_main$a, {
                "search-query": unref(searchQuery),
                "onUpdate:searchQuery": _cache[1] || (_cache[1] = ($event) => isRef(searchQuery) ? searchQuery.value = $event : null),
                "search-mode": unref(searchMode),
                "onUpdate:searchMode": _cache[2] || (_cache[2] = ($event) => isRef(searchMode) ? searchMode.value = $event : null),
                "sort-field": unref(sortField),
                "onUpdate:sortField": _cache[3] || (_cache[3] = ($event) => isRef(sortField) ? sortField.value = $event : null),
                "search-results": unref(searchResults),
                suggestions: unref(suggestions),
                "is-missing-tab": isMissingTab.value,
                "sort-options": unref(sortOptions),
                "is-update-available-tab": isUpdateAvailableTab.value
              }, null, 8, ["search-query", "search-mode", "sort-field", "search-results", "suggestions", "is-missing-tab", "sort-options", "is-update-available-tab"]),
              createBaseVNode("div", _hoisted_8$1, [
                isLoading.value ? (openBlock(), createElementBlock("div", _hoisted_9$1, [
                  createVNode(_sfc_main$8, {
                    "grid-style": GRID_STYLE,
                    "skeleton-card-count": skeletonCardCount.value
                  }, null, 8, ["skeleton-card-count"])
                ])) : unref(searchResults).length === 0 ? (openBlock(), createBlock(NoResultsPlaceholder, {
                  key: 1,
                  title: unref(comfyManagerStore).error ? _ctx.$t("manager.errorConnecting") : _ctx.$t("manager.noResultsFound"),
                  message: unref(comfyManagerStore).error ? _ctx.$t("manager.tryAgainLater") : _ctx.$t("manager.tryDifferentSearch")
                }, null, 8, ["title", "message"])) : (openBlock(), createElementBlock("div", {
                  key: 2,
                  class: "h-full",
                  onClick: handleGridContainerClick
                }, [
                  createVNode(VirtualGrid, {
                    id: "results-grid",
                    items: resultsWithKeys.value,
                    "buffer-rows": 4,
                    "grid-style": GRID_STYLE,
                    onApproachEnd
                  }, {
                    item: withCtx(({ item }) => [
                      createVNode(PackCard, {
                        "node-pack": item,
                        "is-selected": selectedNodePacks.value.some((pack) => pack.id === item.id),
                        onClick: withModifiers(
                          (event) => selectNodePack(item, event),
                          ["stop"]
                        )
                      }, null, 8, ["node-pack", "is-selected", "onClick"])
                    ]),
                    _: 1
                  }, 8, ["items"])
                ]))
              ])
            ])
          ], 2),
          createBaseVNode("div", _hoisted_10$1, [
            createVNode(ContentDivider, {
              orientation: "vertical",
              width: 0.2
            }),
            createBaseVNode("div", _hoisted_11$1, [
              !hasMultipleSelections.value && selectedNodePack.value ? (openBlock(), createBlock(_sfc_main$i, {
                key: 0,
                "node-pack": selectedNodePack.value
              }, null, 8, ["node-pack"])) : (openBlock(), createBlock(_sfc_main$g, {
                key: 1,
                "node-packs": selectedNodePacks.value
              }, null, 8, ["node-packs"]))
            ])
          ])
        ])
      ], 8, _hoisted_1$6);
    };
  }
});
const _sfc_main$6 = {};
const _hoisted_1$5 = { class: "w-full" };
const _hoisted_2$4 = { class: "flex items-center" };
const _hoisted_3$2 = { class: "text-left text-lg font-normal" };
function _sfc_render$1(_ctx, _cache) {
  return openBlock(), createElementBlock("div", _hoisted_1$5, [
    createBaseVNode("div", _hoisted_2$4, [
      createBaseVNode("h2", _hoisted_3$2, toDisplayString(_ctx.$t("manager.discoverCommunityContent")), 1)
    ])
  ]);
}
__name(_sfc_render$1, "_sfc_render$1");
const ManagerHeader = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["render", _sfc_render$1]]);
const _hoisted_1$4 = { class: "flex w-138 flex-col" };
const _hoisted_2$3 = { class: "flex h-full w-full flex-col gap-2 px-4 py-6" };
const _hoisted_3$1 = { key: 0 };
const _hoisted_4 = { class: "m-0 mb-4 text-sm leading-4 text-base-foreground" };
const _hoisted_5 = {
  key: 1,
  class: "flex min-h-8 w-full flex-col rounded-lg bg-base-background"
};
const _hoisted_6 = { class: "flex flex-1" };
const _hoisted_7 = { class: "mr-2 text-xs font-bold text-warning-background" };
const _hoisted_8 = { class: "text-xs font-bold text-base-foreground" };
const _hoisted_9 = {
  key: 0,
  "data-testid": "conflict-dialog-panel-expanded",
  class: "flex max-h-35.5 scrollbar-hide flex-col gap-2.5 overflow-y-auto px-4 py-2"
};
const _hoisted_10 = { class: "text-xs text-muted" };
const _hoisted_11 = {
  key: 2,
  class: "flex min-h-8 w-full flex-col rounded-lg bg-base-background"
};
const _hoisted_12 = { class: "flex flex-1" };
const _hoisted_13 = { class: "mr-2 text-xs font-bold text-warning-background" };
const _hoisted_14 = { class: "text-xs font-bold text-base-foreground" };
const _hoisted_15 = {
  key: 0,
  "data-testid": "conflict-dialog-panel-expanded",
  class: "flex max-h-[142px] scrollbar-hide flex-col gap-2.5 overflow-y-auto px-4 py-2"
};
const _hoisted_16 = { class: "text-xs text-muted" };
const _hoisted_17 = {
  key: 3,
  class: "flex min-h-8 w-full flex-col rounded-lg bg-base-background"
};
const _hoisted_18 = { class: "flex flex-1" };
const _hoisted_19 = { class: "mr-2 text-xs font-bold text-warning-background" };
const _hoisted_20 = { class: "text-xs font-bold text-base-foreground" };
const _hoisted_21 = {
  key: 0,
  "data-testid": "conflict-dialog-panel-expanded",
  class: "flex max-h-35.5 scrollbar-hide flex-col gap-2.5 overflow-y-auto px-4 py-2"
};
const _hoisted_22 = { class: "text-xs text-muted" };
const _sfc_main$5 = /* @__PURE__ */ defineComponent({
  __name: "NodeConflictDialogContent",
  props: {
    showAfterWhatsNew: { type: Boolean, default: false },
    conflictedPackages: {}
  },
  setup(__props) {
    const { t: t2 } = useI18n();
    const { conflictedPackages: globalConflictPackages } = useConflictDetection();
    const conflictsExpanded = ref(false);
    const extensionsExpanded = ref(false);
    const importFailedExpanded = ref(false);
    const conflictData = computed(
      () => __props.conflictedPackages || globalConflictPackages.value
    );
    const allConflictDetails = computed(() => {
      const allConflicts = flatMap(
        conflictData.value,
        (result) => result.conflicts
      );
      return filter(
        allConflicts,
        (conflict) => conflict.type !== "import_failed"
      );
    });
    const packagesWithImportFailed = computed(() => {
      return filter(
        conflictData.value,
        (result) => some(
          result.conflicts,
          (conflict) => conflict.type === "import_failed"
        )
      );
    });
    const importFailedConflicts = computed(() => {
      return map(
        packagesWithImportFailed.value,
        (result) => result.package_name || result.package_id
      );
    });
    const toggleImportFailedPanel = /* @__PURE__ */ __name(() => {
      importFailedExpanded.value = !importFailedExpanded.value;
      conflictsExpanded.value = false;
      extensionsExpanded.value = false;
    }, "toggleImportFailedPanel");
    const toggleConflictsPanel = /* @__PURE__ */ __name(() => {
      conflictsExpanded.value = !conflictsExpanded.value;
      extensionsExpanded.value = false;
      importFailedExpanded.value = false;
    }, "toggleConflictsPanel");
    const toggleExtensionsPanel = /* @__PURE__ */ __name(() => {
      extensionsExpanded.value = !extensionsExpanded.value;
      conflictsExpanded.value = false;
      importFailedExpanded.value = false;
    }, "toggleExtensionsPanel");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$4, [
        createVNode(ContentDivider, { width: 1 }),
        createBaseVNode("div", _hoisted_2$3, [
          _ctx.showAfterWhatsNew ? (openBlock(), createElementBlock("div", _hoisted_3$1, [
            createBaseVNode("p", _hoisted_4, [
              createTextVNode(toDisplayString(_ctx.$t("manager.conflicts.description")) + " ", 1),
              _cache[0] || (_cache[0] = createBaseVNode("br", null, null, -1)),
              _cache[1] || (_cache[1] = createBaseVNode("br", null, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("manager.conflicts.info")), 1)
            ])
          ])) : createCommentVNode("", true),
          importFailedConflicts.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_5, [
            createBaseVNode("div", {
              "data-testid": "conflict-dialog-panel-toggle",
              class: "flex h-8 w-full items-center justify-between gap-2 pl-4",
              onClick: toggleImportFailedPanel
            }, [
              createBaseVNode("div", _hoisted_6, [
                createBaseVNode("span", _hoisted_7, toDisplayString(importFailedConflicts.value.length), 1),
                createBaseVNode("span", _hoisted_8, toDisplayString(_ctx.$t("manager.conflicts.importFailedExtensions")), 1)
              ]),
              createBaseVNode("div", null, [
                createVNode(_sfc_main$2A, {
                  variant: "textonly",
                  class: "bg-transparent text-muted"
                }, {
                  default: withCtx(() => [
                    createBaseVNode("i", {
                      class: normalizeClass(
                        importFailedExpanded.value ? "pi pi-chevron-down text-xs" : "pi pi-chevron-right text-xs"
                      )
                    }, null, 2)
                  ]),
                  _: 1
                })
              ])
            ]),
            importFailedExpanded.value ? (openBlock(), createElementBlock("div", _hoisted_9, [
              (openBlock(true), createElementBlock(Fragment, null, renderList(importFailedConflicts.value, (packageName, i) => {
                return openBlock(), createElementBlock("div", {
                  key: i,
                  class: "conflict-list-item flex h-6 shrink-0 items-center justify-between px-4"
                }, [
                  createBaseVNode("span", _hoisted_10, toDisplayString(packageName), 1),
                  _cache[2] || (_cache[2] = createBaseVNode("span", { class: "pi pi-info-circle text-sm" }, null, -1))
                ]);
              }), 128))
            ])) : createCommentVNode("", true)
          ])) : createCommentVNode("", true),
          allConflictDetails.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_11, [
            createBaseVNode("div", {
              "data-testid": "conflict-dialog-panel-toggle",
              class: "flex h-8 w-full items-center justify-between gap-2 pl-4",
              onClick: toggleConflictsPanel
            }, [
              createBaseVNode("div", _hoisted_12, [
                createBaseVNode("span", _hoisted_13, toDisplayString(allConflictDetails.value.length), 1),
                createBaseVNode("span", _hoisted_14, toDisplayString(_ctx.$t("manager.conflicts.conflicts")), 1)
              ]),
              createBaseVNode("div", null, [
                createVNode(_sfc_main$2A, {
                  variant: "textonly",
                  class: "bg-transparent text-muted"
                }, {
                  default: withCtx(() => [
                    createBaseVNode("i", {
                      class: normalizeClass(
                        conflictsExpanded.value ? "pi pi-chevron-down text-xs" : "pi pi-chevron-right text-xs"
                      )
                    }, null, 2)
                  ]),
                  _: 1
                })
              ])
            ]),
            conflictsExpanded.value ? (openBlock(), createElementBlock("div", _hoisted_15, [
              (openBlock(true), createElementBlock(Fragment, null, renderList(allConflictDetails.value, (conflict, i) => {
                return openBlock(), createElementBlock("div", {
                  key: i,
                  class: "conflict-list-item flex h-6 shrink-0 items-center justify-between px-4"
                }, [
                  createBaseVNode("span", _hoisted_16, toDisplayString(unref(getConflictMessage)(conflict, unref(t2))), 1),
                  _cache[3] || (_cache[3] = createBaseVNode("span", { class: "pi pi-info-circle text-sm" }, null, -1))
                ]);
              }), 128))
            ])) : createCommentVNode("", true)
          ])) : createCommentVNode("", true),
          conflictData.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_17, [
            createBaseVNode("div", {
              "data-testid": "conflict-dialog-panel-toggle",
              class: "flex h-8 w-full items-center justify-between gap-2 pl-4",
              onClick: toggleExtensionsPanel
            }, [
              createBaseVNode("div", _hoisted_18, [
                createBaseVNode("span", _hoisted_19, toDisplayString(conflictData.value.length), 1),
                createBaseVNode("span", _hoisted_20, toDisplayString(_ctx.$t("manager.conflicts.extensionAtRisk")), 1)
              ]),
              createBaseVNode("div", null, [
                createVNode(_sfc_main$2A, {
                  variant: "textonly",
                  class: "bg-transparent text-muted"
                }, {
                  default: withCtx(() => [
                    createBaseVNode("i", {
                      class: normalizeClass(
                        extensionsExpanded.value ? "pi pi-chevron-down text-xs" : "pi pi-chevron-right text-xs"
                      )
                    }, null, 2)
                  ]),
                  _: 1
                })
              ])
            ]),
            extensionsExpanded.value ? (openBlock(), createElementBlock("div", _hoisted_21, [
              (openBlock(true), createElementBlock(Fragment, null, renderList(conflictData.value, (conflictResult) => {
                return openBlock(), createElementBlock("div", {
                  key: conflictResult.package_id,
                  class: "conflict-list-item flex h-6 shrink-0 items-center justify-between px-4"
                }, [
                  createBaseVNode("span", _hoisted_22, toDisplayString(conflictResult.package_name), 1),
                  _cache[4] || (_cache[4] = createBaseVNode("span", { class: "pi pi-info-circle text-sm" }, null, -1))
                ]);
              }), 128))
            ])) : createCommentVNode("", true)
          ])) : createCommentVNode("", true)
        ]),
        createVNode(ContentDivider, { width: 1 })
      ]);
    };
  }
});
const NodeConflictDialogContent = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-c48e4b1c"]]);
const _hoisted_1$3 = { class: "flex w-full items-center justify-between px-3 py-4" };
const _hoisted_2$2 = { class: "flex w-full items-center justify-between gap-2 pr-1" };
const _sfc_main$4 = /* @__PURE__ */ defineComponent({
  __name: "NodeConflictFooter",
  props: {
    buttonText: { default: void 0 },
    onButtonClick: { type: Function, default: void 0 }
  },
  setup(__props) {
    const props = __props;
    const { buildDocsUrl } = useExternalLink();
    const dialogStore = useDialogStore();
    const handleConflictInfoClick = /* @__PURE__ */ __name(() => {
      window.open(
        buildDocsUrl("/troubleshooting/custom-node-issues", {
          includeLocale: true
        }),
        "_blank"
      );
    }, "handleConflictInfoClick");
    const handleButtonClick = /* @__PURE__ */ __name(() => {
      dialogStore.closeDialog({ key: "global-node-conflict" });
      if (props.onButtonClick) {
        props.onButtonClick();
      }
    }, "handleButtonClick");
    return (_ctx, _cache) => {
      return openBlock(), createElementBlock("div", _hoisted_1$3, [
        createBaseVNode("div", _hoisted_2$2, [
          createVNode(_sfc_main$2A, {
            variant: "muted-textonly",
            size: "sm",
            class: "text-sm",
            onClick: handleConflictInfoClick
          }, {
            default: withCtx(() => [
              _cache[0] || (_cache[0] = createBaseVNode("i", { class: "pi pi-info-circle" }, null, -1)),
              createTextVNode(" " + toDisplayString(_ctx.$t("manager.conflicts.conflictInfoTitle")), 1)
            ]),
            _: 1
          }),
          props.buttonText ? (openBlock(), createBlock(_sfc_main$2A, {
            key: 0,
            variant: "secondary",
            size: "sm",
            onClick: handleButtonClick
          }, {
            default: withCtx(() => [
              createTextVNode(toDisplayString(props.buttonText), 1)
            ]),
            _: 1
          })) : createCommentVNode("", true)
        ])
      ]);
    };
  }
});
const _sfc_main$3 = {};
const _hoisted_1$2 = { class: "flex h-12 w-full items-center justify-between pl-6" };
const _hoisted_2$1 = { class: "flex items-center gap-2" };
const _hoisted_3 = { class: "text-base font-bold" };
function _sfc_render(_ctx, _cache) {
  return openBlock(), createElementBlock("div", _hoisted_1$2, [
    createBaseVNode("div", _hoisted_2$1, [
      _cache[0] || (_cache[0] = createBaseVNode("i", { class: "pi pi-exclamation-triangle text-lg" }, null, -1)),
      createBaseVNode("p", _hoisted_3, toDisplayString(_ctx.$t("manager.conflicts.title")), 1)
    ])
  ]);
}
__name(_sfc_render, "_sfc_render");
const NodeConflictHeader = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["render", _sfc_render]]);
const useDialogService = /* @__PURE__ */ __name(() => {
  const dialogStore = useDialogStore();
  function showLoadWorkflowWarning(props) {
    dialogStore.showDialog({
      key: "global-missing-nodes",
      headerComponent: _sfc_main$19,
      footerComponent: _sfc_main$1a,
      component: _sfc_main$1d,
      dialogComponentProps: {
        closable: true,
        pt: {
          root: { class: "bg-base-background border-border-default" },
          header: { class: "!p-0 !m-0" },
          content: { class: "!p-0 overflow-y-hidden" },
          footer: { class: "!p-0" },
          pcCloseButton: {
            root: {
              class: "!w-7 !h-7 !border-none !outline-none !p-2 !m-1.5"
            }
          }
        }
      },
      props
    });
  }
  __name(showLoadWorkflowWarning, "showLoadWorkflowWarning");
  function showMissingModelsWarning(props) {
    dialogStore.showDialog({
      key: "global-missing-models-warning",
      component: MissingModelsWarning,
      props
    });
  }
  __name(showMissingModelsWarning, "showMissingModelsWarning");
  function showSettingsDialog(panel) {
    const props = panel ? { props: { defaultPanel: panel } } : void 0;
    dialogStore.showDialog({
      key: "global-settings",
      headerComponent: SettingDialogHeader,
      component: SettingDialogContent,
      ...props
    });
  }
  __name(showSettingsDialog, "showSettingsDialog");
  function showAboutDialog() {
    dialogStore.showDialog({
      key: "global-settings",
      headerComponent: SettingDialogHeader,
      component: SettingDialogContent,
      props: {
        defaultPanel: "about"
      }
    });
  }
  __name(showAboutDialog, "showAboutDialog");
  function showExecutionErrorDialog(executionError) {
    const props = {
      error: {
        exceptionType: executionError.exception_type,
        exceptionMessage: executionError.exception_message,
        nodeId: executionError.node_id?.toString(),
        nodeType: executionError.node_type,
        traceback: executionError.traceback.join("\n"),
        reportType: "graphExecutionError"
      }
    };
    dialogStore.showDialog({
      key: "global-execution-error",
      component: _sfc_main$17,
      props,
      dialogComponentProps: {
        onClose: /* @__PURE__ */ __name(() => {
        }, "onClose")
      }
    });
  }
  __name(showExecutionErrorDialog, "showExecutionErrorDialog");
  function showManagerDialog(props = {}) {
    dialogStore.showDialog({
      key: "global-manager",
      component: _sfc_main$7,
      headerComponent: ManagerHeader,
      dialogComponentProps: {
        closable: true,
        pt: {
          pcCloseButton: {
            root: {
              class: "bg-dialog-surface w-9 h-9 p-1.5 rounded-full text-white"
            }
          },
          header: { class: "py-0! px-6 m-0! h-[68px]" },
          content: {
            class: "p-0! h-full w-[90vw] max-w-full flex-1 overflow-hidden"
          },
          root: { class: "manager-dialog" }
        }
      },
      props
    });
  }
  __name(showManagerDialog, "showManagerDialog");
  function parseError(error) {
    const filename = "fileName" in error ? error.fileName : error.stack?.match(/(\/extensions\/.*\.js)/)?.[1];
    const extensionFile = filename ? filename.substring(filename.indexOf("/extensions/")) : void 0;
    return {
      errorMessage: error.toString(),
      stackTrace: error.stack,
      extensionFile
    };
  }
  __name(parseError, "parseError");
  function showErrorDialog(error, options = {}) {
    const errorProps = error instanceof Error ? parseError(error) : {
      errorMessage: String(error)
    };
    const props = {
      error: {
        exceptionType: options.title ?? "Unknown Error",
        exceptionMessage: errorProps.errorMessage,
        traceback: errorProps.stackTrace ?? t("errorDialog.noStackTrace"),
        reportType: options.reportType
      }
    };
    dialogStore.showDialog({
      key: "global-error",
      component: _sfc_main$17,
      props,
      dialogComponentProps: {
        onClose: /* @__PURE__ */ __name(() => {
        }, "onClose")
      }
    });
  }
  __name(showErrorDialog, "showErrorDialog");
  function showManagerProgressDialog(options) {
    return dialogStore.showDialog({
      key: "global-manager-progress-dialog",
      component: _sfc_main$D,
      headerComponent: _sfc_main$B,
      footerComponent: _sfc_main$C,
      props: options?.props,
      priority: 2,
      dialogComponentProps: {
        closable: false,
        modal: false,
        position: "bottom",
        pt: {
          root: { class: "w-[80%] max-w-2xl mx-auto border-none" },
          content: { class: "p-0!" },
          header: { class: "p-0! border-none" },
          footer: { class: "p-0! border-none" }
        }
      }
    });
  }
  __name(showManagerProgressDialog, "showManagerProgressDialog");
  async function showApiNodesSignInDialog(apiNodeNames) {
    return new Promise((resolve) => {
      dialogStore.showDialog({
        key: "api-nodes-signin",
        component: _sfc_main$2z,
        props: {
          apiNodeNames,
          onLogin: /* @__PURE__ */ __name(() => showSignInDialog().then((result) => resolve(result)), "onLogin"),
          onCancel: /* @__PURE__ */ __name(() => resolve(false), "onCancel")
        },
        headerComponent: ComfyOrgHeader,
        dialogComponentProps: {
          closable: false,
          onClose: /* @__PURE__ */ __name(() => resolve(false), "onClose")
        }
      });
    }).then((result) => {
      dialogStore.closeDialog({ key: "api-nodes-signin" });
      return result;
    });
  }
  __name(showApiNodesSignInDialog, "showApiNodesSignInDialog");
  async function showSignInDialog() {
    return new Promise((resolve) => {
      dialogStore.showDialog({
        key: "global-signin",
        component: _sfc_main$_,
        headerComponent: ComfyOrgHeader,
        props: {
          onSuccess: /* @__PURE__ */ __name(() => resolve(true), "onSuccess")
        },
        dialogComponentProps: {
          closable: true,
          onClose: /* @__PURE__ */ __name(() => resolve(false), "onClose")
        }
      });
    }).then((result) => {
      dialogStore.closeDialog({ key: "global-signin" });
      return result;
    });
  }
  __name(showSignInDialog, "showSignInDialog");
  async function prompt({
    title,
    message,
    defaultValue = "",
    placeholder
  }) {
    return new Promise((resolve) => {
      dialogStore.showDialog({
        key: "global-prompt",
        title,
        component: _sfc_main$13,
        props: {
          message,
          defaultValue,
          onConfirm: /* @__PURE__ */ __name((value) => {
            resolve(value);
          }, "onConfirm"),
          placeholder
        },
        dialogComponentProps: {
          onClose: /* @__PURE__ */ __name(() => {
            resolve(null);
          }, "onClose")
        }
      });
    });
  }
  __name(prompt, "prompt");
  async function confirm2({
    title,
    message,
    type = "default",
    itemList = [],
    hint
  }) {
    return new Promise((resolve) => {
      const options = {
        key: "global-prompt",
        title,
        component: ConfirmationDialogContent,
        props: {
          message,
          type,
          itemList,
          onConfirm: resolve,
          hint
        },
        dialogComponentProps: {
          onClose: /* @__PURE__ */ __name(() => resolve(null), "onClose")
        }
      };
      dialogStore.showDialog(options);
    });
  }
  __name(confirm2, "confirm");
  function showTopUpCreditsDialog(options) {
    const { isActiveSubscription } = useSubscription();
    if (!isActiveSubscription.value) return;
    return dialogStore.showDialog({
      key: "top-up-credits",
      component: _sfc_main$X,
      props: options,
      dialogComponentProps: {
        headless: true,
        pt: {
          header: { class: "p-0! hidden" },
          content: { class: "p-0! m-0!" }
        }
      }
    });
  }
  __name(showTopUpCreditsDialog, "showTopUpCreditsDialog");
  function showUpdatePasswordDialog() {
    return dialogStore.showDialog({
      key: "global-update-password",
      component: _sfc_main$W,
      headerComponent: ComfyOrgHeader,
      props: {
        onSuccess: /* @__PURE__ */ __name(() => dialogStore.closeDialog({ key: "global-update-password" }), "onSuccess")
      }
    });
  }
  __name(showUpdatePasswordDialog, "showUpdatePasswordDialog");
  function showExtensionDialog(options) {
    return {
      dialog: dialogStore.showExtensionDialog(options),
      closeDialog: /* @__PURE__ */ __name(() => dialogStore.closeDialog({ key: options.key }), "closeDialog")
    };
  }
  __name(showExtensionDialog, "showExtensionDialog");
  function toggleManagerDialog(props) {
    if (dialogStore.isDialogOpen("global-manager")) {
      dialogStore.closeDialog({ key: "global-manager" });
    } else {
      showManagerDialog(props);
    }
  }
  __name(toggleManagerDialog, "toggleManagerDialog");
  function toggleManagerProgressDialog(props) {
    if (dialogStore.isDialogOpen("global-manager-progress-dialog")) {
      dialogStore.closeDialog({ key: "global-manager-progress-dialog" });
    } else {
      showManagerProgressDialog({ props });
    }
  }
  __name(toggleManagerProgressDialog, "toggleManagerProgressDialog");
  function showLayoutDialog(options) {
    const layoutDefaultProps = {
      headless: true,
      modal: true,
      closable: false,
      pt: {
        root: {
          class: "rounded-2xl overflow-hidden"
        },
        header: {
          class: "p-0! hidden"
        },
        content: {
          class: "p-0! m-0!"
        }
      }
    };
    return dialogStore.showDialog({
      ...options,
      dialogComponentProps: merge(
        layoutDefaultProps,
        options.dialogComponentProps || {}
      )
    });
  }
  __name(showLayoutDialog, "showLayoutDialog");
  function showNodeConflictDialog(options = {}) {
    const {
      dialogComponentProps,
      buttonText,
      onButtonClick,
      showAfterWhatsNew,
      conflictedPackages
    } = options;
    return dialogStore.showDialog({
      key: "global-node-conflict",
      headerComponent: NodeConflictHeader,
      footerComponent: _sfc_main$4,
      component: NodeConflictDialogContent,
      dialogComponentProps: {
        closable: true,
        pt: {
          header: { class: "!p-0 !m-0" },
          content: { class: "!p-0 overflow-y-hidden" },
          footer: { class: "!p-0" },
          pcCloseButton: {
            root: {
              class: "!w-7 !h-7 !border-none !outline-none !p-2 !m-1.5 bg-dialog-surface text-white"
            }
          }
        },
        ...dialogComponentProps
      },
      props: {
        showAfterWhatsNew,
        conflictedPackages
      },
      footerProps: {
        buttonText,
        onButtonClick
      }
    });
  }
  __name(showNodeConflictDialog, "showNodeConflictDialog");
  async function showSubscriptionRequiredDialog() {
    {
      return;
    }
  }
  __name(showSubscriptionRequiredDialog, "showSubscriptionRequiredDialog");
  return {
    showLoadWorkflowWarning,
    showMissingModelsWarning,
    showSettingsDialog,
    showAboutDialog,
    showExecutionErrorDialog,
    showManagerDialog,
    showManagerProgressDialog,
    showApiNodesSignInDialog,
    showSignInDialog,
    showSubscriptionRequiredDialog,
    showTopUpCreditsDialog,
    showUpdatePasswordDialog,
    showExtensionDialog,
    prompt,
    showErrorDialog,
    confirm: confirm2,
    toggleManagerDialog,
    toggleManagerProgressDialog,
    showLayoutDialog,
    showNodeConflictDialog
  };
}, "useDialogService");
const _hoisted_1$1 = { class: "relative h-full w-full overflow-hidden" };
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
  __name: "LayoutDefault",
  setup(__props) {
    useFavicon("/assets/favicon.ico");
    return (_ctx, _cache) => {
      const _component_router_view = resolveComponent("router-view");
      return openBlock(), createElementBlock("main", _hoisted_1$1, [
        createVNode(_component_router_view)
      ]);
    };
  }
});
const STORAGE_PREFIX = "Comfy.PreservedQuery.";
const preservedQueries = /* @__PURE__ */ new Map();
const readQueryParam = /* @__PURE__ */ __name((value) => {
  return typeof value === "string" ? value : void 0;
}, "readQueryParam");
const getStorageKey = /* @__PURE__ */ __name((namespace) => `${STORAGE_PREFIX}${namespace}`, "getStorageKey");
const isValidQueryRecord = /* @__PURE__ */ __name((value) => {
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    return false;
  }
  return Object.values(value).every((v2) => typeof v2 === "string");
}, "isValidQueryRecord");
const readFromStorage = /* @__PURE__ */ __name((namespace) => {
  try {
    const raw = sessionStorage.getItem(getStorageKey(namespace));
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    if (!isValidQueryRecord(parsed)) {
      console.warn("[preservedQuery] invalid storage format");
      sessionStorage.removeItem(getStorageKey(namespace));
      return null;
    }
    return parsed;
  } catch (error) {
    console.warn("[preservedQuery] storage operation failed");
    sessionStorage.removeItem(getStorageKey(namespace));
    return null;
  }
}, "readFromStorage");
const writeToStorage = /* @__PURE__ */ __name((namespace, payload) => {
  try {
    if (!payload || Object.keys(payload).length === 0) {
      sessionStorage.removeItem(getStorageKey(namespace));
      return;
    }
    sessionStorage.setItem(getStorageKey(namespace), JSON.stringify(payload));
  } catch (error) {
    console.warn("[preservedQuery] failed to write storage", {
      namespace,
      error
    });
  }
}, "writeToStorage");
const hydratePreservedQuery = /* @__PURE__ */ __name((namespace) => {
  if (preservedQueries.has(namespace)) return;
  const payload = readFromStorage(namespace);
  if (payload) {
    preservedQueries.set(namespace, payload);
  }
}, "hydratePreservedQuery");
const capturePreservedQuery = /* @__PURE__ */ __name((namespace, query, keys) => {
  const payload = {};
  keys.forEach((key) => {
    const value = readQueryParam(query[key]);
    if (value) {
      payload[key] = value;
    }
  });
  if (Object.keys(payload).length === 0) return;
  preservedQueries.set(namespace, payload);
  writeToStorage(namespace, payload);
}, "capturePreservedQuery");
const mergePreservedQueryIntoQuery = /* @__PURE__ */ __name((namespace, query) => {
  const payload = preservedQueries.get(namespace);
  if (!payload) return void 0;
  const nextQuery = { ...query || {} };
  let changed = false;
  for (const [key, value] of Object.entries(payload)) {
    if (typeof nextQuery[key] === "string") continue;
    nextQuery[key] = value;
    changed = true;
  }
  return changed ? nextQuery : void 0;
}, "mergePreservedQueryIntoQuery");
const clearPreservedQuery = /* @__PURE__ */ __name((namespace) => {
  if (!preservedQueries.has(namespace)) return;
  preservedQueries.delete(namespace);
  writeToStorage(namespace, null);
}, "clearPreservedQuery");
const installPreservedQueryTracker = /* @__PURE__ */ __name((router2, definitions) => {
  const trackedDefinitions = definitions.map((definition) => ({
    ...definition
  }));
  router2.beforeEach((to, _from, next) => {
    const queryKeys = new Set(Object.keys(to.query));
    trackedDefinitions.forEach(({ namespace, keys }) => {
      hydratePreservedQuery(namespace);
      const shouldCapture = keys.some((key) => queryKeys.has(key));
      if (shouldCapture) {
        capturePreservedQuery(namespace, to.query, keys);
      }
    });
    next();
  });
}, "installPreservedQueryTracker");
const PRESERVED_QUERY_NAMESPACES = {
  TEMPLATE: "template"
};
const isFileProtocol = window.location.protocol === "file:";
function getBasePath() {
  if (isElectron()) return "/";
  return window.location.pathname;
}
__name(getBasePath, "getBasePath");
const basePath = getBasePath();
const router = createRouter({
  history: isFileProtocol ? createWebHashHistory() : (
    // Base path must be specified to ensure correct relative paths
    // Example: For URL 'http://localhost:7801/ComfyBackendDirect',
    // we need this base path or assets will incorrectly resolve from 'http://localhost:7801/'
    createWebHistory(basePath)
  ),
  routes: [
    ...[],
    {
      path: "/",
      component: _sfc_main$2,
      children: [
        {
          path: "",
          name: "GraphView",
          component: /* @__PURE__ */ __name(() => __vitePreload(() => import("./GraphView-DAIlTCBt.js").then((n) => n.G), true ? __vite__mapDeps([59,1,2,8,4,52,26,35,54,58,29,24,27,30,31,60]) : void 0, import.meta.url), "component"),
          beforeEnter: /* @__PURE__ */ __name(async (_to, _from, next) => {
            const userStore = useUserStore();
            await userStore.initialize();
            if (userStore.needsLogin) {
              next("/user-select");
            } else {
              next();
            }
          }, "beforeEnter")
        },
        {
          path: "user-select",
          name: "UserSelectView",
          component: /* @__PURE__ */ __name(() => __vitePreload(() => import("./UserSelectView-DWDBg_h9.js"), true ? __vite__mapDeps([61,1,2,4,8,9,10,11,12]) : void 0, import.meta.url), "component")
        }
      ]
    }
  ],
  scrollBehavior(_to, _from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    } else {
      return { top: 0 };
    }
  }
});
installPreservedQueryTracker(router, [
  {
    namespace: PRESERVED_QUERY_NAMESPACES.TEMPLATE,
    keys: ["template", "source", "mode"]
  }
]);
const _hoisted_1 = { key: 0 };
const _hoisted_2 = ["id"];
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
  __name: "GlobalDialog",
  setup(__props) {
    const dialogStore = useDialogStore();
    return (_ctx, _cache) => {
      return openBlock(true), createElementBlock(Fragment, null, renderList(unref(dialogStore).dialogStack, (item) => {
        return openBlock(), createBlock(unref(script$n), mergeProps({
          key: item.key,
          visible: item.visible,
          "onUpdate:visible": /* @__PURE__ */ __name(($event) => item.visible = $event, "onUpdate:visible"),
          class: "global-dialog",
          ref_for: true
        }, item.dialogComponentProps, {
          pt: item.dialogComponentProps.pt,
          "aria-labelledby": item.key
        }), createSlots({
          header: withCtx(() => [
            !item.dialogComponentProps?.headless ? (openBlock(), createElementBlock("div", _hoisted_1, [
              item.headerComponent ? (openBlock(), createBlock(resolveDynamicComponent(item.headerComponent), mergeProps({
                key: 0,
                ref_for: true
              }, item.headerProps, {
                id: item.key
              }), null, 16, ["id"])) : (openBlock(), createElementBlock("h3", {
                key: 1,
                id: item.key
              }, toDisplayString(item.title || " "), 9, _hoisted_2))
            ])) : createCommentVNode("", true)
          ]),
          default: withCtx(() => [
            (openBlock(), createBlock(resolveDynamicComponent(item.component), mergeProps({ ref_for: true }, item.contentProps, {
              maximized: item.dialogComponentProps.maximized
            }), null, 16, ["maximized"]))
          ]),
          _: 2
        }, [
          item.footerComponent ? {
            name: "footer",
            fn: withCtx(() => [
              (openBlock(), createBlock(resolveDynamicComponent(item.footerComponent), mergeProps({ ref_for: true }, item.footerProps), null, 16))
            ]),
            key: "0"
          } : void 0
        ]), 1040, ["visible", "onUpdate:visible", "pt", "aria-labelledby"]);
      }), 128);
    };
  }
});
const _sfc_main = /* @__PURE__ */ defineComponent({
  __name: "App",
  setup(__props) {
    const workspaceStore = useWorkspaceStore();
    const conflictDetection = useConflictDetection();
    const workflowStore = useWorkflowStore();
    const dialogService = useDialogService();
    const isLoading = computed(() => workspaceStore.spinner);
    const handleKey = /* @__PURE__ */ __name((e2) => {
      workspaceStore.shiftDown = e2.shiftKey;
    }, "handleKey");
    useEventListener(window, "keydown", handleKey);
    useEventListener(window, "keyup", handleKey);
    const showContextMenu = /* @__PURE__ */ __name((event) => {
      const { target } = event;
      switch (true) {
        case target instanceof HTMLTextAreaElement:
        case (target instanceof HTMLInputElement && target.type === "text"):
          electronAPI()?.showContextMenu({ type: "text" });
          return;
      }
    }, "showContextMenu");
    onMounted(() => {
      window["__COMFYUI_FRONTEND_VERSION__"] = config.app_version;
      if (isElectron()) {
        document.addEventListener("contextmenu", showContextMenu);
      }
      window.addEventListener("vite:preloadError", async (_event) => {
        if (!app$1.vueAppReady || !workflowStore.activeWorkflow?.isModified) {
          window.location.reload();
        } else {
          await dialogService.confirm({
            title: t("g.vitePreloadErrorTitle"),
            message: t("g.vitePreloadErrorMessage")
          }).then((confirmed) => {
            if (confirmed) {
              window.location.reload();
            }
          });
        }
      });
      void conflictDetection.initializeConflictDetection();
    });
    return (_ctx, _cache) => {
      const _component_router_view = resolveComponent("router-view");
      return openBlock(), createElementBlock(Fragment, null, [
        createVNode(_component_router_view),
        isLoading.value ? (openBlock(), createBlock(unref(script$7), {
          key: 0,
          class: "absolute inset-0 flex h-[unset] items-center justify-center"
        })) : createCommentVNode("", true),
        createVNode(_sfc_main$1),
        createVNode(unref(script$I), {
          "full-screen": "",
          blocked: isLoading.value
        }, null, 8, ["blocked"])
      ], 64);
    };
  }
});
const ComfyUIPreset = definePreset(index, {
  semantic: {
    // @ts-expect-error fixme ts strict error
    primary: index["primitive"].blue
  }
});
const firebaseApp = initializeApp(getFirebaseConfig());
const app = createApp(_sfc_main);
const pinia = createPinia();
init({
  app,
  dsn: "",
  enabled: false,
  release: "1.36.14",
  normalizeDepth: 8,
  tracesSampleRate: 0,
  replaysSessionSampleRate: 0,
  replaysOnErrorSampleRate: 0,
  // Only set these for non-cloud builds
  ...{
    integrations: [],
    autoSessionTracking: false,
    defaultIntegrations: false
  }
});
app.directive("tooltip", Tooltip);
app.use(router).use(PrimeVue, {
  theme: {
    preset: ComfyUIPreset,
    options: {
      prefix: "p",
      cssLayer: {
        name: "primevue",
        order: "theme, base, primevue"
      },
      // This is a workaround for the issue with the dark mode selector
      // https://github.com/primefaces/primevue/issues/5515
      darkModeSelector: ".dark-theme, :root:has(.dark-theme)"
    }
  }
}).use(ConfirmationService).use(ToastService).use(pinia).use(i18n).use(VueFire, {
  firebaseApp,
  modules: [VueFireAuth()]
});
app.mount("#vue-app");
export {
  useExternalLink as $,
  useExecutionStore as A,
  collectAllNodes as B,
  useNodeDefStore as C,
  useWorkflowService as D,
  EditableText as E,
  ComfyWorkflow as F,
  appendJsonExt as G,
  useSubgraphNavigationStore as H,
  useSubgraphStore as I,
  useCanvasStore as J,
  forEachSubgraphNode as K,
  useQueueStore as L,
  useCopyToClipboard as M,
  useLitegraphService as N,
  useMediaAssetActions as O,
  createAnnotatedPath as P,
  downloadFile as Q,
  downloadBlob as R,
  SearchBox as S,
  mapTaskOutputToAssetItem as T,
  formatDuration as U,
  useErrorHandling as V,
  useAssetsStore as W,
  useAssetSelectionStore as X,
  _sfc_main$29 as Y,
  useExtensionStore as Z,
  _sfc_main$2A as _,
  api as a,
  _sfc_main$28 as a$,
  useCurrentUser as a0,
  useFirebaseAuthActions as a1,
  useFirebaseAuthStore as a2,
  formatCreditsFromCents as a3,
  useManagerState as a4,
  isElectron as a5,
  ManagerTab as a6,
  useCanvasPositionConversion as a7,
  isDOMWidget as a8,
  isComponentWidget as a9,
  RenderShape as aA,
  LGraphEventMode as aB,
  filterOutputNodes as aC,
  useTitleEditorStore as aD,
  distributeNodes as aE,
  alignNodes as aF,
  useSubgraphOperations as aG,
  useExtensionService as aH,
  useNodeHelpStore as aI,
  NodeHelpContent as aJ,
  isProxyWidget as aK,
  useLayoutMutations as aL,
  LayoutSource as aM,
  NodeSlotType as aN,
  shouldExpand as aO,
  getComponent as aP,
  _sfc_main$1H as aQ,
  parseProxyWidgets as aR,
  widgetItemToProperty as aS,
  matchesPropertyItem as aT,
  isRecommendedWidget as aU,
  pruneDisconnected as aV,
  demoteWidget as aW,
  promoteWidget as aX,
  matchesWidgetItem as aY,
  DraggableList as aZ,
  _sfc_main$27 as a_,
  useDomWidgetStore as aa,
  useChainCallback as ab,
  layoutStore as ac,
  MinimapDataSourceFactory as ad,
  renderMinimapToCanvas as ae,
  useColorPaletteStore as af,
  enforceMinimumBounds as ag,
  calculateMinimapScale as ah,
  useCanvasInteractions as ai,
  LiteGraph as aj,
  isOverNodeInput as ak,
  isOverNodeOutput as al,
  adjustColor as am,
  LGraphCanvas as an,
  isColorable as ao,
  getItemsColorOption as ap,
  useSelectionState as aq,
  isLGraphNode as ar,
  isOutputNode as as,
  SubgraphNode as at,
  useSelectedLiteGraphItems as au,
  useVueFeatureFlags as av,
  isLGraphGroup as aw,
  LGraphNode as ax,
  LGraphGroup as ay,
  computeUnionBounds as az,
  isCloud as b,
  useNodeOutputStore as b$,
  useNodeFrequencyStore as b0,
  useNodeBookmarkStore as b1,
  highlightQuery as b2,
  formatNumberWithSuffix as b3,
  NodeSourceType as b4,
  NodePreview as b5,
  NodeSearchFilter as b6,
  SearchFilterChip as b7,
  LinkReleaseTriggerAction as b8,
  _sfc_main$25 as b9,
  LGraphBadge as bA,
  BadgePosition as bB,
  NodeBadgeMode as bC,
  useSharedCanvasPositionConversion as bD,
  ComfyNodeDefImpl as bE,
  ComfyModelDef as bF,
  legacyMenuCompat as bG,
  te as bH,
  LGraph as bI,
  LLink as bJ,
  DragAndScale as bK,
  ContextMenu as bL,
  isImageNode as bM,
  isVideoNode as bN,
  isAudioNode as bO,
  CanvasPointer as bP,
  LinkMarkerShape as bQ,
  clearPreservedQuery as bR,
  PRESERVED_QUERY_NAMESPACES as bS,
  getStorageValue as bT,
  hydratePreservedQuery as bU,
  mergePreservedQueryIntoQuery as bV,
  setStorageValue as bW,
  useNodeZIndex as bX,
  snapPoint as bY,
  isMiddlePointerInput as bZ,
  syncNodeSlotLayoutsFromDOM as b_,
  _sfc_main$26 as ba,
  _sfc_main$24 as bb,
  _sfc_main$23 as bc,
  useMenuItemStore as bd,
  useColorPaletteService as be,
  SettingDialogContent as bf,
  SettingDialogHeader as bg,
  isAbortError as bh,
  getComfyApiBaseUrl as bi,
  useSystemStatsStore as bj,
  stringToLocale as bk,
  useConflictAcknowledgment as bl,
  electronAPI as bm,
  useComfyManagerService as bn,
  formatVersionAnchor as bo,
  renderMarkdownToHtml as bp,
  useConflictDetection as bq,
  useUserStore as br,
  useKeybindingStore as bs,
  useWorkflowThumbnail as bt,
  usePragmaticDraggable as bu,
  usePragmaticDroppable as bv,
  useWorkflowBookmarkStore as bw,
  ensureCorrectLayoutScale as bx,
  removeNodeTitleHeight as by,
  formatCreditsFromUsd as bz,
  cn as c,
  applyTextReplacements$1 as c$,
  getLocatorIdFromNodeData as c0,
  TitleMode as c1,
  applyLightThemeColor as c2,
  nonWidgetedInputs as c3,
  isTransparent as c4,
  getNodeByLocatorId as c5,
  _sfc_main$1M as c6,
  _sfc_main$1J as c7,
  _sfc_main$1N as c8,
  _sfc_main$1G as c9,
  remoteConfig as cA,
  isComboInputSpec as cB,
  isColorFormat as cC,
  toHexFromFormat as cD,
  useLoad3dDrag as cE,
  Load3DViewerContent as cF,
  useLoad3dService as cG,
  useLoad3d as cH,
  ComfyApp as cI,
  ComfyDialog as cJ,
  $el as cK,
  isComboWidget as cL,
  processDynamicPrompt as cM,
  isValidUrl as cN,
  ExecutableNodeDTO as cO,
  ComfyDialog$1 as cP,
  PREFIX as cQ,
  SEPARATOR as cR,
  getInputSpecType as cS,
  isIntInputSpec$1 as cT,
  isFloatInputSpec$1 as cU,
  isComboInputSpec$1 as cV,
  lcm as cW,
  getComboSpecComboOptions as cX,
  CONFIG as cY,
  GET_CONFIG as cZ,
  ComfyWidgets as c_,
  ChangeTracker as ca,
  UnauthorizedError as cb,
  isNativeWindow as cc,
  mergeCustomNodesI18n as cd,
  forEachNode as ce,
  IS_CONTROL_WIDGET as cf,
  updateControlWidgetLabel as cg,
  migrateLegacyRerouteNodes as ch,
  assetItemSchema as ci,
  MODELS_TAG as cj,
  MISSING_TAG as ck,
  tryToggleWidgetPromotion as cl,
  getAllNonIoNodesInSubgraph as cm,
  useQueueSettingsStore as cn,
  getExecutionIdsForSelectedNodes as co,
  DEFAULT_DARK_COLOR_PALETTE as cp,
  DEFAULT_LIGHT_COLOR_PALETTE as cq,
  ManagerUIState as cr,
  config as cs,
  useQueuePendingTaskCountStore as ct,
  useAssetsSidebarTab as cu,
  _imports_0$1 as cv,
  loadLocale as cw,
  useModelStore as cx,
  KeybindingImpl as cy,
  KeyComboImpl as cz,
  _export_sfc as d,
  isValidWidgetType as d0,
  addValueControlWidgets as d1,
  NodeSlot as d2,
  GROUP as d3,
  useWidgetStore as d4,
  deserialiseAndCreate as d5,
  serialise as d6,
  Load3dUtils as d7,
  nodeToLoad3dMap as d8,
  ComponentWidgetImpl as d9,
  addWidget as da,
  isLoad3dNode as db,
  useMaskEditorStore as dc,
  useMaskEditor as dd,
  createBounds as de,
  useNodeFileInput as df,
  useNodeDragAndDrop as dg,
  useNodePaste as dh,
  isComboInputSpecV1 as di,
  formatSize as dj,
  formatCommitHash as dk,
  _sfc_main$S as dl,
  _sfc_main$Z as dm,
  formatMetronomeCurrency as dn,
  FormItem as dp,
  getFilenameDetails as dq,
  truncateFilename as dr,
  TIER_PRICING as ds,
  TIER_TO_KEY as dt,
  useSettingStore as e,
  useToastStore as f,
  _sfc_main$2t as g,
  assetService as h,
  i18n as i,
  d as j,
  useModelToNodeStore as k,
  useSubscription as l,
  useTelemetry as m,
  normalizeI18nKey as n,
  useDialogService as o,
  useWorkspaceStore as p,
  app$1 as q,
  showNativeSystemMenu as r,
  st as s,
  t,
  useDialogStore as u,
  useWorkflowStore as v,
  useRightSidePanelStore as w,
  useSidebarTabStore as x,
  useBottomPanelStore as y,
  useCommandStore as z
};
//# sourceMappingURL=index-BqxAUePh.js.map
