From 11af9469ee3ede15900d033299e6f48b3a83b33f Mon Sep 17 00:00:00 2001 From: Dijkstra Date: Sat, 26 Jul 2025 19:39:20 +0200 Subject: [PATCH] fix: removed all the advanced node stuff. Replaced it with vanilla node with extra js took from custom-node repo. And added explanation for view info in the readme --- README.md | 11 +- nodes_autotrigger.py | 131 +----------- utils.py | 65 ------ web/js/betterCombos.js | 469 +++++++++++++++++++++++++++++++++++++++++ web/js/loraInfo.js | 36 ---- 5 files changed, 480 insertions(+), 232 deletions(-) create mode 100644 web/js/betterCombos.js delete mode 100644 web/js/loraInfo.js diff --git a/README.md b/README.md index c33c9e6..6122841 100644 --- a/README.md +++ b/README.md @@ -97,8 +97,15 @@ The format is simple. It's the same as python list index, but can select multipl - By default `:` selects everything ### View Info -![image](./images/ViewInfo.png) -Pythongossss's [View Info...](https://github.com/pythongosssss/ComfyUI-Custom-Scripts?tab=readme-ov-file#checkpointloraembedding-info) feature from ComfyUI-Custom-Scripts +![image](./images/ViewInfo.png) + +Pythongossss's [View Info...](https://github.com/pythongosssss/ComfyUI-Custom-Scripts?tab=readme-ov-file#checkpointloraembedding-info) feature from ComfyUI-Custom-Scripts + +To enable this feature go into Settings > Pysssss > ModelInfo > 🐍 Model Info - Lora Nodes/Widgets +And add the following at the end of the line: +``` +LoraLoaderVanilla.lora_name,LoraLoaderStackedVanilla.lora_name,LoraLoaderAdvanced.lora_name,LoraLoaderStackedAdvanced.lora_name,LoraTagsOnly.lora_name +``` ### Examples #### Example of normal workflow diff --git a/nodes_autotrigger.py b/nodes_autotrigger.py index 061af02..5483bf5 100644 --- a/nodes_autotrigger.py +++ b/nodes_autotrigger.py @@ -57,7 +57,6 @@ class LoraLoaderVanilla: self.loaded_lora = (lora_path, lora) model_lora, clip_lora = load_lora_for_models(model, clip, lora, strength_model, strength_clip) - return (model_lora, clip_lora, civitai_tags_list, meta_tags_list, lora_name) class LoraLoaderStackedVanilla: @@ -100,132 +99,6 @@ class LoraLoaderStackedVanilla: return (civitai_tags_list, meta_tags_list, loras, lora_name) -class LoraLoaderAdvanced: - def __init__(self): - self.loaded_lora = None - - @classmethod - def INPUT_TYPES(s): - LORA_LIST = sorted(folder_paths.get_filename_list("loras"), key=str.lower) - populate_items(LORA_LIST, "loras") - return { - "required": { - "model": ("MODEL",), - "lora_name": (LORA_LIST, ), - "strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "force_fetch": ("BOOLEAN", {"default": False}), - "enable_preview": ("BOOLEAN", {"default": False}), - "append_loraname_if_empty": ("BOOLEAN", {"default": False}), - }, - "optional": { - "clip": ("CLIP", ), - "override_lora_name":("STRING", {"forceInput": True}), - } - } - - RETURN_TYPES = ("MODEL", "CLIP", "LIST", "LIST", "STRING") - RETURN_NAMES = ("MODEL", "CLIP", "civitai_tags_list", "meta_tags_list", "lora_name") - FUNCTION = "load_lora" - CATEGORY = "autotrigger" - - def load_lora(self, model, lora_name, strength_model, strength_clip, force_fetch, enable_preview, append_loraname_if_empty, clip=None, override_lora_name=""): - if clip is None: - strength_clip=0 - if override_lora_name != "": - has_preview, prev = get_preview_path(override_lora_name, "loras") - prev = f"loras/{prev}" if has_preview else None - lora_name = {"content": override_lora_name, "image": prev, "type": "loras"} - - meta_tags_list = sort_tags_by_frequency(get_metadata(lora_name["content"], "loras")) - civitai_tags_list = load_and_save_tags(lora_name["content"], force_fetch) - - civitai_tags_list = append_lora_name_if_empty(civitai_tags_list, lora_name["content"], append_loraname_if_empty) - meta_tags_list = append_lora_name_if_empty(meta_tags_list, lora_name["content"], append_loraname_if_empty) - - lora_path = folder_paths.get_full_path("loras", lora_name["content"]) - lora = None - if self.loaded_lora is not None: - if self.loaded_lora[0] == lora_path: - lora = self.loaded_lora[1] - else: - temp = self.loaded_lora - self.loaded_lora = None - del temp - - if lora is None: - lora = load_torch_file(lora_path, safe_load=True) - self.loaded_lora = (lora_path, lora) - - model_lora, clip_lora = load_lora_for_models(model, clip, lora, strength_model, strength_clip) - if enable_preview: - _, preview = copy_preview_to_temp(lora_name["image"]) - if preview is not None: - preview_output = { - "filename": preview, - "subfolder": "lora_preview", - "type": "temp" - } - return {"ui": {"images": [preview_output]}, "result": (model_lora, clip_lora, civitai_tags_list, meta_tags_list, lora_name["content"])} - - - return (model_lora, clip_lora, civitai_tags_list, meta_tags_list, lora_name["content"]) - -class LoraLoaderStackedAdvanced: - @classmethod - def INPUT_TYPES(s): - LORA_LIST = folder_paths.get_filename_list("loras") - populate_items(LORA_LIST, "loras") - return { - "required": { - "lora_name": (LORA_LIST,), - "lora_weight": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "force_fetch": ("BOOLEAN", {"default": False}), - "enable_preview": ("BOOLEAN", {"default": False}), - "append_loraname_if_empty": ("BOOLEAN", {"default": False}), - }, - "optional": { - "lora_stack": ("LORA_STACK", ), - "override_lora_name":("STRING", {"forceInput": True}), - } - } - - RETURN_TYPES = ("LIST", "LIST", "LORA_STACK", "STRING") - RETURN_NAMES = ("civitai_tags_list", "meta_tags_list", "LORA_STACK", "lora_name") - FUNCTION = "set_stack" - #OUTPUT_NODE = False - CATEGORY = "autotrigger" - - def set_stack(self, lora_name, lora_weight, force_fetch, enable_preview, append_loraname_if_empty, lora_stack=None, override_lora_name=""): - if override_lora_name != "": - has_preview, prev = get_preview_path(override_lora_name, "loras") - prev = f"loras/{prev}" if has_preview else None - lora_name = {"content": override_lora_name, "image": prev, "type": "loras"} - - civitai_tags_list = load_and_save_tags(lora_name["content"], force_fetch) - - meta_tags = get_metadata(lora_name["content"], "loras") - meta_tags_list = sort_tags_by_frequency(meta_tags) - - civitai_tags_list = append_lora_name_if_empty(civitai_tags_list, lora_name["content"], append_loraname_if_empty) - meta_tags_list = append_lora_name_if_empty(meta_tags_list, lora_name["content"], append_loraname_if_empty) - - loras = [(lora_name["content"],lora_weight,lora_weight,)] - if lora_stack is not None: - loras.extend(lora_stack) - - if enable_preview: - _, preview = copy_preview_to_temp(lora_name["image"]) - if preview is not None: - preview_output = { - "filename": preview, - "subfolder": "lora_preview", - "type": "temp" - } - return {"ui": {"images": [preview_output]}, "result": (civitai_tags_list, meta_tags_list, loras, lora_name["content"])} - - return {"result": (civitai_tags_list, meta_tags_list, loras, lora_name["content"])} - class LoraTagsOnly: @classmethod def INPUT_TYPES(s): @@ -262,8 +135,8 @@ class LoraTagsOnly: NODE_CLASS_MAPPINGS = { "LoraLoaderVanilla": LoraLoaderVanilla, "LoraLoaderStackedVanilla": LoraLoaderStackedVanilla, - "LoraLoaderAdvanced": LoraLoaderAdvanced, - "LoraLoaderStackedAdvanced": LoraLoaderStackedAdvanced, + "LoraLoaderAdvanced": LoraLoaderVanilla, + "LoraLoaderStackedAdvanced": LoraLoaderStackedVanilla, "LoraTagsOnly": LoraTagsOnly, } diff --git a/utils.py b/utils.py index baa2254..9bf2615 100644 --- a/utils.py +++ b/utils.py @@ -3,63 +3,6 @@ import hashlib import json import os import requests -import shutil - -def get_preview_path(name, type): - file_name = os.path.splitext(name)[0] - file_path = folder_paths.get_full_path(type, name) - - if file_path is None: - print(f"Unable to get path for {type} {name}") - return None - - file_path_no_ext = os.path.splitext(file_path)[0] - item_image=None - for ext in ["png", "jpg", "jpeg", "gif"]: - if item_image is not None: - break - for ext2 in ["", ".preview"]: - has_image = os.path.isfile(file_path_no_ext + ext2 + "." + ext) - if has_image: - item_image = f"{file_name}{ext2}.{ext}" - break - - return has_image, item_image - - -def copy_preview_to_temp(file_name): - if file_name is None: - return None, None - base_name = os.path.basename(file_name) - lora_less = "/".join(file_name.split("/")[1:]) - - file_path = folder_paths.get_full_path("loras", lora_less) - if file_path is None: - return None, None - - temp_path = folder_paths.get_temp_directory() - preview_path = os.path.join(temp_path, "lora_preview") - if not os.path.isdir(preview_path) : - os.makedirs(preview_path) - preview_path = os.path.join(preview_path, base_name) - - - shutil.copyfile(file_path, preview_path) - return preview_path, base_name - -# add previews in selectors -def populate_items(names, type): - for idx, item_name in enumerate(names): - - has_image, item_image = get_preview_path(item_name, type) - - names[idx] = { - "content": item_name, - "image": f"{type}/{item_image}" if has_image else None, - "type": "loras", - } - names.sort(key=lambda i: i["content"].lower()) - def load_json_from_file(file_path): try: @@ -133,14 +76,6 @@ def load_and_save_tags(lora_name, force_fetch): return output_tags_list -def show_list(list_input): - i = 0 - output = "" - for debug in list_input: - output += f"{i} : {debug}\n" - i+=1 - return output - def get_metadata(filepath, type): filepath = folder_paths.get_full_path(type, filepath) with open(filepath, "rb") as file: diff --git a/web/js/betterCombos.js b/web/js/betterCombos.js new file mode 100644 index 0000000..a8f21df --- /dev/null +++ b/web/js/betterCombos.js @@ -0,0 +1,469 @@ +// COMPLETELY STOLEN FROM https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/web/js/betterCombos.js + +import { app } from "../../../scripts/app.js"; +import { $el } from "../../../scripts/ui.js"; +import { api } from "../../../scripts/api.js"; + +const LORA_LOADER = "LoraLoaderAdvanced" +const LORA_LOADER_STACKED = "LoraLoaderStackedAdvanced" +const IMAGE_WIDTH = 384; +const IMAGE_HEIGHT = 384; + +function getType(node) { + return "loras"; +} + +function getWidgetName(type) { + return type === "checkpoints" ? "ckpt_name" : "lora_name"; +} + +function encodeRFC3986URIComponent(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`); +} + +const calculateImagePosition = (el, bodyRect) => { + let { top, left, right } = el.getBoundingClientRect(); + const { width: bodyWidth, height: bodyHeight } = bodyRect; + + const isSpaceRight = right + IMAGE_WIDTH <= bodyWidth; + if (isSpaceRight) { + left = right; + } else { + left -= IMAGE_WIDTH; + } + + top = top - IMAGE_HEIGHT / 2; + if (top + IMAGE_HEIGHT > bodyHeight) { + top = bodyHeight - IMAGE_HEIGHT; + } + if (top < 0) { + top = 0; + } + + return { left: Math.round(left), top: Math.round(top), isLeft: !isSpaceRight }; +}; + +function showImage(relativeToEl, imageEl) { + const bodyRect = document.body.getBoundingClientRect(); + if (!bodyRect) return; + + const { left, top, isLeft } = calculateImagePosition(relativeToEl, bodyRect); + + imageEl.style.left = `${left}px`; + imageEl.style.top = `${top}px`; + + if (isLeft) { + imageEl.classList.add("left"); + } else { + imageEl.classList.remove("left"); + } + + document.body.appendChild(imageEl); +} + +let imagesByType = {}; +const loadImageList = async (type) => { + imagesByType[type] = await (await api.fetchApi(`/pysssss/images/${type}`)).json(); +}; + +app.registerExtension({ + name: "autotrigger.Combo++", + init() { + const displayOptions = { "List (normal)": 0, "Tree (subfolders)": 1, "Thumbnails (grid)": 2 }; + const displaySetting = app.ui.settings.addSetting({ + id: "autotrigger.Combo++.Submenu", + name: "🐍 Lora & Checkpoint loader display mode", + defaultValue: 1, + type: "combo", + options: (value) => { + value = +value; + + return Object.entries(displayOptions).map(([k, v]) => ({ + value: v, + text: k, + selected: k === value, + })); + }, + }); + + $el("style", { + textContent: ` + .pysssss-combo-image { + position: absolute; + left: 0; + top: 0; + width: ${IMAGE_WIDTH}px; + height: ${IMAGE_HEIGHT}px; + object-fit: contain; + object-position: top left; + z-index: 9999; + } + .pysssss-combo-image.left { + object-position: top right; + } + .pysssss-combo-folder { opacity: 0.7 } + .pysssss-combo-folder-arrow { display: inline-block; width: 15px; } + .pysssss-combo-folder:hover { background-color: rgba(255, 255, 255, 0.1); } + .pysssss-combo-prefix { display: none } + + /* Special handling for when the filter input is populated to revert to normal */ + .litecontextmenu:has(input:not(:placeholder-shown)) .pysssss-combo-folder-contents { + display: block !important; + } + .litecontextmenu:has(input:not(:placeholder-shown)) .pysssss-combo-folder { + display: none; + } + .litecontextmenu:has(input:not(:placeholder-shown)) .pysssss-combo-prefix { + display: inline; + } + .litecontextmenu:has(input:not(:placeholder-shown)) .litemenu-entry { + padding-left: 2px !important; + } + + /* Grid mode */ + .pysssss-combo-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 10px; + overflow-x: hidden; + max-width: 60vw; + } + .pysssss-combo-grid .comfy-context-menu-filter { + grid-column: 1 / -1; + position: sticky; + top: 0; + } + .pysssss-combo-grid .litemenu-entry { + word-break: break-word; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + } + .pysssss-combo-grid .litemenu-entry:before { + content: ""; + display: block; + width: 100%; + height: 250px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; + /* No-image image attribution: Picture icons created by Pixel perfect - Flaticon */ + background-image: var(--background-image, url(extensions/ComfyUI-Custom-Scripts/js/assets/no-image.png)); + } + + `, + parent: document.body, + }); + const p1 = loadImageList("checkpoints"); + const p2 = loadImageList("loras"); + + const refreshComboInNodes = app.refreshComboInNodes; + app.refreshComboInNodes = async function () { + const r = await Promise.all([ + refreshComboInNodes.apply(this, arguments), + loadImageList("checkpoints").catch(() => {}), + loadImageList("loras").catch(() => {}), + ]); + return r[0]; + }; + + const imageHost = $el("img.pysssss-combo-image"); + + const positionMenu = (menu, fillWidth) => { + // compute best position + let left = app.canvas.last_mouse[0] - 10; + let top = app.canvas.last_mouse[1] - 10; + + const body_rect = document.body.getBoundingClientRect(); + const root_rect = menu.getBoundingClientRect(); + + 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; + + menu.style.left = `${left}px`; + menu.style.top = `${top}px`; + if (fillWidth) { + menu.style.right = "10px"; + } + }; + + const updateMenu = async (menu, type) => { + try { + await p1; + await p2; + } catch (error) { + console.error(error); + console.error("Error loading pysssss.betterCombos data"); + } + + // Clamp max height so it doesn't overflow the screen + const position = menu.getBoundingClientRect(); + const maxHeight = window.innerHeight - position.top - 20; + menu.style.maxHeight = `${maxHeight}px`; + + const images = imagesByType[type]; + const items = menu.querySelectorAll(".litemenu-entry"); + + // Add image handler to items + const addImageHandler = (item) => { + const text = item.getAttribute("data-value").trim(); + if (images[text]) { + const textNode = document.createTextNode("*"); + item.appendChild(textNode); + + item.addEventListener( + "mouseover", + () => { + imageHost.src = `/pysssss/view/${encodeRFC3986URIComponent(images[text])}?${+new Date()}`; + document.body.appendChild(imageHost); + showImage(item, imageHost); + }, + { passive: true } + ); + item.addEventListener( + "mouseout", + () => { + imageHost.remove(); + }, + { passive: true } + ); + item.addEventListener( + "click", + () => { + imageHost.remove(); + }, + { passive: true } + ); + } + }; + + const createTree = () => { + // Create a map to store folder structures + const folderMap = new Map(); + const rootItems = []; + const splitBy = (navigator.platform || navigator.userAgent).includes("Win") ? /\/|\\/ : /\//; + const itemsSymbol = Symbol("items"); + + // First pass - organize items into folder structure + for (const item of items) { + const path = item.getAttribute("data-value").split(splitBy); + + // Remove path from visible text + item.textContent = path[path.length - 1]; + if (path.length > 1) { + // Add the prefix path back in so it can be filtered on + const prefix = $el("span.pysssss-combo-prefix", { + textContent: path.slice(0, -1).join("/") + "/", + }); + item.prepend(prefix); + } + + addImageHandler(item); + + if (path.length === 1) { + rootItems.push(item); + continue; + } + + // Temporarily remove the item from current position + item.remove(); + + // Create folder hierarchy + let currentLevel = folderMap; + for (let i = 0; i < path.length - 1; i++) { + const folder = path[i]; + if (!currentLevel.has(folder)) { + currentLevel.set(folder, new Map()); + } + currentLevel = currentLevel.get(folder); + } + + // Store the actual item in the deepest folder + if (!currentLevel.has(itemsSymbol)) { + currentLevel.set(itemsSymbol, []); + } + currentLevel.get(itemsSymbol).push(item); + } + + const createFolderElement = (name) => { + const folder = $el("div.litemenu-entry.pysssss-combo-folder", { + innerHTML: ` ${name}`, + style: { paddingLeft: "5px" }, + }); + return folder; + }; + + const insertFolderStructure = (parentElement, map, level = 0) => { + for (const [folderName, content] of map.entries()) { + if (folderName === itemsSymbol) continue; + + const folderElement = createFolderElement(folderName); + folderElement.style.paddingLeft = `${level * 10 + 5}px`; + parentElement.appendChild(folderElement); + + const childContainer = $el("div.pysssss-combo-folder-contents", { + style: { display: "none" }, + }); + + // Add items in this folder + const items = content.get(itemsSymbol) || []; + for (const item of items) { + item.style.paddingLeft = `${(level + 1) * 10 + 14}px`; + childContainer.appendChild(item); + } + + // Recursively add subfolders + insertFolderStructure(childContainer, content, level + 1); + parentElement.appendChild(childContainer); + + // Add click handler for folder + folderElement.addEventListener("click", (e) => { + e.stopPropagation(); + const arrow = folderElement.querySelector(".pysssss-combo-folder-arrow"); + const contents = folderElement.nextElementSibling; + if (contents.style.display === "none") { + contents.style.display = "block"; + arrow.textContent = "▼"; + } else { + contents.style.display = "none"; + arrow.textContent = "▶"; + } + }); + } + }; + + insertFolderStructure(items[0]?.parentElement || menu, folderMap); + positionMenu(menu); + }; + + const addImageData = (item) => { + const text = item.getAttribute("data-value").trim(); + if (images[text]) { + item.style.setProperty("--background-image", `url(/pysssss/view/${encodeRFC3986URIComponent(images[text])})`); + } + }; + + if (displaySetting.value === 1 || displaySetting.value === true) { + createTree(); + } else if (displaySetting.value === 2) { + menu.classList.add("pysssss-combo-grid"); + + for (const item of items) { + addImageData(item); + } + positionMenu(menu, true); + } else { + for (const item of items) { + addImageHandler(item); + } + } + }; + + const mutationObserver = new MutationObserver((mutations) => { + const node = app.canvas.current_node; + + if (!node || (node.comfyClass !== LORA_LOADER && node.comfyClass !== LORA_LOADER_STACKED)) { + return; + } + + for (const mutation of mutations) { + for (const removed of mutation.removedNodes) { + if (removed.classList?.contains("litecontextmenu")) { + imageHost.remove(); + } + } + + for (const added of mutation.addedNodes) { + if (added.classList?.contains("litecontextmenu")) { + const overWidget = app.canvas.getWidgetAtCursor(); + const type = getType(node); + if (overWidget?.name === getWidgetName(type)) { + requestAnimationFrame(() => { + // Bad hack to prevent showing on right click menu by checking for the filter input + if (!added.querySelector(".comfy-context-menu-filter")) return; + updateMenu(added, type); + }); + } + return; + } + } + } + }); + mutationObserver.observe(document.body, { childList: true, subtree: false }); + }, + async beforeRegisterNodeDef(nodeType, nodeData, app) { + const isLora = (nodeData.name === LORA_LOADER || nodeData.name === LORA_LOADER_STACKED); + if (isLora) { + const onAdded = nodeType.prototype.onAdded; + nodeType.prototype.onAdded = function () { + onAdded?.apply(this, arguments); + + + + const modelWidget = this.widgets[0]; + const modelCb = modelWidget.callback; + let prev = undefined; + modelWidget.callback = function () { + let ret = modelCb?.apply(this, arguments) ?? modelWidget.value; + if (typeof ret === "object" && "content" in ret) { + ret = ret.content; + modelWidget.value = ret; + } + let v = ret; + if (prev !== v) { + prev = v; + } + return ret; + }; + setTimeout(() => { + modelWidget.callback(); + }, 30); + }; + } + + const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; + nodeType.prototype.getExtraMenuOptions = function (_, options) { + if (this.imgs) { + // If this node has images then we add an open in new tab item + let img; + if (this.imageIndex != null) { + // An image is selected so select that + img = this.imgs[this.imageIndex]; + } else if (this.overIndex != null) { + // No image is selected but one is hovered + img = this.imgs[this.overIndex]; + } + if (img) { + const nodes = app.graph._nodes.filter((n) => n.comfyClass === LORA_LOADER || n.comfyClass === LORA_LOADER_STACKED); + if (nodes.length) { + options.unshift({ + content: "Save as Preview", + submenu: { + options: nodes.map((n) => ({ + content: n.widgets[0].value, + callback: async () => { + const url = new URL(img.src); + await api.fetchApi("/pysssss/save/" + encodeRFC3986URIComponent(`${getType(n)}/${n.widgets[0].value}`), { + method: "POST", + body: JSON.stringify({ + filename: url.searchParams.get("filename"), + subfolder: url.searchParams.get("subfolder"), + type: url.searchParams.get("type"), + }), + headers: { + "content-type": "application/json", + }, + }); + loadImageList(getType(n)); + }, + })), + }, + }); + } + } + } + return getExtraMenuOptions?.apply(this, arguments); + }; + }, +}); \ No newline at end of file diff --git a/web/js/loraInfo.js b/web/js/loraInfo.js deleted file mode 100644 index ca4181a..0000000 --- a/web/js/loraInfo.js +++ /dev/null @@ -1,36 +0,0 @@ -import { app } from "../../../scripts/app.js"; -import { LoraInfoDialog } from "../../ComfyUI-Custom-Scripts/js/modelInfo.js"; - -const infoHandlers = { - "LoraLoaderVanilla":true, - "LoraLoaderStackedVanilla":true, - "LoraLoaderAdvanced":true, - "LoraLoaderStackedAdvanced":true -} - -app.registerExtension({ - name: "autotrigger.LoraInfo", - beforeRegisterNodeDef(nodeType) { - if (! infoHandlers[nodeType.comfyClass]) { - return; - } - const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; - nodeType.prototype.getExtraMenuOptions = function (_, options) { - let value = this.widgets[0].value; - if (!value) { - return; - } - if (value.content) { - value = value.content; - } - options.unshift({ - content: "View info...", - callback: async () => { - new LoraInfoDialog(value).show("loras", value); - }, - }); - - return getExtraMenuOptions?.apply(this, arguments); - }; - } -}); \ No newline at end of file