- BUG !! Critical workflow loading/tab duplication bug
- The extension was dangerously overriding ComfyUI's core loadGraphData() function, which interfered with workflow management and caused infinite tab creation when loading workflows.
- Removed the unsafe loadGraphData override completely
- Added safe workflow monitoring using setInterval polling instead of function overriding
- Added proper cleanup of all intervals in destroyed() method
This commit is contained in:
Alex Furer
2025-12-15 19:20:49 +01:00
parent d4b781e82f
commit 84c6ad0df9
4 changed files with 148 additions and 152 deletions
+7
View File
@@ -1,5 +1,12 @@
## Changelog
### v0.2.2
- Critical workflow loading/tab duplication bug
- The extension was dangerously overriding ComfyUI's core loadGraphData() function, which interfered with workflow management and caused infinite tab creation when loading workflows.
- Removed the unsafe loadGraphData override completely
- Added safe workflow monitoring using setInterval polling instead of function overriding
- Added proper cleanup of all intervals in destroyed() method
### v0.2.1
- Updated README with screenshots
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "comfyui-af-find-nodes",
"version": "0.2.1",
"version": "0.2.2",
"description": "A ComfyUI extension that allows you to find and locate nodes by their ID, title, pack, or type in complex workflows.",
"author": "Alex Furer",
"license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-af-find-nodes"
description = "A ComfyUI extension that allows you to find and locate nodes by their ID, title, pack, or type in complex workflows."
version = "0.2.1"
version = "0.2.2"
license = {file = "LICENSE"}
dependencies = []
+139 -150
View File
@@ -24,32 +24,97 @@ if (window.AF_Find_Nodes_Widget) {
} else {
class AF_Find_Nodes_Widget {
constructor() {
this.searchPanel = null;
this.isVisible = false;
this.highlightedNode = null;
this.originalNodeColors = new Map();
this.searchHistory = [];
this.maxHistory = 10;
this.inspectorMode = false;
this.searchTimeout = null;
this.experimentalEnabled = localStorage.getItem('af-find-node-experimental') === 'true'; // ← Change to ONLY true if explicitly set
constructor() {
this.searchPanel = null;
this.isVisible = false;
this.highlightedNode = null;
this.originalNodeColors = new Map();
this.searchHistory = [];
this.maxHistory = 10;
this.inspectorMode = false;
this.searchTimeout = null;
this.experimentalEnabled = localStorage.getItem('af-find-node-experimental') === 'true';
this.workflowPackIndex = {}; // { "was": [12, 15, 23], "rgthree": [7, 8] }
this.workflowTypeIndex = {}; // { "KSampler": [10, 11], "CLIPTextEncode": [1, 2] }
this.lastWorkflowSignature = null;
this.scanCompleted = false;
// Remember last tab state - default to 'id' if not set
this.currentTab = localStorage.getItem('af-find-node-last-tab') || 'id';
this.tabInputs = {};
this.tabHistory = {
id: JSON.parse(localStorage.getItem('af-find-node-history-id') || '[]'),
title: JSON.parse(localStorage.getItem('af-find-node-history-title') || '[]'),
pack: JSON.parse(localStorage.getItem('af-find-node-history-pack') || '[]'),
type: JSON.parse(localStorage.getItem('af-find-node-history-type') || '[]')
};
}
this.workflowPackIndex = {};
this.workflowTypeIndex = {};
this.lastWorkflowSignature = null;
this.scanCompleted = false;
this.currentTab = localStorage.getItem('af-find-node-last-tab') || 'id';
this.tabInputs = {};
this.tabHistory = {
id: JSON.parse(localStorage.getItem('af-find-node-history-id') || '[]'),
title: JSON.parse(localStorage.getItem('af-find-node-history-title') || '[]'),
pack: JSON.parse(localStorage.getItem('af-find-node-history-pack') || '[]'),
type: JSON.parse(localStorage.getItem('af-find-node-history-type') || '[]')
};
// Add workflow change monitoring WITHOUT overriding anything
this.workflowMonitorInterval = null;
this.lastNodeCount = 0;
this.selectionInterval = null;
this.lastSelectedNodes = [];
}
setupSelectionMonitor() {
if (this.inspectorMode && !this.selectionInterval) {
this.selectionInterval = setInterval(() => {
this.checkNodeSelection();
}, 300);
} else if (!this.inspectorMode && this.selectionInterval) {
clearInterval(this.selectionInterval);
this.selectionInterval = null;
this.lastSelectedNodes = [];
}
}
checkNodeSelection() {
if (!this.inspectorMode || !app.canvas || !app.canvas.selected_nodes) return;
const currentSelected = app.canvas.selected_nodes;
if (!currentSelected || Object.keys(currentSelected).length === 0) return;
// Get the first selected node
const nodeId = Object.keys(currentSelected)[0];
const node = currentSelected[nodeId];
if (node && !this.lastSelectedNodes.includes(node.id)) {
this.AF_Find_Nodes_HandleNodeClick(node);
this.lastSelectedNodes = [node.id];
}
}
// SAFE: Monitor workflow changes without overriding ComfyUI functions
startWorkflowMonitor() {
if (this.workflowMonitorInterval) return;
this.workflowMonitorInterval = setInterval(() => {
if (!app.graph || !app.graph.nodes) return;
const currentCount = app.graph.nodes.length;
// Check if workflow changed significantly (more than just selection changes)
if (Math.abs(currentCount - this.lastNodeCount) > 0) {
this.lastNodeCount = currentCount;
// Debounce the scan
clearTimeout(this.workflowScanTimeout);
this.workflowScanTimeout = setTimeout(() => {
this.scanWorkflowForPacks();
}, 1000);
}
}, 2000); // Check every 2 seconds - very conservative
}
stopWorkflowMonitor() {
if (this.workflowMonitorInterval) {
clearInterval(this.workflowMonitorInterval);
this.workflowMonitorInterval = null;
}
if (this.workflowScanTimeout) {
clearTimeout(this.workflowScanTimeout);
this.workflowScanTimeout = null;
}
}
createSearchPanel() {
// Create main container
@@ -827,7 +892,7 @@ showWorkflowStats() {
this.searchPanel.style.display = 'block';
this.isVisible = true;
// Scan workflow for packs/types
// Scan workflow when panel opens (safe, on-demand scanning)
this.scanWorkflowForPacks();
// Show quick stats if we have data
@@ -858,16 +923,22 @@ showWorkflowStats() {
// Update tab appearance to reflect experimental setting
this.updateTabAppearance();
// Start monitoring when panel is open
this.startWorkflowMonitor();
}
AF_Find_Nodes_HidePanel() {
if (this.searchPanel) {
this.searchPanel.style.display = 'none';
}
this.isVisible = false;
this.AF_Find_Nodes_ClearAll();
this.AF_Find_Nodes_SetInspectorMode(false);
}
AF_Find_Nodes_HidePanel() {
if (this.searchPanel) {
this.searchPanel.style.display = 'none';
}
this.isVisible = false;
this.AF_Find_Nodes_ClearAll();
this.AF_Find_Nodes_SetInspectorMode(false);
// Stop monitoring when panel is closed
this.stopWorkflowMonitor();
}
AF_Find_Nodes_TogglePanel() {
if (this.isVisible) {
@@ -1413,24 +1484,27 @@ showWorkflowStats() {
} */
}
AF_Find_Nodes_SetInspectorMode(enabled) {
if (enabled && this.currentTab !== 'id') {
this.switchTab('id');
}
this.inspectorMode = enabled;
const btn = document.getElementById('af-find-nodes-inspector-toggle');
if (btn) {
btn.style.background = enabled ? '#ff9800' : '#2196F3';
btn.textContent = enabled ? 'Exit Inspector' : 'Inspector';
}
if (enabled) {
this.AF_Find_Nodes_UpdateResults('Inspector mode active. Click any node to see its ID.');
} else {
this.AF_Find_Nodes_UpdateResults('Inspector mode disabled.');
}
}
AF_Find_Nodes_SetInspectorMode(enabled) {
if (enabled && this.currentTab !== 'id') {
this.switchTab('id');
}
this.inspectorMode = enabled;
const btn = document.getElementById('af-find-nodes-inspector-toggle');
if (btn) {
btn.style.background = enabled ? '#ff9800' : '#2196F3';
btn.textContent = enabled ? 'Exit Inspector' : 'Inspector';
}
// Setup or cleanup selection monitor
this.setupSelectionMonitor();
if (enabled) {
this.AF_Find_Nodes_UpdateResults('Inspector mode active. Click any node to see its ID.');
} else {
this.AF_Find_Nodes_UpdateResults('Inspector mode disabled.');
}
}
AF_Find_Nodes_HandleNodeClick(node) {
if (this.inspectorMode && node) {
@@ -1564,7 +1638,7 @@ showWorkflowStats() {
// Initialize the search widget with guard against double initialization
window.AF_Find_Nodes_Widget = new AF_Find_Nodes_Widget();
// Register the extension
// Register the extension - SAFE VERSION with NO overrides
app.registerExtension({
name: "AF-Find-Nodes",
@@ -1578,107 +1652,22 @@ app.registerExtension({
window.AF_Find_Nodes_Widget.AF_Find_Nodes_HandleKeyboard(e);
});
// Scan when workflow loads
const originalLoadGraphData = app.loadGraphData;
if (originalLoadGraphData) {
app.loadGraphData = async function(data) {
const result = await originalLoadGraphData.call(this, data);
// Scan after a short delay
setTimeout(() => {
if (window.AF_Find_Nodes_Widget) {
window.AF_Find_Nodes_Widget.scanWorkflowForPacks();
}
}, 500);
return result;
};
}
// Hook into node selection changes for inspector mode
let lastSelectedNodes = [];
// Monitor for node selection changes
const checkNodeSelection = () => {
if (!window.AF_Find_Nodes_Widget.inspectorMode) return;
const currentSelected = app.canvas.selected_nodes;
if (!currentSelected || Object.keys(currentSelected).length === 0) return;
// Get the first selected node
const nodeId = Object.keys(currentSelected)[0];
const node = currentSelected[nodeId];
if (node && !lastSelectedNodes.includes(node.id)) {
window.AF_Find_Nodes_Widget.AF_Find_Nodes_HandleNodeClick(node);
lastSelectedNodes = [node.id];
}
};
// Check selection periodically when inspector is active
let selectionInterval = null;
// In the setup() function, replace the setInterval with:
const setupSelectionMonitor = () => {
if (window.AF_Find_Nodes_Widget.inspectorMode && !selectionInterval) {
selectionInterval = setInterval(() => {
checkNodeSelection();
}, 300); // Reduced from 100ms to 300ms
} else if (!window.AF_Find_Nodes_Widget.inspectorMode && selectionInterval) {
clearInterval(selectionInterval);
selectionInterval = null;
lastSelectedNodes = [];
}
};
// Monitor inspector mode changes
const originalSetInspectorMode = window.AF_Find_Nodes_Widget.AF_Find_Nodes_SetInspectorMode;
window.AF_Find_Nodes_Widget.AF_Find_Nodes_SetInspectorMode = function(enabled) {
originalSetInspectorMode.call(this, enabled);
setupSelectionMonitor();
};
// Also try hooking into the canvas click event
const canvas = app.canvas.canvas;
let lastInspectorClick = 0;
canvas.addEventListener('click', (e) => {
if (!window.AF_Find_Nodes_Widget.inspectorMode) return;
// Debounce clicks to prevent multiple rapid triggers
const now = Date.now();
if (now - lastInspectorClick < 200) return; // 200ms debounce
lastInspectorClick = now;
// Only proceed if we're actually in inspector mode
if (!window.AF_Find_Nodes_Widget.inspectorMode) return;
// Get canvas coordinates
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Convert to canvas coordinates
const canvasX = (x - app.canvas.ds.offset[0]) / app.canvas.ds.scale;
const canvasY = (y - app.canvas.ds.offset[1]) / app.canvas.ds.scale;
// Find node at position
const node = app.graph.getNodeOnPos(canvasX, canvasY);
if (node) {
window.AF_Find_Nodes_Widget.AF_Find_Nodes_HandleNodeClick(node);
e.stopPropagation();
}
}, true);
console.log("AF - Find Nodes extension loaded. Use Ctrl+Shift+F to open search panel.");
// Start workflow monitoring when extension loads (but only scans when needed)
window.AF_Find_Nodes_Widget.startWorkflowMonitor();
},
async destroyed() {
// Clean up interval when extension is destroyed
if (selectionInterval) {
clearInterval(selectionInterval);
selectionInterval = null;
// Clean up all intervals and timeouts
if (window.AF_Find_Nodes_Widget) {
window.AF_Find_Nodes_Widget.stopWorkflowMonitor();
// Clean up selection interval if it exists
if (window.AF_Find_Nodes_Widget.selectionInterval) {
clearInterval(window.AF_Find_Nodes_Widget.selectionInterval);
window.AF_Find_Nodes_Widget.selectionInterval = null;
}
}
}
});