Migrate app.js (#12)

* Rename js to ts

* Fix all tsc errors

* nit
This commit is contained in:
Chenlei Hu
2024-06-14 17:11:19 -04:00
committed by GitHub
parent 23074eb823
commit 1376459cd8
10 changed files with 213 additions and 49 deletions

View File

@@ -10,7 +10,7 @@
<script type="text/javascript" src="/lib/litegraph.core.js"></script>
<script type="text/javascript" src="/lib/litegraph.extensions.js" defer></script>
<script type="module">
import { app } from "./src/scripts/app.js";
import { app } from "./src/scripts/app";
(async () => {
await app.setup();
window.app = app;

View File

@@ -1,11 +1,13 @@
import { ComfyLogging } from "./logging";
import { ComfyWidgets, initWidgets } from "./widgets";
import { ComfyWidgetConstructor, ComfyWidgets, initWidgets } from "./widgets";
import { ComfyUI, $el } from "./ui";
import { api } from "./api";
import { defaultGraph } from "./defaultGraph.js";
import { getPngMetadata, getWebpMetadata, importA1111, getLatentMetadata } from "./pnginfo";
import { addDomClippingSetting } from "./domWidget";
import { createImageHost, calculateImageGrid } from "./ui/imagePreview.js"
import type { ComfyExtension } from "/types/comfy";
import type { IWidget, LGraph, LGraphCanvas, LGraphNode } from "/types/litegraph";
export const ANIM_PREVIEW_WIDGET = "$$comfy_animation_preview"
@@ -49,6 +51,31 @@ export class ComfyApp {
static open_maskeditor = null;
static clipspace_return_node = null;
ui: ComfyUI;
logging: ComfyLogging;
extensions: ComfyExtension[];
nodeOutputs: Record<string, any>;
nodePreviewImages: Record<string, typeof Image>;
shiftDown: boolean;
graph: LGraph;
enableWorkflowViewRestore: any;
canvas: LGraphCanvas;
dragOverNode: LGraphNode | null;
canvasEl: HTMLCanvasElement;
// x, y, scale
zoom_drag_start: [number, number, number] | null;
lastNodeErrors: any[] | null;
runningNodeId: number | null;
lastExecutionError: { node_id: number } | null;
progress: { value: number, max: number } | null;
configuringGraph: boolean;
isNewUserSession: boolean;
// Are there any other options than "server"?
storageLocation: string;
multiUserServer: boolean;
ctx: CanvasRenderingContext2D;
widgets: Record<string, ComfyWidgetConstructor>;
constructor() {
this.ui = new ComfyUI(this);
this.logging = new ComfyLogging(this);
@@ -194,13 +221,19 @@ export class ComfyApp {
}
if(ComfyApp.clipspace.widgets) {
ComfyApp.clipspace.widgets.forEach(({ type, name, value }) => {
// @ts-ignore
const prop = Object.values(node.widgets).find(obj => obj.type === type && obj.name === name);
// @ts-ignore
if (prop && prop.type != 'button') {
// @ts-ignore
if(prop.type != 'image' && typeof prop.value == "string" && value.filename) {
// @ts-ignore
prop.value = (value.subfolder?value.subfolder+'/':'') + value.filename + (value.type?` [${value.type}]`:'');
}
else {
// @ts-ignore
prop.value = value;
// @ts-ignore
prop.callback(value);
}
}
@@ -264,8 +297,10 @@ export class ComfyApp {
}
#addRestoreWorkflowView() {
// @ts-ignore
const serialize = LGraph.prototype.serialize;
const self = this;
// @ts-ignore
LGraph.prototype.serialize = function() {
const workflow = serialize.apply(this, arguments);
@@ -327,7 +362,7 @@ export class ComfyApp {
const canvas = $el("canvas", {
width: img.naturalWidth,
height: img.naturalHeight,
});
}) as HTMLCanvasElement;
const ctx = canvas.getContext("2d");
let image;
if (typeof window.createImageBitmap === "undefined") {
@@ -385,14 +420,14 @@ export class ComfyApp {
window.open(url, "_blank");
},
},
...getCopyImageOption(img),
...getCopyImageOption(img),
{
content: "Save Image",
callback: () => {
const a = document.createElement("a");
let url = new URL(img.src);
url.searchParams.delete("preview");
a.href = url;
a.href = url.toString();
a.setAttribute("download", new URLSearchParams(url.search).get("filename"));
document.body.append(a);
a.click();
@@ -586,7 +621,7 @@ export class ComfyApp {
}
}
function calculateGrid(w, h, n) {
const calculateGrid = (w, h, n) => {
let columns, rows, cellsize;
if (w > h) {
@@ -614,7 +649,7 @@ export class ComfyApp {
return {cell_size, columns, rows};
}
function is_all_same_aspect_ratio(imgs) {
const is_all_same_aspect_ratio = (imgs) => {
// assume: imgs.length >= 2
let ratio = imgs[0].naturalWidth/imgs[0].naturalHeight;
@@ -629,7 +664,7 @@ export class ComfyApp {
if (this.imgs?.length) {
const widgetIdx = this.widgets?.findIndex((w) => w.name === ANIM_PREVIEW_WIDGET);
if(this.animatedImages) {
// Instead of using the canvas we'll use a IMG
if(widgetIdx > -1) {
@@ -833,6 +868,7 @@ export class ComfyApp {
this.dragOverNode = null;
// Node handles file drop, we dont use the built in onDropFile handler as its buggy
// If you drag multiple files it will call it multiple times with the same file
// @ts-ignore This is not a standard event. TODO fix it.
if (n && n.onDragDrop && (await n.onDragDrop(event))) {
return;
}
@@ -865,8 +901,10 @@ export class ComfyApp {
"dragover",
(e) => {
this.canvas.adjustMouseEvent(e);
// @ts-ignore: canvasX and canvasY are added by adjustMouseEvent in litegraph
const node = this.graph.getNodeOnPos(e.canvasX, e.canvasY);
if (node) {
// @ts-ignore This is not a standard event. TODO fix it.
if (node.onDragOver && node.onDragOver(e)) {
this.dragOverNode = node;
@@ -887,11 +925,14 @@ export class ComfyApp {
* Adds a handler on paste that extracts and loads images or workflows from pasted JSON data
*/
#addPasteHandler() {
document.addEventListener("paste", async (e) => {
document.addEventListener("paste", async (e: ClipboardEvent) => {
// ctrl+shift+v is used to paste nodes with connections
// this is handled by litegraph
if(this.shiftDown) return;
// @ts-ignore: Property 'clipboardData' does not exist on type 'Window & typeof globalThis'.
// Did you mean 'Clipboard'?ts(2551)
// TODO: Not sure what the code wants to do.
let data = (e.clipboardData || window.clipboardData);
const items = data.items;
@@ -936,9 +977,8 @@ export class ComfyApp {
if (workflow && workflow.version && workflow.nodes && workflow.extra) {
await this.loadGraphData(workflow);
}
else {
if (e.target.type === "text" || e.target.type === "textarea") {
} else {
if ((e.target instanceof HTMLInputElement) && (e.target.type === "text" || e.target.type === "textarea")) {
return;
}
@@ -956,13 +996,13 @@ export class ComfyApp {
*/
#addCopyHandler() {
document.addEventListener("copy", (e) => {
if (e.target.type === "text" || e.target.type === "textarea") {
if ((e.target instanceof HTMLInputElement) && (e.target.type === "text" || e.target.type === "textarea")) {
// Default system copy
return;
}
// copy nodes and clear clipboard
if (e.target.className === "litegraph" && this.canvas.selected_nodes) {
if ((e.target instanceof HTMLElement) && e.target.className === "litegraph" && this.canvas.selected_nodes) {
this.canvas.copyToClipboard();
e.clipboardData.setData('text', ' '); //clearData doesn't remove images from clipboard
e.preventDefault();
@@ -981,7 +1021,9 @@ export class ComfyApp {
#addProcessMouseHandler() {
const self = this;
// @ts-ignore
const origProcessMouseDown = LGraphCanvas.prototype.processMouseDown;
// @ts-ignore
LGraphCanvas.prototype.processMouseDown = function(e) {
// prepare for ctrl+shift drag: zoom start
if(e.ctrlKey && e.shiftKey && e.buttons) {
@@ -999,6 +1041,7 @@ export class ComfyApp {
var height = font_size * 1.4;
// Move group by header
// @ts-ignore
if (LiteGraph.isInsideRectangle(e.canvasX, e.canvasY, this.selected_group.pos[0], this.selected_group.pos[1], this.selected_group.size[0], height)) {
this.selected_group_moving = true;
}
@@ -1006,8 +1049,9 @@ export class ComfyApp {
return res;
}
// @ts-ignore
const origProcessMouseMove = LGraphCanvas.prototype.processMouseMove;
// @ts-ignore
LGraphCanvas.prototype.processMouseMove = function(e) {
// handle ctrl+shift drag
if(e.ctrlKey && e.shiftKey && self.zoom_drag_start) {
@@ -1052,7 +1096,9 @@ export class ComfyApp {
*/
#addProcessKeyHandler() {
const self = this;
// @ts-ignore
const origProcessKey = LGraphCanvas.prototype.processKey;
// @ts-ignore
LGraphCanvas.prototype.processKey = function(e) {
if (!this.graph) {
return;
@@ -1060,7 +1106,7 @@ export class ComfyApp {
var block_default = false;
if (e.target.localName == "input") {
if ((e.target instanceof HTMLElement) && e.target.localName == "input") {
return;
}
@@ -1149,8 +1195,9 @@ export class ComfyApp {
*/
#addDrawGroupsHandler() {
const self = this;
// @ts-ignore
const origDrawGroups = LGraphCanvas.prototype.drawGroups;
// @ts-ignore
LGraphCanvas.prototype.drawGroups = function(canvas, ctx) {
if (!this.graph) {
return;
@@ -1192,9 +1239,10 @@ export class ComfyApp {
* Draws node highlights (executing, drag drop) and progress bar
*/
#addDrawNodeHandler() {
// @ts-ignore
const origDrawNodeShape = LGraphCanvas.prototype.drawNodeShape;
const self = this;
// @ts-ignore
LGraphCanvas.prototype.drawNodeShape = function (node, ctx, size, fgcolor, bgcolor, selected, mouse_over) {
const res = origDrawNodeShape.apply(this, arguments);
@@ -1217,6 +1265,7 @@ export class ComfyApp {
}
if (color) {
// @ts-ignore
const shape = node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;
ctx.lineWidth = lineWidth;
ctx.globalAlpha = 0.8;
@@ -1273,7 +1322,9 @@ export class ComfyApp {
return res;
};
// @ts-ignore
const origDrawNode = LGraphCanvas.prototype.drawNode;
// @ts-ignore
LGraphCanvas.prototype.drawNode = function (node, ctx) {
var editor_alpha = this.editor_alpha;
var old_color = node.bgcolor;
@@ -1282,10 +1333,11 @@ export class ComfyApp {
this.editor_alpha = 0.4;
}
if (node.mode === 4) { // never
node.bgcolor = "#FF00FF";
this.editor_alpha = 0.2;
}
// Mode never equals 4 by ts check.
// if (node.mode === 4) { // never
// node.bgcolor = "#FF00FF";
// this.editor_alpha = 0.2;
// }
const res = origDrawNode.apply(this, arguments);
@@ -1340,7 +1392,9 @@ export class ComfyApp {
}
const node = this.graph.getNodeById(detail.node);
if (node) {
// @ts-ignore
if (node.onExecuted)
// @ts-ignore
node.onExecuted(detail.output);
}
});
@@ -1348,8 +1402,11 @@ export class ComfyApp {
api.addEventListener("execution_start", ({ detail }) => {
this.runningNodeId = null;
this.lastExecutionError = null
// @ts-ignore
this.graph._nodes.forEach((node) => {
// @ts-ignore
if (node.onExecutionStart)
// @ts-ignore
node.onExecutionStart()
})
});
@@ -1368,6 +1425,7 @@ export class ComfyApp {
const blob = detail
const blobUrl = URL.createObjectURL(blob)
// @ts-ignore
this.nodePreviewImages[id] = [blobUrl]
});
@@ -1385,8 +1443,10 @@ export class ComfyApp {
#addConfigureHandler() {
const app = this;
// @ts-ignore
const configure = LGraph.prototype.configure;
// Flag that the graph is configuring to prevent nodes from running checks while its still loading
// @ts-ignore
LGraph.prototype.configure = function () {
app.configuringGraph = true;
try {
@@ -1399,17 +1459,23 @@ export class ComfyApp {
#addAfterConfigureHandler() {
const app = this;
// @ts-ignore
const onConfigure = app.graph.onConfigure;
// @ts-ignore
app.graph.onConfigure = function () {
// Fire callbacks before the onConfigure, this is used by widget inputs to setup the config
// @ts-ignore
for (const node of app.graph._nodes) {
// @ts-ignore
node.onGraphConfigured?.();
}
const r = onConfigure?.apply(this, arguments);
// Fire after onConfigure, used by primitves to generate widget using input nodes config
// @ts-ignore _nodes is private.
for (const node of app.graph._nodes) {
// @ts-ignore
node.onAfterGraphConfigured?.();
}
@@ -1423,7 +1489,7 @@ export class ComfyApp {
async #loadExtensions() {
const extensions = await api.getExtensions();
this.logging.addEntry("Comfy.App", "debug", { Extensions: extensions });
const extensionPromises = extensions.map(async ext => {
try {
await import(api.apiURL(ext));
@@ -1431,7 +1497,7 @@ export class ComfyApp {
console.error("Error loading extension", ext, error);
}
});
await Promise.all(extensionPromises);
}
@@ -1469,7 +1535,7 @@ export class ComfyApp {
if (!user || !users[user]) {
// This will rarely be hit so move the loading to on demand
const { UserSelectionScreen } = await import("./ui/userSelection.js");
this.ui.menuContainer.style.display = "none";
const { userId, username, created } = await new UserSelectionScreen().show(users, user);
this.ui.menuContainer.style.display = "";
@@ -1512,6 +1578,8 @@ export class ComfyApp {
]),
]);
},
// TODO: Is that the correct default value?
defaultValue: undefined,
});
}
@@ -1527,7 +1595,7 @@ export class ComfyApp {
const mainCanvas = document.createElement("canvas")
mainCanvas.style.touchAction = "none"
const canvasEl = (this.canvasEl = Object.assign(mainCanvas, { id: "graph-canvas" }));
canvasEl.tabIndex = "1";
canvasEl.tabIndex = 1;
document.body.prepend(canvasEl);
addDomClippingSetting();
@@ -1537,10 +1605,12 @@ export class ComfyApp {
this.#addApiUpdateHandlers();
this.#addRestoreWorkflowView();
// @ts-ignore
this.graph = new LGraph();
this.#addAfterConfigureHandler();
// @ts-ignore
const canvas = (this.canvas = new LGraphCanvas(canvasEl, this.graph));
this.ctx = canvasEl.getContext("2d");
@@ -1660,13 +1730,18 @@ export class ComfyApp {
this.addInput(inputName, type);
widgetCreated = false;
}
// @ts-ignore
if(widgetCreated && inputData[1]?.forceInput && config?.widget) {
// @ts-ignore
if (!config.widget.options) config.widget.options = {};
// @ts-ignore
config.widget.options.forceInput = inputData[1].forceInput;
}
// @ts-ignore
if(widgetCreated && inputData[1]?.defaultInput && config?.widget) {
// @ts-ignore
if (!config.widget.options) config.widget.options = {};
// @ts-ignore
config.widget.options.defaultInput = inputData[1].defaultInput;
}
}
@@ -1696,11 +1771,13 @@ export class ComfyApp {
node.prototype.comfyClass = nodeData.name;
this.#addNodeContextMenuHandler(node);
this.#addDrawBackgroundHandler(node, app);
this.#addDrawBackgroundHandler(node);
this.#addNodeKeyHandler(node);
await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData);
// @ts-ignore
LiteGraph.registerNodeType(nodeId, node);
// @ts-ignore
node.category = nodeData.category;
}
@@ -1768,18 +1845,26 @@ export class ComfyApp {
Array.from(new Set(missingNodeTypes)).map((t) => {
let children = [];
if (typeof t === "object") {
// @ts-ignore
if(seenTypes.has(t.type)) return null;
// @ts-ignore
seenTypes.add(t.type);
// @ts-ignore
children.push($el("span", { textContent: t.type }));
// @ts-ignore
if (t.hint) {
// @ts-ignore
children.push($el("span", { textContent: t.hint }));
}
// @ts-ignore
if (t.action) {
// @ts-ignore
children.push($el("button", { onclick: t.action.callback, textContent: t.action.text }));
}
} else {
if(seenTypes.has(t)) return null;
seenTypes.add(t);
// @ts-ignore
children.push($el("span", { textContent: t }));
}
return $el("li", children);
@@ -1800,7 +1885,7 @@ export class ComfyApp {
* @param {*} graphData A serialized graph object
* @param { boolean } clean If the graph state, e.g. images, should be cleared
*/
async loadGraphData(graphData, clean = true, restore_view = true) {
async loadGraphData(graphData?, clean: boolean = true, restore_view: boolean = true) {
if (clean !== false) {
this.clean();
}
@@ -1883,16 +1968,17 @@ export class ComfyApp {
return;
}
// @ts-ignore
for (const node of this.graph._nodes) {
const size = node.computeSize();
size[0] = Math.max(node.size[0], size[0]);
size[1] = Math.max(node.size[1], size[1]);
node.size = size;
// @ts-ignore
if (node.widgets) {
// If you break something in the backend and want to patch workflows in the frontend
// This is the place to do this
// @ts-ignore
for (let widget of node.widgets) {
if (node.type == "KSampler" || node.type == "KSamplerAdvanced") {
if (widget.name == "sampler_name") {
@@ -2078,7 +2164,9 @@ export class ComfyApp {
if (error.response.error.details)
message += ": " + error.response.error.details;
for (const [nodeID, nodeError] of Object.entries(error.response.node_errors)) {
// @ts-ignore
message += "\n" + nodeError.class_type + ":"
// @ts-ignore
for (const errorReason of nodeError.errors) {
message += "\n - " + errorReason.message + ": " + errorReason.details
}
@@ -2140,7 +2228,9 @@ export class ComfyApp {
for (const widget of node.widgets) {
// Allow widgets to run callbacks after a prompt has been queued
// e.g. random seed after every gen
// @ts-ignore
if (widget.afterQueued) {
// @ts-ignore
widget.afterQueued();
}
}
@@ -2197,7 +2287,7 @@ export class ComfyApp {
} else if (file.type === "application/json" || file.name?.endsWith(".json")) {
const reader = new FileReader();
reader.onload = async () => {
const jsonContent = JSON.parse(reader.result);
const jsonContent = JSON.parse(reader.result as string);
if (jsonContent?.templates) {
this.loadTemplateData(jsonContent);
} else if(this.isApiJson(jsonContent)) {
@@ -2209,9 +2299,14 @@ export class ComfyApp {
reader.readAsText(file);
} else if (file.name?.endsWith(".latent") || file.name?.endsWith(".safetensors")) {
const info = await getLatentMetadata(file);
// TODO define schema to LatentMetadata
// @ts-ignore
if (info.workflow) {
// @ts-ignore
await this.loadGraphData(JSON.parse(info.workflow));
// @ts-ignore
} else if (info.prompt) {
// @ts-ignore
this.loadApiJson(JSON.parse(info.prompt));
} else {
this.showErrorOnFileLoad(file);
@@ -2222,12 +2317,15 @@ export class ComfyApp {
}
isApiJson(data) {
// @ts-ignore
return Object.values(data).every((v) => v.class_type);
}
loadApiJson(apiData) {
// @ts-ignore
const missingNodeTypes = Object.values(apiData).filter((n) => !LiteGraph.registered_node_types[n.class_type]);
if (missingNodeTypes.length) {
// @ts-ignore
this.showMissingNodesError(missingNodeTypes.map(t => t.class_type), false);
return;
}
@@ -2237,6 +2335,7 @@ export class ComfyApp {
for (const id of ids) {
const data = apiData[id];
const node = LiteGraph.createNode(data.class_type);
// @ts-ignore
node.id = isNaN(+id) ? id : +id;
node.title = data._meta?.title ?? node.title
graph.add(node);
@@ -2244,7 +2343,7 @@ export class ComfyApp {
for (const id of ids) {
const data = apiData[id];
const node = app.graph.getNodeById(id);
const node = app.graph.getNodeById(Number.parseInt(id));
for (const input in data.inputs ?? {}) {
const value = data.inputs[input];
if (value instanceof Array) {
@@ -2255,6 +2354,7 @@ export class ComfyApp {
try {
// Target has no matching input, most likely a converted widget
const widget = node.widgets?.find((w) => w.name === input);
// @ts-ignore
if (widget && node.convertWidgetToInput?.(widget)) {
toSlot = node.inputs?.length - 1;
}
@@ -2267,6 +2367,7 @@ export class ComfyApp {
const widget = node.widgets?.find((w) => w.name === input);
if (widget) {
widget.value = value;
// @ts-ignore
widget.callback?.(value);
}
}
@@ -2299,11 +2400,12 @@ export class ComfyApp {
for (const nodeId in defs) {
this.registerNodeDef(nodeId, defs[nodeId]);
}
// @ts-ignore
for(let nodeNum in this.graph._nodes) {
// @ts-ignore
const node = this.graph._nodes[nodeNum];
const def = defs[node.type];
// @ts-ignore
// Allow primitive nodes to handle refresh
node.refreshComboInNode?.(defs);
@@ -2317,6 +2419,7 @@ export class ComfyApp {
if(widget.name != 'image' && !widget.options.values.includes(widget.value)) {
widget.value = widget.options.values[0];
// @ts-ignore
widget.callback(widget.value);
}
}

View File

@@ -1,4 +1,4 @@
import { app, ANIM_PREVIEW_WIDGET } from "./app.js";
import { app, ANIM_PREVIEW_WIDGET } from "./app";
import type { LGraphNode, Vector4 } from "/types/litegraph";
@@ -198,12 +198,16 @@ const computeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes;
//@ts-ignore
LGraphCanvas.prototype.computeVisibleNodes = function (): LGraphNode[] {
const visibleNodes = computeVisibleNodes.apply(this, arguments);
// @ts-ignore
for (const node of app.graph._nodes) {
if (elementWidgets.has(node)) {
const hidden = visibleNodes.indexOf(node) === -1;
for (const w of node.widgets) {
// @ts-ignore
if (w.element) {
// @ts-ignore
w.element.hidden = hidden;
// @ts-ignore
w.element.style.display = hidden ? "none" : undefined;
if (hidden) {
w.options.onHide?.(w);
@@ -298,6 +302,7 @@ LGraphNode.prototype.addDOMWidget = function (
width: `${widgetWidth - margin * 2}px`,
height: `${(widget.computedHeight ?? 50) - margin * 2}px`,
position: "absolute",
// @ts-ignore
zIndex: app.graph._nodes.indexOf(node),
});

View File

@@ -1,6 +1,6 @@
import { $el, ComfyDialog } from "./ui";
import { api } from "./api";
import type { ComfyApp } from "./app.js";
import type { ComfyApp } from "./app";
$el("style", {
textContent: `

View File

@@ -2,7 +2,7 @@ import { api } from "./api";
import { ComfyDialog as _ComfyDialog } from "./ui/dialog.js";
import { toggleSwitch } from "./ui/toggleSwitch.js";
import { ComfySettingsDialog } from "./ui/settings.js";
import { ComfyApp, app } from "./app.js";
import { ComfyApp, app } from "./app";
export const ComfyDialog = _ComfyDialog;

View File

@@ -1,7 +1,7 @@
import { $el } from "../ui";
import { api } from "../api";
import { ComfyDialog } from "./dialog.js";
import type { ComfyApp } from "../app.js";
import type { ComfyApp } from "../app";
/* The Setting entry stored in `ComfySettingsDialog` */
interface Setting {

View File

@@ -43,9 +43,11 @@ export function applyTextReplacements(app: ComfyApp, value: string): string {
}
// Find node with matching S&R property name
// @ts-ignore
let nodes = app.graph._nodes.filter((n) => n.properties?.["Node name for S&R"] === split[0]);
// If we cant, see if there is a node with that title
if (!nodes.length) {
// @ts-ignore
nodes = app.graph._nodes.filter((n) => n.title === split[0]);
}
if (!nodes.length) {

View File

@@ -1,5 +1,30 @@
import { api } from "./api"
import "./domWidget";
import type { ComfyApp } from "./app";
import type { IWidget, LGraphNode } from "/types/litegraph";
interface InputDataOptions {
display?: string;
default?: any;
label_on?: boolean;
label_off?: boolean;
multiline?: boolean;
// TODO: infer type
dynamicPrompts?: any;
// TODO: infer type
control_after_generate?: any;
// Name of widget.
widget?: string
}
export type InputData = [
string, InputDataOptions
];
export type ComfyWidgetConstructor = (
node: LGraphNode, inputName: string, inputData: InputData, app?: ComfyApp, widgetName?: string) =>
{widget: IWidget, minWidth?: number; minHeight?: number };
let controlValueRunBefore = false;
export function updateControlWidgetLabel(widget) {
@@ -301,11 +326,11 @@ export function initWidgets(app) {
});
}
export const ComfyWidgets = {
export const ComfyWidgets: Record<string, ComfyWidgetConstructor> = {
"INT:seed": seedWidget,
"INT:noise_seed": seedWidget,
FLOAT(node, inputName, inputData, app) {
let widgetType = isSlider(inputData[1]["display"], app);
let widgetType: "number" | "slider" = isSlider(inputData[1]["display"], app);
let precision = app.ui.settings.getSettingValue("Comfy.FloatRoundingPrecision");
let disable_rounding = app.ui.settings.getSettingValue("Comfy.DisableFloatRounding")
if (precision == 0) precision = undefined;
@@ -369,17 +394,22 @@ export const ComfyWidgets = {
}
const res = { widget: node.addWidget("combo", inputName, defaultValue, () => {}, { values: type }) };
if (inputData[1]?.control_after_generate) {
// TODO make combo handle a widget node type?
// @ts-ignore
res.widget.linkedWidgets = addValueControlWidgets(node, res.widget, undefined, undefined, inputData);
}
return res;
},
IMAGEUPLOAD(node, inputName, inputData, app) {
IMAGEUPLOAD(node: LGraphNode, inputName, inputData, app) {
// TODO make image upload handle a custom node type?
// @ts-ignore
const imageWidget = node.widgets.find((w) => w.name === (inputData[1]?.widget ?? "image"));
let uploadWidget;
function showImage(name) {
const img = new Image();
img.onload = () => {
// @ts-ignore
node.imgs = [img];
app.graph.setDirtyCanvas(true);
};
@@ -390,6 +420,7 @@ export const ComfyWidgets = {
name = name.substring(folder_separator + 1);
}
img.src = api.apiURL(`/view?filename=${encodeURIComponent(name)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`);
// @ts-ignore
node.setSizeForImage?.();
}
@@ -422,6 +453,8 @@ export const ComfyWidgets = {
});
// Add our own callback to the combo widget to render an image when it changes
// TODO: Explain this?
// @ts-ignore
const cb = node.callback;
imageWidget.callback = function () {
showImage(imageWidget.value);
@@ -493,6 +526,7 @@ export const ComfyWidgets = {
uploadWidget.serialize = false;
// Add handler to check if an image is being dragged over our node
// @ts-ignore
node.onDragOver = function (e) {
if (e.dataTransfer && e.dataTransfer.items) {
const image = [...e.dataTransfer.items].find((f) => f.kind === "file");
@@ -503,6 +537,7 @@ export const ComfyWidgets = {
};
// On drop upload files
// @ts-ignore
node.onDragDrop = function (e) {
console.log("onDragDrop called");
let handled = false;
@@ -516,6 +551,7 @@ export const ComfyWidgets = {
return handled;
};
// @ts-ignore
node.pasteFile = function(file) {
if (file.type.startsWith("image/")) {
const is_pasted = (file.name === "image.png") &&

View File

@@ -1,4 +1,4 @@
import { LGraphNode, IWidget } from "./litegraph";
import { LGraphNode, IWidget, LGraph } from "./litegraph";
import { ComfyApp } from "../../scripts/app";
export interface ComfyExtension {
@@ -74,3 +74,8 @@ export type ComfyObjectInfo = {
};
export type ComfyObjectInfoConfig = [string | any[]] | [string | any[], any];
declare global {
const app: ComfyApp;
const graph: LGraph;
}

View File

@@ -221,7 +221,7 @@ export declare class LGraph {
*/
updateExecutionOrder(): void;
/** This is more internal, it computes the executable nodes in order and returns it */
computeExecutionOrder<T = any>(only_onExecute: boolean, set_level: any): T;
computeExecutionOrder<T = any>(only_onExecute: boolean, set_level?: any): T;
/**
* Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.
* It doesn't include the node itself
@@ -357,7 +357,7 @@ export declare class LGraph {
clearTriggeredSlots(): void;
/* Called when something visually changed (not the graph!) */
change(): void;
setDirtyCanvas(fg: boolean, bg: boolean): void;
setDirtyCanvas(fg: boolean, bg?: boolean): void;
/** Destroys a link */
removeLink(link_id: number): void;
/** Creates a Object containing all the info about this graph, it can be serialized */
@@ -457,6 +457,8 @@ export declare class LGraphNode {
| typeof LiteGraph.NEVER
| typeof LiteGraph.ALWAYS;
widgets?: IWidget[];
/** If set to true widgets do not start after the slots */
widgets_up: boolean;
/** widgets start at y distance from the top of the node */
@@ -1075,6 +1077,12 @@ export declare class LGraphCanvas {
visible_links: LLink[];
visible_nodes: LGraphNode[];
zoom_modify_alpha: boolean;
//mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle
mouse: Vector2;
//mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle
graph_mouse: Vector2;
pointer_is_down?: boolean;
/** clears all the data inside */
clear(): void;
@@ -1309,8 +1317,8 @@ declare global {
y: number,
width: number,
height: number,
radius: number,
radiusLow: number
radius: number | Vector4,
radiusLow?: number
): void;
}
@@ -1319,6 +1327,11 @@ declare global {
}
const LiteGraph: {
DEFAULT_GROUP_FONT_SIZE: any;
overlapBounding(visible_area: any, _bounding: any): unknown;
release_link_on_empty_shows_menu: boolean;
alt_drag_do_clone_nodes: boolean;
GRID_SHAPE: number;
VERSION: number;
CANVAS_GRID_SIZE: number;