diff --git a/py/image_comparer.py b/py/image_comparer.py index f3b4942..079ebdb 100644 --- a/py/image_comparer.py +++ b/py/image_comparer.py @@ -9,6 +9,7 @@ class RgthreeImageComparer(PreviewImage): NAME = get_name('Image Comparer') CATEGORY = get_category() FUNCTION = "compare_images" + DESCRIPTION = "Compares two images with a hover slider, or click from properties." @classmethod def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring diff --git a/py/pyproject.py b/py/pyproject.py index ba21a8d..7622911 100644 --- a/py/pyproject.py +++ b/py/pyproject.py @@ -1,7 +1,6 @@ import os import re import json -import requests from .utils import set_dict_value @@ -45,14 +44,29 @@ LOGO_URL: str = _DATA['tool']['comfy']['Icon'] if not LOGO_URL.endswith('.svg'): raise ValueError('Bad logo url.') -# Fetch the logo so we have any updated markup. -try: - LOGO_SVG = requests.get( - LOGO_URL, - headers={"user-agent": f"rgthree-comfy/{VERSION}"}, - timeout=10 - ).text - LOGO_SVG = re.sub(r'(id="bg".*fill=)"[^\"]+"', r'\1"{bg}"', LOGO_SVG) - LOGO_SVG = re.sub(r'(id="fg".*fill=)"[^\"]+"', r'\1"{fg}"', LOGO_SVG) -except Exception: - LOGO_SVG = '' +LOGO_SVG = None +async def get_logo_svg(): + import aiohttp + global LOGO_SVG + if LOGO_SVG is not None: + return LOGO_SVG + # Fetch the logo so we have any updated markup. + try: + async with aiohttp.ClientSession( + trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=True) + ) as session: + headers = { + "user-agent": f"rgthree-comfy/{VERSION}", + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache', + 'Expires': '0' + } + async with session.get(LOGO_URL, headers=headers) as resp: + LOGO_SVG = await resp.text() + LOGO_SVG = '' + LOGO_SVG = re.sub(r'(id="bg".*fill=)"[^\"]+"', r'\1"{bg}"', LOGO_SVG) + LOGO_SVG = re.sub(r'(id="fg".*fill=)"[^\"]+"', r'\1"{fg}"', LOGO_SVG) + except Exception: + LOGO_SVG = '' + print(LOGO_SVG) + return LOGO_SVG diff --git a/py/server/routes_config.py b/py/server/routes_config.py index 21339d1..d67b159 100644 --- a/py/server/routes_config.py +++ b/py/server/routes_config.py @@ -4,12 +4,13 @@ from aiohttp import web from server import PromptServer -from ..pyproject import LOGO_SVG +from ..pyproject import get_logo_svg from .utils_server import is_param_truthy, get_param from ..config import get_config, set_user_config, refresh_config routes = PromptServer.instance.routes + @routes.get('/rgthree/config.js') def api_get_user_config_file(request): """ Returns the user configuration as a javascript file. """ @@ -36,9 +37,29 @@ async def api_set_user_config(request): @routes.get('/rgthree/logo.svg') -async def get_logo(request): +async def get_logo(request, as_markup=False): """ Returns the rgthree logo with color config. """ bg = get_param(request, 'bg', 'transparent') fg = get_param(request, 'fg', '#111111') - resp = LOGO_SVG.format(bg=bg, fg=fg) + w = get_param(request, 'w') + h = get_param(request, 'h') + css_class = get_param(request, 'cssClass') + svg = await get_logo_svg() + resp = svg.format(bg=bg, fg=fg) + if w is not None: + resp = re.sub(r'(]*?)width="[^\"]+"', r'\1', resp) + resp = re.sub(r']*?)height="[^\"]+"', r'\1', resp) + resp = re.sub(r' { rgthreeButton.iconElement.innerHTML = t;}); rgthreeButton.withPopup( new ComfyPopup( - { target: rgthreeButton.element, classList: "rgthree-top-menu" }, + {target: rgthreeButton.element, classList: "rgthree-top-menu"}, createElement("menu", { children: [ createElement("li", { @@ -61,7 +61,7 @@ function addRgthreeTopBarButtons() { const bookmarksListEl = createElement("menu"); bookmarksListEl.appendChild( createElement("li.rgthree-message", { - child: createElement("span", { text: "No bookmarks in current workflow." }), + child: createElement("span", {text: "No bookmarks in current workflow."}), }), ); const bookmarksButton = new ComfyButton({ @@ -70,7 +70,7 @@ function addRgthreeTopBarButtons() { app, }); const bookmarksPopup = new ComfyPopup( - { target: bookmarksButton.element, classList: "rgthree-top-menu" }, + {target: bookmarksButton.element, classList: "rgthree-top-menu"}, bookmarksListEl, ); bookmarksPopup.addEventListener("open", () => { @@ -92,7 +92,7 @@ function addRgthreeTopBarButtons() { } else { bookmarksListEl.appendChild( createElement("li.rgthree-message", { - child: createElement("span", { text: "No bookmarks in current workflow." }), + child: createElement("span", {text: "No bookmarks in current workflow."}), }), ); } diff --git a/src_web/comfyui/feature_group_fast_toggle.ts b/src_web/comfyui/feature_group_fast_toggle.ts index 6b4f83e..260d7cb 100644 --- a/src_web/comfyui/feature_group_fast_toggle.ts +++ b/src_web/comfyui/feature_group_fast_toggle.ts @@ -97,7 +97,7 @@ app.registerExtension({ message: "No output nodes for group!", }); } else { - rgthree.queueOutputNodes(outputNodes.map((n) => n.id)); + rgthree.queueOutputNodes(outputNodes); } } else { const toggleMode = TOGGLE_TO_MODE.get(toggleAction); diff --git a/src_web/comfyui/menu_queue_node.ts b/src_web/comfyui/menu_queue_node.ts index c6a9cc1..c8e5507 100644 --- a/src_web/comfyui/menu_queue_node.ts +++ b/src_web/comfyui/menu_queue_node.ts @@ -21,7 +21,7 @@ function showQueueNodesMenuIfOutputNodesAreSelected( content: `Queue Selected Output Nodes (rgthree)  `, className: "rgthree-contextmenu-item", callback: () => { - rgthree.queueOutputNodes(outputNodes.map((n) => n.id)); + rgthree.queueOutputNodes(outputNodes); }, disabled: !outputNodes.length, }; @@ -50,7 +50,7 @@ function showQueueGroupNodesMenuIfGroupIsSelected( content: `Queue Group Output Nodes (rgthree)  `, className: "rgthree-contextmenu-item", callback: () => { - outputNodes && rgthree.queueOutputNodes(outputNodes.map((n) => n.id)); + outputNodes && rgthree.queueOutputNodes(outputNodes); }, disabled: !outputNodes?.length, }; diff --git a/src_web/comfyui/rgthree.ts b/src_web/comfyui/rgthree.ts index a32f59f..cc1e072 100644 --- a/src_web/comfyui/rgthree.ts +++ b/src_web/comfyui/rgthree.ts @@ -255,6 +255,8 @@ class Rgthree extends EventTarget { // Comfy/LiteGraph states so nodes and tell what the hell is going on. canvasCurrentlyCopyingToClipboard = false; canvasCurrentlyCopyingToClipboardWithMultipleNodes = false; + canvasCurrentlyPastingFromClipboard = false; + canvasCurrentlyPastingFromClipboardWithMultipleNodes = false; initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff: any = null; private readonly isMac: boolean = !!( @@ -419,6 +421,14 @@ class Rgthree extends EventTarget { rgthree.canvasCurrentlyCopyingToClipboardWithMultipleNodes = false; }; + // [🤮] Pasting from clipboard. + const pasteFromClipboard = LGraphCanvas.prototype.pasteFromClipboard; + LGraphCanvas.prototype.pasteFromClipboard = function (...args: any[]) { + rgthree.canvasCurrentlyPastingFromClipboard = true; + pasteFromClipboard.apply(this, [...arguments] as any); + rgthree.canvasCurrentlyPastingFromClipboard = false; + }; + // [⭐] Make it so when we add a group, we get to name it immediately. const onGroupAdd = LGraphCanvas.onGroupAdd; LGraphCanvas.onGroupAdd = function (...args: any[]) { @@ -648,7 +658,13 @@ class Rgthree extends EventTarget { * Wraps an `app.queuePrompt` call setting a specific node id that we will inspect and change the * serialized graph right before being sent (below, in our `api.queuePrompt` override). */ - async queueOutputNodes(nodeIds: NodeId[]) { + async queueOutputNodes(nodes: LGraphNode[]) { + // We can use just these next two lines when + // https://github.com/ltdrdata/ComfyUI-Inspire-Pack/pull/258 is pulled. Until then, we'll keep + // our custom logic as ComfyUI-Inspire-Pack would cause it not to work. + // app.canvas.selectItems(nodes); + // app.extensionManager.command.execute('Comfy.QueueSelectedOutputNodes'); + const nodeIds = nodes.map(n => n.id); try { this.queueNodeIds = nodeIds; await app.queuePrompt(0); @@ -729,7 +745,7 @@ class Rgthree extends EventTarget { // then we only want to queue those nodes, by rewriting the api format (prompt 'output' field) // so only those are evaluated. const apiQueuePrompt = api.queuePrompt as Function; - api.queuePrompt = async function (index: number, prompt: ComfyApiPrompt) { + api.queuePrompt = async function (index: number, prompt: ComfyApiPrompt, ...args: any[]) { if (rgthree.queueNodeIds?.length && prompt.output) { const oldOutput = prompt.output; let newOutput = {}; @@ -742,7 +758,7 @@ class Rgthree extends EventTarget { workflow: prompt.workflow, output: prompt.output, }); - const response = apiQueuePrompt.apply(app, [index, prompt]); + const response = apiQueuePrompt.apply(app, [index, prompt, ...args]); rgthree.dispatchCustomEvent("comfy-api-queue-prompt-end"); return response; }; diff --git a/src_web/comfyui/seed.ts b/src_web/comfyui/seed.ts index 8e749fa..2d44f29 100644 --- a/src_web/comfyui/seed.ts +++ b/src_web/comfyui/seed.ts @@ -41,30 +41,40 @@ class RgthreeSeed extends RgthreeBaseServerNode { static override exposedActions = ["Randomize Each Time", "Use Last Queued Seed"]; + static "@randomMax" = {type: "number"}; + static "@randomMin" = {type: "number"}; + lastSeed?: number = undefined; serializedCtx: SeedSerializedCtx = {}; seedWidget!: IWidget; lastSeedButton!: IWidget; lastSeedValue: IWidget | null = null; - randMax = 1125899906842624; - // We can have a full range of seeds, including negative. But, for the randomRange we'll - // only generate positives, since that's what folks assume. - // const min = Math.max(-1125899906842624, this.seedWidget.options.min); - randMin = 0; - randomRange = 1125899906842624; - private handleApiHijackingBound = this.handleApiHijacking.bind(this); constructor(title = RgthreeSeed.title) { super(title); + this.properties["randomMax"] = 1125899906842624; + // We can have a full range of seeds, including negative. But, for the randomRange we'll + // only generate positives, since that's what folks assume. + this.properties["randomMin"] = 0; + rgthree.addEventListener( "comfy-api-queue-prompt-before", this.handleApiHijackingBound as EventListener, ); } + override onPropertyChanged(prop: string, value: unknown, prevValue?: unknown): boolean { + if (prop === 'randomMax') { + this.properties["randomMax"] = Math.min(1125899906842624, Number(value as number)); + } else if (prop === 'randomMin') { + this.properties["randomMin"] = Math.max(-1125899906842624, Number(value as number)); + } + return true; + } + override onRemoved() { rgthree.addEventListener( "comfy-api-queue-prompt-before", @@ -101,14 +111,6 @@ class RgthreeSeed extends RgthreeBaseServerNode { } } - // Update random values in case seed comes down with different options. - let step = this.seedWidget.options.step || 1; - this.randMax = Math.min(1125899906842624, this.seedWidget.options.max ?? 0); - // We can have a full range of seeds, including negative. But, for the randomRange we'll - // only generate positives, since that's what folks assume. - this.randMin = Math.max(0, this.seedWidget.options.min ?? 0); - this.randomRange = (this.randMax - Math.max(0, this.randMin)) / (step / 10); - this.addWidget( "button", "🎲 Randomize Each Time", @@ -124,8 +126,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { "🎲 New Fixed Random", "", () => { - this.seedWidget.value = - Math.floor(Math.random() * this.randomRange) * (step / 10) + this.randMin; + this.seedWidget.value = this.generateRandomSeed(); }, {serialize: false}, ); @@ -144,6 +145,18 @@ class RgthreeSeed extends RgthreeBaseServerNode { this.lastSeedButton.disabled = true; } + generateRandomSeed() { + let step = this.seedWidget.options.step || 1; + const randomMin = Number(this.properties['randomMin'] || 0); + const randomMax = Number(this.properties['randomMax'] || 1125899906842624); + const randomRange = (randomMax - randomMin) / (step / 10); + let seed = Math.floor(Math.random() * randomRange) * (step / 10) + randomMin; + if (SPECIAL_SEEDS.includes(seed)) { + seed = 0; + } + return seed; + } + override getExtraMenuOptions(canvas: LGraphCanvas, options: IContextMenuValue[]) { super.getExtraMenuOptions?.apply(this, [...arguments] as any); options.splice(options.length - 1, 0, { @@ -263,10 +276,7 @@ class RgthreeSeed extends RgthreeBaseServerNode { // If we don't have a seed to use, or it's special seed (like we incremented into one), then // we randomize. if (seedToUse == null || SPECIAL_SEEDS.includes(seedToUse)) { - seedToUse = - Math.floor(Math.random() * this.randomRange) * - ((this.seedWidget.options.step || 1) / 10) + - this.randMin; + seedToUse = this.generateRandomSeed(); } } diff --git a/src_web/comfyui/services/fast_groups_service.ts b/src_web/comfyui/services/fast_groups_service.ts index 49c8ea8..dc765e8 100644 --- a/src_web/comfyui/services/fast_groups_service.ts +++ b/src_web/comfyui/services/fast_groups_service.ts @@ -1,4 +1,4 @@ -import type {LGraphGroup} from "@comfyorg/frontend"; +import type {LGraphGroup as TLGraphGroup} from "@comfyorg/frontend"; import type {BaseFastGroupsModeChanger} from "../fast_groups_muter.js"; import {app} from "scripts/app.js"; @@ -6,6 +6,8 @@ import {getGraphDependantNodeKey, getGroupNodes, reduceNodesDepthFirst} from ".. type Vector4 = [number, number, number, number]; + + /** * A service that keeps global state that can be shared by multiple FastGroupsMuter or * FastGroupsBypasser nodes rather than calculate it on it's own. @@ -16,9 +18,9 @@ class FastGroupsService { private msLastAlpha = 0; private msLastPosition = 0; - private groupsUnsorted: LGraphGroup[] = []; - private groupsSortedAlpha: LGraphGroup[] = []; - private groupsSortedPosition: LGraphGroup[] = []; + private groupsUnsorted: TLGraphGroup[] = []; + private groupsSortedAlpha: TLGraphGroup[] = []; + private groupsSortedPosition: TLGraphGroup[] = []; private readonly fastGroupNodes: BaseFastGroupsModeChanger[] = []; @@ -118,10 +120,13 @@ class FastGroupsService { } /** - * This overrides `LGraphGroup.prototype.recomputeInsideNodes` to be much more efficient when + * This overrides `TLGraphGroup.prototype.recomputeInsideNodes` to be much more efficient when * calculating for many groups at once (only compute all nodes once in `getBoundingsForAllNodes`). */ - recomputeInsideNodesForGroup(group: LGraphGroup) { + recomputeInsideNodesForGroup(group: TLGraphGroup) { + // If the canvas is currently being dragged (includes if a group is being dragged around) then + // don't recompute anything. + if (app.canvas.isDragging) return; const cachedBoundings = this.getBoundingsForAllNodes(); const nodes = group.graph!.nodes; group._children.clear(); diff --git a/src_web/common/media/svgs.ts b/src_web/common/media/svgs.ts index 2ee479f..b468cd0 100644 --- a/src_web/common/media/svgs.ts +++ b/src_web/common/media/svgs.ts @@ -1,13 +1,37 @@ -import { createElement as $el } from "../utils_dom.js"; +import {createElement as $el} from "../utils_dom.js"; // Some svg repo : https://www.svgrepo.com/svg/326731/open-outline -export const logoRgthree = ``; +export let logoRgthree: string = ""; + +export async function logoRgthreeAsync(): Promise { + if (logoRgthree) return logoRgthree; + let baseUrl = null; + if (window.location.pathname.includes("/rgthree/")) { + // Try to find how many relatives paths we need to go back to hit ./rgthree/api + const parts = window.location.pathname.split("/rgthree/")[1]?.split("/"); + if (parts && parts.length) { + baseUrl = parts.map(() => "../").join("") + "rgthree"; + } + } + baseUrl = baseUrl || "./rgthree"; + return fetch(`${baseUrl}/logo_markup.svg?fg=currentColor&cssClass=rgthree-logo&w=auto&h=auto`) + .then((r) => r.text()) + .then((t) => { + if (t.length < 100) { + t = ``; + } + logoRgthree = t; + return t; + }); +} +// Kick it off to cache upfront. +logoRgthreeAsync(); export const github = `