mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-08-21 19:50:04 +08:00
393 lines
16 KiB
JavaScript
393 lines
16 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
import { api } from "../../scripts/api.js";
|
|
|
|
import { is_UEnode, is_helper, inject, Logger, get_real_node, defineProperty, graphConverter, fix_inputs, create } from "./use_everywhere_utilities.js";
|
|
import { update_input_label, indicate_restriction } from "./use_everywhere_ui.js";
|
|
import { LinkRenderController } from "./use_everywhere_ui.js";
|
|
import { GraphAnalyser } from "./use_everywhere_graph_analysis.js";
|
|
import { canvas_menu_settings, SETTINGS, add_extra_menu_items } from "./use_everywhere_settings.js";
|
|
import { add_debug } from "./ue_debug.js";
|
|
import { settingsCache } from "./use_everywhere_cache.js";
|
|
import { convert_to_links } from "./use_everywhere_apply.js";
|
|
import { get_subgraph_input_type, link_is_from_subgraph_input, node_graph, visible_graph } from "./use_everywhere_subgraph_utils.js";
|
|
import { any_restrictions, setup_ue_properties_oncreate, setup_ue_properties_onload } from "./ue_properties.js";
|
|
import { edit_restrictions } from "./ue_properties_editor.js";
|
|
|
|
/*
|
|
The ui component that looks after the link rendering
|
|
*/
|
|
var linkRenderController;
|
|
var graphAnalyser;
|
|
|
|
/*
|
|
Inject a call to linkRenderController.mark_list_link_outdated into a method with name methodname on all objects in the array
|
|
If object is undefined, do nothing.
|
|
The injection is added at the end of the existing method (if the method didn't exist, it is created).
|
|
*/
|
|
function inject_outdating_into_objects(array, methodname, tracetext) {
|
|
if (array) {
|
|
array.forEach((object) => { inject_outdating_into_object_method(object, methodname, tracetext); })
|
|
}
|
|
}
|
|
function inject_outdating_into_object_method(object, methodname, tracetext) {
|
|
if (object) inject(object, methodname, tracetext, linkRenderController.mark_link_list_outdated, linkRenderController);
|
|
}
|
|
|
|
class Deferred {
|
|
constructor() { this.deferred_actions = [] }
|
|
push(x) { this.deferred_actions.push(x) } // add action of the form: { fn:function, args:array }
|
|
execute() {
|
|
while (this.deferred_actions.length>0) {
|
|
const action = this.deferred_actions.pop()
|
|
try { action?.fn(...action?.args) }
|
|
catch (e) { Logger.log_error(e) }
|
|
}
|
|
}
|
|
}
|
|
|
|
const deferred_actions = new Deferred()
|
|
|
|
app.registerExtension({
|
|
name: "cg.customnodes.use_everywhere",
|
|
settings: SETTINGS,
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
/*
|
|
When a node is connected or unconnected, the link list is dirty.
|
|
If it is a UE node, we need to update it as well
|
|
*/
|
|
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
|
nodeType.prototype.onConnectionsChange = function (side,slot,connect,link_info,output) {
|
|
if (this.IS_UE && side==1) { // side 1 is input
|
|
var type = '*'
|
|
if (connect && link_info) {
|
|
var connected_type
|
|
if (link_is_from_subgraph_input(link_info)) { // input slot of subgraph
|
|
connected_type = get_subgraph_input_type(node_graph(this), link_info.origin_slot)
|
|
} else {
|
|
connected_type = get_real_node(link_info.origin_id, node_graph(this))?.outputs[link_info.origin_slot]?.type
|
|
}
|
|
if (connected_type) type = connected_type;
|
|
};
|
|
this.inputs[slot].type = type;
|
|
if (link_info) link_info.type = type
|
|
update_input_label(this, slot, app);
|
|
if (!graphConverter.graph_being_configured) {
|
|
// do the fix at the end of graph change
|
|
deferred_actions.push( { fn:fix_inputs, args:[this,]} )
|
|
// disconnecting doesn't trigger graphChange call?
|
|
setTimeout(deferred_actions.execute.bind(deferred_actions), 100)
|
|
}
|
|
}
|
|
linkRenderController.mark_link_list_outdated();
|
|
onConnectionsChange?.apply(this, arguments);
|
|
};
|
|
|
|
/*
|
|
Reject duplicated inputs for now
|
|
*/
|
|
if (is_UEnode(nodeType)) {
|
|
const onConnectInput = nodeType.prototype.onConnectInput
|
|
nodeType.prototype.onConnectInput = function (index, type) {
|
|
if (!this.properties.ue_properties.fixed_inputs) {
|
|
if (this.inputs.find((i, j)=>(i.type==type && j!=index))) return false
|
|
}
|
|
return onConnectInput?.apply(this, arguments)
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Extra menu options are the node right click menu.
|
|
We add to this list, and also insert a link list outdate to everything.
|
|
*/
|
|
add_extra_menu_items(nodeType.prototype, inject_outdating_into_object_method)
|
|
|
|
if (is_UEnode(nodeType)) {
|
|
const original_onDrawTitleBar = nodeType.prototype.onDrawTitleBar;
|
|
nodeType.prototype.onDrawTitleBar = function(ctx, title_height) {
|
|
original_onDrawTitleBar?.apply(this, arguments);
|
|
if (any_restrictions(this)) indicate_restriction(ctx, title_height);
|
|
}
|
|
}
|
|
|
|
if (is_UEnode(nodeType)) {
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
|
/*if (!this.properties) this.properties = {}
|
|
if (this.inputs) {
|
|
if (!this.widgets) this.widgets = [];
|
|
for (const input of this.inputs) {
|
|
if (input.widget && !this.widgets.find((w) => w.name === input.widget.name)) this.widgets.push(input.widget)
|
|
}
|
|
}*/
|
|
Logger.log_detail(`Node ${this.id} created`)
|
|
setup_ue_properties_oncreate(this)
|
|
return r;
|
|
}
|
|
}
|
|
},
|
|
|
|
async nodeCreated(node) {
|
|
// TODO see if we still need this
|
|
if (!node.__mode) {
|
|
node.__mode = node.mode
|
|
defineProperty(node, "mode", {
|
|
get: ( )=>{return node.__mode},
|
|
set: (v)=>{node.__mode = v; node.afterChangeMade?.('mode', v);}
|
|
})
|
|
}
|
|
|
|
const original_afterChangeMade = node.afterChangeMade
|
|
node.afterChangeMade = (p, v) => {
|
|
original_afterChangeMade?.(p,v)
|
|
if (p==='mode') {
|
|
linkRenderController.mark_link_list_outdated();
|
|
node.widgets?.forEach((widget) => {widget.onModeChange?.(v)}); // no idea why I have this?
|
|
}
|
|
}
|
|
|
|
if (is_helper(node)) { // editing a helper node makes the list dirty
|
|
inject_outdating_into_objects(node.widgets,'callback',`widget callback on ${this.id}`);
|
|
}
|
|
|
|
// removing a node makes the list dirty
|
|
inject_outdating_into_object_method(node, 'onRemoved', `node ${node.id} removed`)
|
|
|
|
// check if the extra menu_items have been added (catch subgraph niode creation)
|
|
add_extra_menu_items(node, inject_outdating_into_object_method)
|
|
|
|
// creating a node makes the link list dirty - but give the system a moment to finish
|
|
setTimeout( ()=>{linkRenderController.mark_link_list_outdated()}, 100 );
|
|
},
|
|
|
|
// When a graph node is loaded convert it if needed
|
|
loadedGraphNode(node) {
|
|
if (graphConverter.running_116_plus()) {
|
|
graphConverter.convert_if_pre_116(node);
|
|
if (node.isSubgraphNode?.()) {
|
|
node.subgraph.nodes.forEach((n) => {
|
|
graphConverter.convert_if_pre_116(n);
|
|
})
|
|
}
|
|
}
|
|
setup_ue_properties_onload(node)
|
|
},
|
|
|
|
async setup() {
|
|
|
|
create('link', null, document.getElementsByTagName('HEAD')[0],
|
|
{'rel':'stylesheet', 'type':'text/css', 'href': new URL("./ue.css", import.meta.url).href } )
|
|
|
|
api.addEventListener("status", ({detail}) => {
|
|
if (linkRenderController) linkRenderController.note_queue_size(detail ? detail.exec_info.queue_remaining : 0)
|
|
});
|
|
|
|
/* if we are on version 1.16 or later, stash input data to convert nodes when they are loaded */
|
|
if (graphConverter.running_116_plus()) {
|
|
const original_loadGraphData = app.loadGraphData;
|
|
app.loadGraphData = function (data) {
|
|
try {
|
|
graphConverter.store_node_input_map(data);
|
|
} catch (e) { Logger.log_error(e); }
|
|
const cvw_was = settingsCache.getSettingValue("Comfy.Validation.Workflows")
|
|
if (settingsCache.getSettingValue("Use Everywhere.Options.block_graph_validation")) {
|
|
app.ui.settings.setSettingValue("Comfy.Validation.Workflows", false);
|
|
}
|
|
original_loadGraphData.apply(this, arguments);
|
|
app.ui.settings.setSettingValue("Comfy.Validation.Workflows", cvw_was);
|
|
}
|
|
}
|
|
|
|
/*
|
|
When we draw a node, render the virtual connection points
|
|
*/
|
|
const original_drawNode = LGraphCanvas.prototype.drawNode;
|
|
LGraphCanvas.prototype.drawNode = function(node, ctx) {
|
|
try {
|
|
linkRenderController.pause('drawNode')
|
|
const v = original_drawNode.apply(this, arguments);
|
|
linkRenderController.highlight_ue_connections(node, ctx);
|
|
if (node._last_seen_bg !== node.bgcolor) linkRenderController.mark_link_list_outdated();
|
|
node._last_seen_bg = node.bgcolor
|
|
return v
|
|
} catch (e) {
|
|
Logger.log_error(e)
|
|
} finally {
|
|
linkRenderController.unpause()
|
|
}
|
|
}
|
|
|
|
const original_drawFrontCanvas = LGraphCanvas.prototype.drawFrontCanvas
|
|
LGraphCanvas.prototype.drawFrontCanvas = function() {
|
|
try {
|
|
linkRenderController.disable_all_connected_widgets(true)
|
|
return original_drawFrontCanvas.apply(this, arguments);
|
|
} catch (e) {
|
|
Logger.log_error(e)
|
|
} finally {
|
|
try {
|
|
linkRenderController.disable_all_connected_widgets(false)
|
|
} catch (e) {
|
|
Logger.log_error(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
When we draw connections, do the ue ones as well (logic for on/off is in lrc)
|
|
*/
|
|
const drawConnections = LGraphCanvas.prototype.drawConnections;
|
|
LGraphCanvas.prototype.drawConnections = function(ctx) {
|
|
drawConnections?.apply(this, arguments);
|
|
try {
|
|
linkRenderController.render_all_ue_links(ctx);
|
|
} catch (e) {
|
|
Logger.log_error(e)
|
|
}
|
|
}
|
|
|
|
/*
|
|
Canvas menu is the right click on backdrop.
|
|
We need to add our options, and hijack the others to mark link list dirty
|
|
*/
|
|
const original_getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
|
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
|
|
// Add our items to the canvas menu
|
|
const options = original_getCanvasMenuOptions.apply(this, arguments);
|
|
canvas_menu_settings(options);
|
|
|
|
// every menu item makes our list dirty
|
|
inject_outdating_into_objects(options,'callback',`menu option on canvas`);
|
|
|
|
return options;
|
|
}
|
|
|
|
/*
|
|
Finding a widget by it's name is something done a lot of times in rendering,
|
|
so add a method that caches the names that can be used deep in the rendering code.
|
|
|
|
TODO: Ought to delete this._widgetNameMap when widgets are added or removed.
|
|
*/
|
|
LGraphNode.prototype._getWidgetByName = function(nm) {
|
|
if (this._widgetNameMap === undefined || !this._widgetNameMap[nm]) {
|
|
this._widgetNameMap = {}
|
|
this.widgets?.forEach((w)=>{this._widgetNameMap[w.name] = w})
|
|
}
|
|
if (!this._widgetNameMap[nm]) {
|
|
let breakpoint_be_here; // someone is asking for a widget that doesn't exist
|
|
}
|
|
return this._widgetNameMap[nm]
|
|
}
|
|
|
|
|
|
},
|
|
|
|
init() {
|
|
graphAnalyser = GraphAnalyser.instance();
|
|
linkRenderController = LinkRenderController.instance(graphAnalyser);
|
|
|
|
const original_afterChange = app.graph.afterChange
|
|
app.graph.afterChange = function () {
|
|
deferred_actions.execute()
|
|
original_afterChange?.apply(this, arguments)
|
|
}
|
|
|
|
|
|
var prompt_being_queued = false;
|
|
|
|
const original_graphToPrompt = app.graphToPrompt;
|
|
app.graphToPrompt = async function () {
|
|
if (prompt_being_queued) {
|
|
return await graphAnalyser.graph_to_prompt( );
|
|
} else {
|
|
return await original_graphToPrompt.apply(app, arguments);
|
|
}
|
|
}
|
|
|
|
app.ue_modified_prompt = async function () {
|
|
return await graphAnalyser.graph_to_prompt();
|
|
}
|
|
|
|
const original_queuePrompt = app.queuePrompt;
|
|
app.queuePrompt = async function () {
|
|
prompt_being_queued = true;
|
|
try {
|
|
return await original_queuePrompt.apply(app, arguments);
|
|
} finally {
|
|
prompt_being_queued = false;
|
|
}
|
|
}
|
|
|
|
app.canvas.__node_over = app.canvas.node_over;
|
|
defineProperty(app.canvas, 'node_over', {
|
|
get: ( )=>{return app.canvas.__node_over },
|
|
set: (v)=>{app.canvas.__node_over = v; linkRenderController.node_over_changed(v)}
|
|
} )
|
|
|
|
app.canvas.canvas.addEventListener('litegraph:set-graph', ()=>{
|
|
linkRenderController.mark_link_list_outdated()
|
|
setTimeout(()=>{app.canvas.setDirty(true,true)},200)
|
|
})
|
|
|
|
app.canvas.canvas.addEventListener('litegraph:canvas', (e)=>{
|
|
if (e?.detail?.subType=='node-double-click') {
|
|
const node = e.detail.node
|
|
if (node.IS_UE) {
|
|
if (app.ui.settings.getSettingValue('Comfy.Node.DoubleClickTitleToEdit') && e.detail.originalEvent.canvasY<node.pos[1]) return
|
|
edit_restrictions(null, null, null, null, node)
|
|
}
|
|
}
|
|
})
|
|
|
|
if (false) add_debug();
|
|
|
|
const export_api_label = Array.from(document.getElementsByClassName('p-menubar-item-label')).find((e)=>e.innerText=='Export (API)')
|
|
if (export_api_label) {
|
|
export_api_label.addEventListener('click', (e)=>{
|
|
const ue_links = app.graph.extra['ue_links'];
|
|
if (ue_links.length>0) {
|
|
if (!confirm("This model contains links added by Use Everywhere which won't work with the API. " +
|
|
"You probably want to use 'Convert all UEs to real links' on the canvas right click menu before saving.\n\n" +
|
|
"Save anyway?"))
|
|
{
|
|
e.stopImmediatePropagation()
|
|
e.stopPropagation()
|
|
e.preventDefault()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const original_subgraph = app.graph.convertToSubgraph
|
|
app.graph.convertToSubgraph = function () {
|
|
const ctb_was = graphAnalyser.connect_to_bypassed
|
|
graphAnalyser.connect_to_bypassed = true
|
|
try {
|
|
|
|
const cur_list = graphAnalyser.wait_to_analyse_visible_graph()
|
|
const mods = convert_to_links(cur_list, null, visible_graph());
|
|
const r = original_subgraph.apply(this, arguments);
|
|
mods.restorer()
|
|
return r
|
|
} finally {
|
|
graphAnalyser.connect_to_bypassed = ctb_was
|
|
}
|
|
}
|
|
},
|
|
|
|
beforeConfigureGraph() {
|
|
linkRenderController.pause("before configure", 1000)
|
|
graphAnalyser.pause("before configure", 1000)
|
|
graphConverter.graph_being_configured = true
|
|
},
|
|
|
|
afterConfigureGraph() {
|
|
graphConverter.remove_saved_ue_links_recursively(app.graph)
|
|
//convert_old_nodes(app.graph)
|
|
graphConverter.graph_being_configured = false
|
|
}
|
|
|
|
});
|