mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-14 23:42:35 +08:00
file subflows
This commit is contained in:
parent
4b50c20ccb
commit
22a62d7a8a
@ -1,6 +1,6 @@
|
|||||||
import folder_paths
|
import folder_paths
|
||||||
|
|
||||||
class Subflow:
|
class LoadSubflow:
|
||||||
@classmethod
|
@classmethod
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {"required": { "subflow_name": (folder_paths.get_filename_list("subflows"), ), }}
|
return {"required": { "subflow_name": (folder_paths.get_filename_list("subflows"), ), }}
|
||||||
@ -9,10 +9,32 @@ class Subflow:
|
|||||||
|
|
||||||
CATEGORY = "loaders"
|
CATEGORY = "loaders"
|
||||||
|
|
||||||
|
class FileSubflow:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": { "subflow_name": (folder_paths.get_filename_list("subflows"), )} }
|
||||||
|
RETURN_TYPES = ()
|
||||||
|
FUNCTION = ""
|
||||||
|
|
||||||
|
CATEGORY = "utils"
|
||||||
|
|
||||||
|
class InMemorySubflow:
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(s):
|
||||||
|
return {"required": {} }
|
||||||
|
RETURN_TYPES = ()
|
||||||
|
FUNCTION = ""
|
||||||
|
|
||||||
|
CATEGORY = ""
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {
|
NODE_CLASS_MAPPINGS = {
|
||||||
"Subflow": Subflow,
|
"LoadSubflow": LoadSubflow,
|
||||||
|
"FileSubflow": FileSubflow,
|
||||||
|
"InMemorySubflow": InMemorySubflow
|
||||||
}
|
}
|
||||||
|
|
||||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||||
"Subflow": "Load Subflow"
|
"Subflow": "Load Subflow",
|
||||||
|
"FileSubflow": "File Subflow",
|
||||||
|
"InMemorySubflow": "In Memory Subflow"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,139 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { api } from "../../scripts/api.js";
|
import { api } from "../../scripts/api.js";
|
||||||
|
import { ComfyWidgets } from "/scripts/widgets.js";
|
||||||
|
const CONFIG = Symbol();
|
||||||
|
// const GET_CONFIG = Symbol();
|
||||||
|
import { GET_CONFIG } from "./widgetInputs.js";
|
||||||
|
|
||||||
class InMemorySubflow {
|
function getConfig(widgetName) {
|
||||||
constructor() {}
|
const { nodeData } = this.constructor;
|
||||||
|
return nodeData?.input?.required[widgetName] ?? nodeData?.input?.optional?.[widgetName];
|
||||||
onConfigure() {
|
|
||||||
console.log(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getExportedOutput(slot) {
|
// class InMemorySubflow extends LGraphNode {
|
||||||
return this.subflow.extras.outputSlots[slot];
|
// constructor(title) {
|
||||||
};
|
// super(title ?? "InMemorySubflow");
|
||||||
|
// }
|
||||||
|
|
||||||
getExportedInput(slot) {
|
// onConfigure() {
|
||||||
return this.subflow.extras.inputSlots[slot];
|
// console.log(this);
|
||||||
};
|
// }
|
||||||
|
|
||||||
async refreshNode(subflow) {
|
// getExportedOutput(slot) {
|
||||||
if (!subflow) return;
|
// return this.subflow.extras.outputSlots[slot];
|
||||||
this.updateSubflowPrompt(subflow);
|
// };
|
||||||
this.refreshPins(subflow);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshPins(subflow) {
|
// getExportedInput(slot) {
|
||||||
|
// return this.subflow.extras.inputSlots[slot];
|
||||||
|
// };
|
||||||
|
|
||||||
|
// async refreshNode(subflow) {
|
||||||
|
// if (!subflow) return;
|
||||||
|
// this.updateSubflowPrompt(subflow);
|
||||||
|
// this.refreshPins(subflow);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// refreshPins(subflow) {
|
||||||
|
// if(!subflow)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// subflow.extras = { inputSlots: [], outputSlots: [] };
|
||||||
|
// const { inputSlots } = subflow.extras;
|
||||||
|
// const { outputSlots } = subflow.extras;
|
||||||
|
|
||||||
|
// // remove all existing pins
|
||||||
|
// const numInputs = this.inputs?.length ?? 0;
|
||||||
|
// const numOutputs = this.outputs?.length ?? 0;
|
||||||
|
// for(let i = numInputs-1; i > -1; i--) {
|
||||||
|
// this.removeInput(i);
|
||||||
|
// }
|
||||||
|
// for(let i = numOutputs-1; i > -1; i--) {
|
||||||
|
// this.removeOutput(i);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const subflowNodes = subflow.nodes;
|
||||||
|
// // add the new pins and keep track of where the exported vars go to within the inner nodes
|
||||||
|
// for (const subflowNode of subflowNodes) {
|
||||||
|
// const exports = subflowNode.properties?.exports;
|
||||||
|
// if (exports) {
|
||||||
|
// let pinNum = 0;
|
||||||
|
// for (const inputRef of exports.inputs) {
|
||||||
|
// console.log(subflowNode.inputs);
|
||||||
|
// const input = subflowNode.inputs.find(q => q.name === inputRef);
|
||||||
|
// if (!input) continue;
|
||||||
|
// const { name, type, link, slot_index, ...extras } = input;
|
||||||
|
// console.log("Input");
|
||||||
|
// console.log(input);
|
||||||
|
// console.log(extras);
|
||||||
|
// if (extras.widget) {
|
||||||
|
// extras.widget[GET_CONFIG] = () => config;
|
||||||
|
// }
|
||||||
|
// this.addInput(input.name, input.type, extras );
|
||||||
|
// inputSlots.push([subflowNode.id, pinNum]);
|
||||||
|
// pinNum++;
|
||||||
|
// }
|
||||||
|
// pinNum = 0;
|
||||||
|
// for (const outputRef of exports.outputs) {
|
||||||
|
// const output = subflowNode.outputs.find(q => q.name === outputRef);
|
||||||
|
// if (!output) continue;
|
||||||
|
// this.addOutput(output.name, output.type);
|
||||||
|
// outputSlots.push([subflowNode.id, pinNum]);
|
||||||
|
// pinNum++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this.size[0] = 180;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// updateSubflowPrompt(subflow) {
|
||||||
|
// this.subflow = subflow;
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// class FileSubflow extends InMemorySubflow {
|
||||||
|
// constructor() {
|
||||||
|
// super("FileSubflow");
|
||||||
|
|
||||||
|
// console.log("constructor called");
|
||||||
|
// // ComfyWidgets.SUBFLOWUPLOAD(this, null, null, app);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// async refreshNode(subflow) {
|
||||||
|
// if (!subflow) return;
|
||||||
|
|
||||||
|
// this.updateSubflowPrompt(subflow);
|
||||||
|
// this.refreshPins(subflow);
|
||||||
|
|
||||||
|
// this.size[0] = this.computeSizeX();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// computeSizeX() {
|
||||||
|
// return Math.max(100, LiteGraph.NODE_TEXT_SIZE * this.title.length * 0.45 + 160);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: "Comfy.Subflow",
|
||||||
|
beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||||
|
|
||||||
|
// console.log("in init")
|
||||||
|
// LiteGraph.registerNodeType(
|
||||||
|
// "InMemorySubflow",
|
||||||
|
// Object.assign(InMemorySubflow, {
|
||||||
|
// title: "InMemorySubflow",
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
|
// LiteGraph.registerNodeType(
|
||||||
|
// "FileSubflow",
|
||||||
|
// Object.assign(FileSubflow, {
|
||||||
|
// title: "FileSubflow",
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
|
||||||
|
// FileSubflow.category = "utils";
|
||||||
|
const refreshPins = (node, subflow) => {
|
||||||
if(!subflow)
|
if(!subflow)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -30,72 +141,6 @@ class InMemorySubflow {
|
|||||||
const { inputSlots } = subflow.extras;
|
const { inputSlots } = subflow.extras;
|
||||||
const { outputSlots } = subflow.extras;
|
const { outputSlots } = subflow.extras;
|
||||||
|
|
||||||
// remove all existing pins
|
|
||||||
const numInputs = this.inputs?.length ?? 0;
|
|
||||||
const numOutputs = this.outputs?.length ?? 0;
|
|
||||||
for(let i = numInputs-1; i > -1; i--) {
|
|
||||||
this.removeInput(i);
|
|
||||||
}
|
|
||||||
for(let i = numOutputs-1; i > -1; i--) {
|
|
||||||
this.removeOutput(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
const subflowNodes = subflow.nodes;
|
|
||||||
// add the new pins and keep track of where the exported vars go to within the inner nodes
|
|
||||||
for (const subflowNode of subflowNodes) {
|
|
||||||
const exports = subflowNode.properties?.exports;
|
|
||||||
if (exports) {
|
|
||||||
let pinNum = 0;
|
|
||||||
for (const inputRef of exports.inputs) {
|
|
||||||
const input = subflowNode.inputs.find(q => q.name === inputRef);
|
|
||||||
if (!input) continue;
|
|
||||||
this.addInput(input.name, input.type);
|
|
||||||
inputSlots.push([subflowNode.id, pinNum]);
|
|
||||||
pinNum++;
|
|
||||||
}
|
|
||||||
pinNum = 0;
|
|
||||||
for (const outputRef of exports.outputs) {
|
|
||||||
const output = subflowNode.outputs.find(q => q.name === outputRef);
|
|
||||||
if (!output) continue;
|
|
||||||
this.addOutput(output.name, output.type);
|
|
||||||
outputSlots.push([subflowNode.id, pinNum]);
|
|
||||||
pinNum++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.size[0] = 180;
|
|
||||||
};
|
|
||||||
|
|
||||||
updateSubflowPrompt(subflow) {
|
|
||||||
this.subflow = subflow;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
app.registerExtension({
|
|
||||||
name: "Comfy.Subflow",
|
|
||||||
async nodeCreated(node) {
|
|
||||||
if (!node.widgets) return;
|
|
||||||
if (node.widgets[0].name !== "subflow_name") return;
|
|
||||||
|
|
||||||
let outputSlots = []; // (int (node), int (slot))
|
|
||||||
let inputSlots = [];
|
|
||||||
|
|
||||||
const refreshNode = async (subflowName) => {
|
|
||||||
const subflowData = await api.getSubflow(subflowName);
|
|
||||||
if (!subflowData.subflow) return;
|
|
||||||
|
|
||||||
const subflowNodes = subflowData.subflow.nodes
|
|
||||||
updateSubflowPrompt(subflowData.subflow);
|
|
||||||
refreshPins(subflowNodes);
|
|
||||||
|
|
||||||
node.size[0] = computeSizeX(subflowName);
|
|
||||||
};
|
|
||||||
|
|
||||||
const refreshPins = (subflowNodes) => {
|
|
||||||
inputSlots = [];
|
|
||||||
outputSlots = [];
|
|
||||||
|
|
||||||
// remove all existing pins
|
// remove all existing pins
|
||||||
const numInputs = node.inputs?.length ?? 0;
|
const numInputs = node.inputs?.length ?? 0;
|
||||||
const numOutputs = node.outputs?.length ?? 0;
|
const numOutputs = node.outputs?.length ?? 0;
|
||||||
@ -106,15 +151,28 @@ app.registerExtension({
|
|||||||
node.removeOutput(i);
|
node.removeOutput(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subflowNodes = subflow.nodes;
|
||||||
// add the new pins and keep track of where the exported vars go to within the inner nodes
|
// add the new pins and keep track of where the exported vars go to within the inner nodes
|
||||||
for (const subflowNode of subflowNodes) {
|
for (const subflowNode of subflowNodes) {
|
||||||
const exports = subflowNode.properties.exports;
|
const exports = subflowNode.properties?.exports;
|
||||||
if (exports) {
|
if (exports) {
|
||||||
let pinNum = 0;
|
let pinNum = 0;
|
||||||
for (const inputRef of exports.inputs) {
|
for (const inputRef of exports.inputs) {
|
||||||
|
console.log(subflowNode.inputs);
|
||||||
const input = subflowNode.inputs.find(q => q.name === inputRef);
|
const input = subflowNode.inputs.find(q => q.name === inputRef);
|
||||||
if (!input) continue;
|
if (!input) continue;
|
||||||
node.addInput(input.name, input.type);
|
const { name, type, link, slot_index, ...extras } = input;
|
||||||
|
console.log("Input");
|
||||||
|
console.log(input);
|
||||||
|
console.log(extras);
|
||||||
|
if (extras.widget) {
|
||||||
|
const w = extras.widget;
|
||||||
|
const config = getConfig.call(this, input.name) ?? [input.type, w.options || {}];
|
||||||
|
extras.widget[GET_CONFIG] = () => config;
|
||||||
|
console.log(extras);
|
||||||
|
console.log(input.type);
|
||||||
|
}
|
||||||
|
node.addInput(input.name, input.type, extras );
|
||||||
inputSlots.push([subflowNode.id, pinNum]);
|
inputSlots.push([subflowNode.id, pinNum]);
|
||||||
pinNum++;
|
pinNum++;
|
||||||
}
|
}
|
||||||
@ -128,32 +186,29 @@ app.registerExtension({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node.size[0] = 180;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSubflowPrompt = (subflow) => {
|
const refreshNode = async (node, subflow) => {
|
||||||
|
if (!subflow) return;
|
||||||
|
|
||||||
node.subflow = subflow;
|
node.subflow = subflow;
|
||||||
|
refreshPins(node, subflow);
|
||||||
|
|
||||||
|
node.size[0] = Math.max(100, LiteGraph.NODE_TEXT_SIZE * node.title.length * 0.45 + 160);
|
||||||
};
|
};
|
||||||
|
|
||||||
const computeSizeX = (subflowName) => {
|
// if (nodeData.name == "InMemorySubflow") {
|
||||||
return Math.max(100, LiteGraph.NODE_TEXT_SIZE * subflowName.length * 0.45 + 160);
|
// Object.assign(nodeData, new InMemorySubflow());
|
||||||
};
|
// }
|
||||||
|
|
||||||
node.widgets[0].callback = (subflowName) => refreshNode(subflowName);
|
if (nodeData.name == "FileSubflow") {
|
||||||
|
nodeType.prototype.refreshNode = function(subflow) { refreshNode(this, subflow); };
|
||||||
|
nodeType.prototype.getExportedOutput = function(slot) { return this.subflow.extras.outputSlots[slot]; }
|
||||||
|
nodeType.prototype.getExportedInput = function(slot) { return this.subflow.extras.inputSlots[slot]; }
|
||||||
|
|
||||||
node.getExportedOutput = (slot) => {
|
nodeData.input.required = { subflow: ["SUBFLOWUPLOAD"] };
|
||||||
return outputSlots[slot];
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
node.getExportedInput = (slot) => {
|
|
||||||
return inputSlots[slot];
|
|
||||||
};
|
|
||||||
},
|
|
||||||
registerCustomNodes() {
|
|
||||||
LiteGraph.registerNodeType(
|
|
||||||
"InMemorySubflow",
|
|
||||||
Object.assign(InMemorySubflow, {
|
|
||||||
title: "InMemorySubflow",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { app } from "../../scripts/app.js";
|
|||||||
const CONVERTED_TYPE = "converted-widget";
|
const CONVERTED_TYPE = "converted-widget";
|
||||||
const VALID_TYPES = ["STRING", "combo", "number", "BOOLEAN"];
|
const VALID_TYPES = ["STRING", "combo", "number", "BOOLEAN"];
|
||||||
const CONFIG = Symbol();
|
const CONFIG = Symbol();
|
||||||
const GET_CONFIG = Symbol();
|
export const GET_CONFIG = Symbol();
|
||||||
|
|
||||||
function getConfig(widgetName) {
|
function getConfig(widgetName) {
|
||||||
const { nodeData } = this.constructor;
|
const { nodeData } = this.constructor;
|
||||||
|
|||||||
@ -495,4 +495,121 @@ export const ComfyWidgets = {
|
|||||||
|
|
||||||
return { widget: uploadWidget };
|
return { widget: uploadWidget };
|
||||||
},
|
},
|
||||||
|
SUBFLOWUPLOAD(node, inputName, inputData, app) {
|
||||||
|
// const subflowWidget = node.widgets.find((w) => w.name === "subflow");
|
||||||
|
let uploadWidget;
|
||||||
|
|
||||||
|
// var default_value = subflowWidget.value;
|
||||||
|
// Object.defineProperty(subflowWidget, "value", {
|
||||||
|
// set : function(value) {
|
||||||
|
// this._real_value = value;
|
||||||
|
// },
|
||||||
|
|
||||||
|
// get : function() {
|
||||||
|
// let value = "";
|
||||||
|
// if (this._real_value) {
|
||||||
|
// value = this._real_value;
|
||||||
|
// } else {
|
||||||
|
// return default_value;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (value.filename) {
|
||||||
|
// let real_value = value;
|
||||||
|
// value = "";
|
||||||
|
// if (real_value.subfolder) {
|
||||||
|
// value = real_value.subfolder + "/";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// value += real_value.filename;
|
||||||
|
|
||||||
|
// if(real_value.type && real_value.type !== "input")
|
||||||
|
// value += ` [${real_value.type}]`;
|
||||||
|
// }
|
||||||
|
// return value;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Add our own callback to the combo widget to render an image when it changes
|
||||||
|
// const cb = node.callback;
|
||||||
|
// imageWidget.callback = function () {
|
||||||
|
// if (cb) {
|
||||||
|
// return cb.apply(this, arguments);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// On load if we have a value then render the image
|
||||||
|
// The value isnt set immediately so we need to wait a moment
|
||||||
|
// No change callbacks seem to be fired on initial setting of the value
|
||||||
|
// requestAnimationFrame(() => {
|
||||||
|
|
||||||
|
// });
|
||||||
|
|
||||||
|
const uploadFile = async (file) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const subflow = JSON.parse(e.target.result);
|
||||||
|
console.log(node);
|
||||||
|
node.refreshNode(subflow);
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const fileInput = document.createElement("input");
|
||||||
|
Object.assign(fileInput, {
|
||||||
|
type: "file",
|
||||||
|
accept: "image/png,application/json",
|
||||||
|
style: "display: none",
|
||||||
|
onchange: async () => {
|
||||||
|
if (fileInput.files.length) {
|
||||||
|
await uploadFile(fileInput.files[0], true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
document.body.append(fileInput);
|
||||||
|
|
||||||
|
// Create the button widget for selecting the files
|
||||||
|
console.log("adding widge")
|
||||||
|
uploadWidget = node.addWidget("button", "choose file with subflow", "subflow", () => {
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
console.log(node.widgets);
|
||||||
|
uploadWidget.serialize = false;
|
||||||
|
|
||||||
|
// Add handler to check if an image is being dragged over our node
|
||||||
|
node.onDragOver = function (e) {
|
||||||
|
if (e.dataTransfer && e.dataTransfer.items) {
|
||||||
|
const image = [...e.dataTransfer.items].find((f) => f.kind === "file");
|
||||||
|
return !!image;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// On drop upload files
|
||||||
|
node.onDragDrop = function (e) {
|
||||||
|
console.log("onDragDrop called");
|
||||||
|
let handled = false;
|
||||||
|
for (const file of e.dataTransfer.files) {
|
||||||
|
if (file.type === "image/png" || file.type === "application/json") {
|
||||||
|
uploadFile(file, !handled); // Dont await these, any order is fine, only update on first one
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return handled;
|
||||||
|
};
|
||||||
|
|
||||||
|
node.pasteFile = function(file) {
|
||||||
|
if (file.type === "image/png" || file.type === "application/json") {
|
||||||
|
const is_pasted = (file.name === "image.png") &&
|
||||||
|
(file.lastModified - Date.now() < 2000);
|
||||||
|
uploadFile(file, true, is_pasted);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { widget: uploadWidget };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user