Files
2025-04-06 12:54:48 -04:00

253 lines
10 KiB
JavaScript

/**
* Workflow Integration for ComfyUI_ControlFreak
* Handles saving and loading mappings from the workflow JSON
*/
import { app } from "../../../../scripts/app.js";
import { contextProvider } from "./contextProvider.js";
import { eventBus } from "./eventBus.js";
export class WorkflowIntegration {
constructor(appInstance, mappingEngine) {
this.app = appInstance || app;
this.mappingEngine = mappingEngine;
// Set up event listeners for graph changes
this._setupEventListeners();
}
/**
* Set up event listeners for ComfyUI workflow events
* @private
*/
_setupEventListeners() {
// Add event listener for when graph is exported to JSON
const originalGraphToJSON = this.app.graph.toJSON;
this.app.graph.toJSON = (data) => {
const result = originalGraphToJSON.call(this.app.graph, data);
try {
// Add our mappings data to the workflow JSON
if (!result.extra) {
result.extra = {};
}
// Get the mapping engine to export mappings
const engine = this.mappingEngine || contextProvider.get('mappingEngine');
if (engine) {
const mappings = engine.exportMappingsForWorkflow();
// Only add to workflow if we have mappings
if (mappings && mappings.length > 0) {
result.extra.controlFreak = {
// Only include active profile data
activeProfile: engine.getActiveProfile(),
// Only include mappings for active profile
mappings: mappings
};
// Ensure the marker node is in the workflow
this._ensureMarkerNodeInWorkflow(result);
}
}
} catch (error) {
console.error("WorkflowIntegration: Error adding mapping data to workflow JSON:", error);
}
return result;
};
}
/**
* Adds a hidden marker node to the workflow JSON if it doesn't exist
* This ensures ComfyUI-Manager can detect the dependency
* @private
* @param {Object} workflowJSON - The workflow JSON object
*/
_ensureMarkerNodeInWorkflow(workflowJSON) {
try {
// Check if any ControlFreak node already exists
const hasMarkerNode = workflowJSON.nodes.some(node =>
node.type === "ControlFreak"
);
// If no marker node exists, add one at a hidden position
if (!hasMarkerNode) {
// Add a hidden marker node to indicate ControlFreak is required
const markerNode = {
id: `ControlFreak_Marker_${Date.now()}`,
type: "ControlFreak",
pos: [-9999, -9999], // Position far off-screen
inputs: {},
outputs: {},
properties: { "hidden": true }
};
workflowJSON.nodes.push(markerNode);
}
} catch (error) {
console.error("WorkflowIntegration: Error adding marker node to workflow:", error);
}
}
/**
* Sync the profile and mappings to the workflow data
* Call this when mappings have changed to update the workflow JSON
*/
syncProfileToWorkflow() {
if (!this.app || !this.app.graph) return;
try {
if (!this.app.graph.extra) {
this.app.graph.extra = {};
}
// Get the mapping engine to export mappings
const engine = this.mappingEngine || contextProvider.get('mappingEngine');
if (engine) {
const mappings = engine.exportMappingsForWorkflow();
// Only update if we have mappings
if (mappings && mappings.length > 0) {
this.app.graph.extra.controlFreak = {
activeProfile: engine.getActiveProfile(),
mappings: mappings
};
// Ensure a marker node exists in the graph
this._addMarkerNodeToGraph();
// Trigger a graph change event so it gets saved
this.app.graph.change();
// Emit event for profile synced to workflow
eventBus.emit('workflow:profileSynced', {
profile: engine.getActiveProfile(),
mappingsCount: mappings.length
});
}
}
} catch (error) {
console.error("WorkflowIntegration: Error syncing profile to workflow:", error);
}
}
/**
* Add a marker node to the current graph if none exists
* @private
*/
_addMarkerNodeToGraph() {
try {
// Check if the marker node already exists in the graph
const hasMarkerNode = this.app.graph.nodes.some(node =>
node.type === "ControlFreak"
);
// If no marker node exists, add one
if (!hasMarkerNode && window.LiteGraph) {
// Create a marker node
let node = LiteGraph.createNode("ControlFreak");
// Position off-screen
node.pos = [-9999, -9999];
// Add to graph
this.app.graph.add(node);
}
} catch (error) {
console.error("WorkflowIntegration: Error adding marker node to graph:", error);
}
}
/**
* Read the profile and mappings from the workflow data
* Call this when a workflow is loaded to get the mapping data
*/
syncProfileFromWorkflow() {
if (!this.app?.graph?.extra?.controlFreak) return;
try {
const workflowData = this.app.graph.extra.controlFreak;
// Get the mapping engine
const engine = this.mappingEngine || contextProvider.get('mappingEngine');
if (engine) {
// Import mappings from workflow by adding them individually
if (workflowData.mappings && Array.isArray(workflowData.mappings)) {
let importedCount = 0;
workflowData.mappings.forEach(mapping => {
// The addMapping method now handles duplicate checks
engine.addMapping(mapping);
// Note: We might need to track if addMapping actually added it if we need an accurate count
importedCount++; // Assuming addMapping handles duplicates and we count all attempts
});
// Save mappings after potentially adding multiple
engine.saveMappings();
// Ensure marker node exists
this._addMarkerNodeToGraph();
// Emit event after import and save
// Retrieve eventBus from context provider to ensure availability
const busForImport = contextProvider.get('eventBus');
if (busForImport) {
busForImport.emit('mappings:imported', workflowData.mappings);
} else {
console.warn("WorkflowIntegration: eventBus not found in contextProvider when emitting mappings:imported");
}
}
// Set active profile
if (workflowData.activeProfile) {
engine.setActiveProfile(workflowData.activeProfile);
}
// Emit event for profile synced from workflow
// Retrieve eventBus from context provider to ensure availability
const bus = contextProvider.get('eventBus');
if (bus) {
bus.emit('workflow:profileLoaded', {
profile: workflowData.activeProfile || 'default',
mappingsCount: workflowData.mappings ? workflowData.mappings.length : 0
});
} else {
console.warn("WorkflowIntegration: eventBus not found in contextProvider during syncProfileFromWorkflow");
}
}
} catch (error) {
console.error("WorkflowIntegration: Error loading profile from workflow:", error);
}
}
/**
* Handle graph loaded event
* Called by ComfyUI when a workflow is loaded
* @param {Object} appInstance - The ComfyUI app instance
*/
static handleGraphLoaded(appInstance) {
// Get mapping engine from context
const mappingEngine = contextProvider.get('mappingEngine');
if (mappingEngine && appInstance.graph) {
try {
// Load mappings from workflow
mappingEngine.loadMappings();
// Ensure all mappings are properly reconnected
if (mappingEngine.reconnectMappings) {
mappingEngine.reconnectMappings();
}
// Check if we have mappings and add marker node if needed
const workflowIntegration = new WorkflowIntegration(appInstance, mappingEngine);
if (appInstance.graph.extra?.controlFreak?.mappings?.length > 0) {
workflowIntegration._addMarkerNodeToGraph();
}
// Emit event for graph loaded
eventBus.emit('workflow:graphLoaded');
} catch (error) {
console.error("WorkflowIntegration: Error handling graph loaded:", error);
}
}
}
}