mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-14 07:22:36 +08:00
save
This commit is contained in:
parent
2a3054a91f
commit
757fce2561
@ -32,16 +32,17 @@ app.registerExtension({
|
|||||||
if (!input) continue;
|
if (!input) continue;
|
||||||
const { name, type, link, slot_index, ...extras } = input;
|
const { name, type, link, slot_index, ...extras } = input;
|
||||||
|
|
||||||
node.addInput(input.name, input.type, extras );
|
node.addInput(input.name, input.type, extras);
|
||||||
inputSlots.push([subflowNode.id, pinNum]);
|
inputSlots.push([subflowNode, pinNum]);
|
||||||
pinNum++;
|
pinNum++;
|
||||||
}
|
}
|
||||||
|
|
||||||
pinNum = 0;
|
pinNum = 0;
|
||||||
for (const exportedOutput of exports.outputs) {
|
for (const exportedOutput of exports.outputs) {
|
||||||
const output = subflowNode.outputs.find(q => q.name === exportedOutput.name);
|
const output = subflowNode.outputs.find(q => q.name === exportedOutput.name);
|
||||||
if (!output) continue;
|
if (!output) continue;
|
||||||
node.addOutput(output.name, output.type);
|
node.addOutput(output.name, output.type);
|
||||||
outputSlots.push([subflowNode.id, pinNum]);
|
outputSlots.push([subflowNode, pinNum]);
|
||||||
pinNum++;
|
pinNum++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,20 +55,61 @@ app.registerExtension({
|
|||||||
if (!subflow)
|
if (!subflow)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (node.widgets) {
|
// Allow widgets to cleanup
|
||||||
// Allow widgets to cleanup
|
for (let i = 1; i < node.widgets.length; ++i) {
|
||||||
let subflowWidget;
|
if (node.widgets[i].onRemove) {
|
||||||
for (const w of node.widgets) {
|
node.widgets[i].onRemove();
|
||||||
if (w.type == "button") {
|
}
|
||||||
subflowWidget = w;
|
}
|
||||||
} else if (w.onRemove) {
|
node.widgets = [node.widgets[0]];
|
||||||
w.onRemove();
|
|
||||||
|
// Map widgets
|
||||||
|
subflow.extras.widgetNodes = [];
|
||||||
|
const resolveWidgetPath = (thisNode, path, widgetIndex) => {
|
||||||
|
const subflowNodes = thisNode.subflow.nodes;
|
||||||
|
|
||||||
|
let q = 0; // get what would be the q-th exported widget
|
||||||
|
console.log(path);
|
||||||
|
for (const subflowNode of subflowNodes) {
|
||||||
|
const exportedWidgets = subflowNode.properties?.exports?.widgets;
|
||||||
|
console.log("has nodes", q, widgetIndex);
|
||||||
|
if (exportedWidgets) {
|
||||||
|
console.log("in exports");
|
||||||
|
const childPath = `${path}${subflowNode.id}/`;
|
||||||
|
for (const i in exportedWidgets) {
|
||||||
|
console.log("exported Widgets",q, widgetIndex);
|
||||||
|
if (widgetIndex == q) {
|
||||||
|
console.log(subflowNode);
|
||||||
|
if (subflowNode.subflow) {
|
||||||
|
console.log("widget is inside subflow!")
|
||||||
|
return resolveWidgetPath(subflowNode, childPath, i);
|
||||||
|
}
|
||||||
|
return `${childPath}${subflowNode.id}/`;
|
||||||
|
}
|
||||||
|
q++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.warn("couldn't export a widget");
|
||||||
|
};
|
||||||
|
const subflowNodes = subflow.nodes;
|
||||||
|
|
||||||
|
for (const subflowNode of subflowNodes) {
|
||||||
|
const exportedWidgets = subflowNode.properties?.exports?.widgets;
|
||||||
|
if (exportedWidgets) {
|
||||||
|
const childPath = `/${subflowNode.id}/`;
|
||||||
|
for (const i in exportedWidgets) {
|
||||||
|
console.log("exporting", exportedWidgets[i].name);
|
||||||
|
if (subflowNode.subflow) {
|
||||||
|
subflow.extras.widgetNodes.push(resolveWidgetPath(subflowNode, childPath, i));
|
||||||
|
} else {
|
||||||
|
subflow.extras.widgetNodes.push(childPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.widgets = [subflowWidget];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const subflowNodes = subflow.nodes;
|
console.log(subflow.extras.widgetNodes);
|
||||||
let widgetIndex = 1;
|
let widgetIndex = 1;
|
||||||
for (const subflowNode of subflowNodes) {
|
for (const subflowNode of subflowNodes) {
|
||||||
const exports = subflowNode.properties?.exports;
|
const exports = subflowNode.properties?.exports;
|
||||||
@ -90,7 +132,6 @@ app.registerExtension({
|
|||||||
if (v !== null && node.widgets_values) {
|
if (v !== null && node.widgets_values) {
|
||||||
node.widgets_values[widgetIndex] = v;
|
node.widgets_values[widgetIndex] = v;
|
||||||
}
|
}
|
||||||
return subflowNode.id;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let value = exportedWidget.value;
|
let value = exportedWidget.value;
|
||||||
@ -103,21 +144,27 @@ app.registerExtension({
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(subflow.extras.widgetNodes);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshNode = (node, subflow) => {
|
const refreshNode = (node, subflow, filename) => {
|
||||||
if (!subflow) return;
|
if (!subflow) return;
|
||||||
|
|
||||||
node.subflow = subflow;
|
node.subflow = subflow;
|
||||||
|
node.title = `File Subflow (Loaded: ${filename})`;
|
||||||
refreshPins(node, subflow);
|
refreshPins(node, subflow);
|
||||||
refreshWidgets(node, subflow, false);
|
refreshWidgets(node, subflow, false);
|
||||||
|
|
||||||
|
|
||||||
|
console.log("HAS", node.subflow.extras.inputSlots);
|
||||||
|
|
||||||
node.size[0] = Math.max(100, LiteGraph.NODE_TEXT_SIZE * node.title.length * 0.45 + 160);
|
node.size[0] = Math.max(100, LiteGraph.NODE_TEXT_SIZE * node.title.length * 0.45 + 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (nodeData.name == "FileSubflow") {
|
if (nodeData.name == "FileSubflow") {
|
||||||
nodeType.prototype.onConfigure = function() { refreshWidgets(this, this.subflow, true); };
|
nodeType.prototype.onConfigure = function() { refreshWidgets(this, this.subflow, true); };
|
||||||
nodeType.prototype.refreshNode = function(subflow) { refreshNode(this, subflow); };
|
nodeType.prototype.refreshNode = function(subflow, filename) { refreshNode(this, subflow, filename); };
|
||||||
nodeType.prototype.getExportedOutput = function(slot) { return this.subflow.extras.outputSlots[slot]; }
|
nodeType.prototype.getExportedOutput = function(slot) { return this.subflow.extras.outputSlots[slot]; }
|
||||||
nodeType.prototype.getExportedInput = function(slot) { return this.subflow.extras.inputSlots[slot]; }
|
nodeType.prototype.getExportedInput = function(slot) { return this.subflow.extras.inputSlots[slot]; }
|
||||||
|
|
||||||
|
|||||||
@ -14073,7 +14073,6 @@ LGraphNode.prototype.executeAction = function(action)
|
|||||||
!options.ignore_item_callbacks &&
|
!options.ignore_item_callbacks &&
|
||||||
value.disabled !== true
|
value.disabled !== true
|
||||||
) {
|
) {
|
||||||
console.log("value", value);
|
|
||||||
//item callback
|
//item callback
|
||||||
var r = value.callback.call(
|
var r = value.callback.call(
|
||||||
this,
|
this,
|
||||||
|
|||||||
@ -1588,12 +1588,12 @@ export class ComfyApp {
|
|||||||
* Converts the current graph workflow for sending to the API
|
* Converts the current graph workflow for sending to the API
|
||||||
* @returns The workflow and node links
|
* @returns The workflow and node links
|
||||||
*/
|
*/
|
||||||
async graphToPrompt(graph=this.graph) {
|
async graphToPrompt(graph=this.graph, nodeIdOffset=0, path="/", globalMappings={}) {
|
||||||
let subflowNodeIdOffset = graph.last_node_id;
|
console.log("MY PATH IS ", path)
|
||||||
const subflowIdOffsets = {};
|
|
||||||
const workflow = graph.serialize();
|
const workflow = graph.serialize();
|
||||||
const output = {};
|
const output = {};
|
||||||
|
|
||||||
|
let childNodeIdOffset = nodeIdOffset + graph.last_node_id;
|
||||||
// Process nodes in order of execution
|
// Process nodes in order of execution
|
||||||
for (const node of graph.computeExecutionOrder(false)) {
|
for (const node of graph.computeExecutionOrder(false)) {
|
||||||
const n = workflow.nodes.find((n) => n.id === node.id);
|
const n = workflow.nodes.find((n) => n.id === node.id);
|
||||||
@ -1613,24 +1613,32 @@ export class ComfyApp {
|
|||||||
|
|
||||||
if (node.subflow) {
|
if (node.subflow) {
|
||||||
const subgraph = new LGraph();
|
const subgraph = new LGraph();
|
||||||
|
console.log("configuring subflow");
|
||||||
subgraph.configure(node.subflow);
|
subgraph.configure(node.subflow);
|
||||||
const subgraphPrompt = (await this.graphToPrompt(subgraph)).output;
|
console.log("subgraph last node id is ", subgraph.last_node_id);
|
||||||
|
const subgraphPrompt = (await this.graphToPrompt(subgraph, childNodeIdOffset, path+String(node.id)+"/", globalMappings));
|
||||||
|
const subgraphPromptOutput = subgraphPrompt.output;
|
||||||
|
const subgraphGlobalMappings = subgraphPrompt.globalMappings;
|
||||||
|
|
||||||
// replace ids to not conflict with existing ids
|
// replace ids to not conflict with existing ids
|
||||||
for ( const [key, value] of Object.entries(subgraphPrompt) ) {
|
for ( const [key, value] of Object.entries(subgraphPromptOutput) ) {
|
||||||
for ( const [inputKey, inputValue] of Object.entries(value.inputs) ) {
|
// for ( const [inputKey, inputValue] of Object.entries(value.inputs) ) {
|
||||||
if (Array.isArray(inputValue)) {
|
// if (Array.isArray(inputValue)) {
|
||||||
value.inputs[inputKey][0] = String(Number(value.inputs[inputKey][0]) + subflowNodeIdOffset);
|
// value.inputs[inputKey][0] = String(Number(value.inputs[inputKey][0]) + subflowNodeIdOffset);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
output[String(Number(key) + subflowNodeIdOffset)] = {
|
output[key] = {
|
||||||
...value,
|
...value,
|
||||||
for_subflow: String(node.id) // keep reference of parent node
|
for_subflow: String(node.id) // keep reference of root level subflow node
|
||||||
};
|
};
|
||||||
|
// subflowNodes[node.id][key] = value;
|
||||||
}
|
}
|
||||||
subflowIdOffsets[node.id] = subflowNodeIdOffset;
|
|
||||||
|
|
||||||
subflowNodeIdOffset += Object.keys(node.subflow).length;
|
// subflowIdOffsets[node.id] = subflowNodeIdOffset;
|
||||||
|
|
||||||
|
childNodeIdOffset += subgraph.last_node_id;
|
||||||
|
Object.assign(globalMappings, subgraphGlobalMappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputs = {};
|
const inputs = {};
|
||||||
@ -1644,9 +1652,13 @@ export class ComfyApp {
|
|||||||
if (node.subflow) {
|
if (node.subflow) {
|
||||||
if (widget.type !== "button") {
|
if (widget.type !== "button") {
|
||||||
// use the callback to obtain node reference
|
// use the callback to obtain node reference
|
||||||
const forNode = widget.callback(null);
|
console.log(node.subflow.extras.widgetNodes);
|
||||||
const widgetNode = subflowIdOffsets[node.id] + forNode;
|
const globalMappingKey = node.subflow.extras.widgetNodes[i];
|
||||||
output[ widgetNode ].inputs[widget.name] = widget.serializeValue ? await widget.serializeValue(n, i) : widget.value;
|
const globalId = globalMappings[globalMappingKey];
|
||||||
|
if (!output[globalId]) {
|
||||||
|
console.log("couldn't find reference with global mapping key", globalMappingKey);
|
||||||
|
}
|
||||||
|
output[ globalId ].inputs[widget.name] = widget.serializeValue ? await widget.serializeValue(n, i) : widget.value;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inputs[widget.name] = widget.serializeValue ? await widget.serializeValue(n, i) : widget.value;
|
inputs[widget.name] = widget.serializeValue ? await widget.serializeValue(n, i) : widget.value;
|
||||||
@ -1655,15 +1667,37 @@ export class ComfyApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getInputRef = (inputNode, inputSlot) => {
|
const getOutputRef = (inputNode, inputSlot, inputPath) => {
|
||||||
if (inputNode.subflow) {
|
if (inputNode.subflow) {
|
||||||
// input should be mapped to inner node
|
// input should be mapped to inner node
|
||||||
const [ localOriginId, originSlot ] = inputNode.getExportedOutput(inputSlot);
|
// const [ , originSlot ] = inputNode.getExportedOutput(inputSlot);
|
||||||
const originId = subflowIdOffsets[inputNode.id] + localOriginId;
|
const [ underlyingNode, underlyingSlot ] = inputNode.subflow.extras.inputSlots[inputSlot];
|
||||||
return [String(originId), parseInt(originSlot)];
|
console.log("GOT UNDERLYING NODE", underlyingNode, "from input of", inputSlot, "calling", inputNode);
|
||||||
|
return getOutputRef(underlyingNode, underlyingSlot, `${inputPath}${String(inputNode.id)}/` );
|
||||||
|
|
||||||
|
// const originId = nodeIdOffset + localOriginId;
|
||||||
|
// return [String(originId), parseInt(originSlot)];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [String(inputNode.id), parseInt(inputSlot)];
|
const globalId = globalMappings[`${inputPath}${inputNode.id}/`];
|
||||||
|
console.log("outputRef", inputNode, `${inputPath}${inputNode.id}/`, globalId);
|
||||||
|
return [globalId, parseInt(inputSlot)];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInputRef = (inputNode, inputSlot, inputPath) => {
|
||||||
|
if (inputNode.subflow) {
|
||||||
|
// input should be mapped to inner node
|
||||||
|
// const [ , originSlot ] = inputNode.getExportedOutput(inputSlot);
|
||||||
|
const [ underlyingNode, underlyingSlot ] = inputNode.subflow.extras.outputSlots[inputSlot];
|
||||||
|
return getInputRef(underlyingNode, underlyingSlot, `${inputPath}${String(inputNode.id)}/`);
|
||||||
|
|
||||||
|
// const originId = nodeIdOffset + localOriginId;
|
||||||
|
// return [String(originId), parseInt(originSlot)];
|
||||||
|
}
|
||||||
|
const globalId = globalMappings[`${inputPath}${inputNode.id}/`];
|
||||||
|
console.log(globalMappings);
|
||||||
|
console.log("inputRef", inputNode,`${inputPath}${inputNode.id}/`, globalId);
|
||||||
|
return [globalId, parseInt(inputSlot)];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store all node links
|
// Store all node links
|
||||||
@ -1707,18 +1741,23 @@ export class ComfyApp {
|
|||||||
if (link) {
|
if (link) {
|
||||||
if (node.subflow) {
|
if (node.subflow) {
|
||||||
// inner node's input should be used
|
// inner node's input should be used
|
||||||
const [ localTargetId, targetSlot ] = node.getExportedInput(link.target_slot);
|
// const [ targetNode, targetSlot ] = node.getExportedInput(link.target_slot);
|
||||||
const targetId = subflowIdOffsets[node.id] + localTargetId;
|
const [targetId, targetSlot] = getOutputRef(node, link.target_slot, path);
|
||||||
output[ String(targetId) ].inputs[ node.inputs[targetSlot].name ] = getInputRef(parent, link.origin_slot);
|
console.log("received targetId", targetId, link);
|
||||||
|
output[ targetId ].inputs[ node.inputs[targetSlot].name ] = getInputRef(parent, link.origin_slot, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs[node.inputs[i].name] = getInputRef(parent, link.origin_slot);
|
console.log("handling", node.type, node.id, node.inputs[i].name );
|
||||||
|
inputs[node.inputs[i].name] = getInputRef(parent, link.origin_slot, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!node.subflow) {
|
if (!node.subflow) {
|
||||||
output[String(node.id)] = {
|
const globalId = String(node.id + nodeIdOffset);
|
||||||
|
console.log("setting globalMapping", path+String(node.id)+"/", globalId);
|
||||||
|
globalMappings[path+String(node.id)+"/"] = globalId;
|
||||||
|
output[globalId] = {
|
||||||
inputs,
|
inputs,
|
||||||
class_type: node.comfyClass,
|
class_type: node.comfyClass,
|
||||||
};
|
};
|
||||||
@ -1737,7 +1776,8 @@ export class ComfyApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { workflow, output };
|
console.log(output);
|
||||||
|
return { workflow, output, globalMappings };
|
||||||
}
|
}
|
||||||
|
|
||||||
#formatPromptError(error) {
|
#formatPromptError(error) {
|
||||||
|
|||||||
@ -547,10 +547,10 @@ export const ComfyWidgets = {
|
|||||||
|
|
||||||
const uploadFile = async (file) => {
|
const uploadFile = async (file) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
const filename = file.name;
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const subflow = JSON.parse(e.target.result);
|
const subflow = JSON.parse(e.target.result);
|
||||||
console.log(node);
|
node.refreshNode(subflow, filename);
|
||||||
node.refreshNode(subflow);
|
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user