diff --git a/web/resolver/model_resolver.js b/web/resolver/model_resolver.js index 8171abc..4fb34bd 100644 --- a/web/resolver/model_resolver.js +++ b/web/resolver/model_resolver.js @@ -246,8 +246,9 @@ export class ModelResolver { const target = event.target instanceof Element ? event.target : null; if (target?.closest('#model-resolver-modal, .model-resolver-backdrop')) return; + if (!owner.isLikelyWorkflowTabClickTarget(target)) return; - owner.handleActiveWorkflowRouteChange('document-click'); + owner.handleActiveWorkflowRouteChange('workflow-tab-click'); }; const focusHandler = () => { window.__ModelResolverWorkflowChangeOwner?.handleActiveWorkflowRouteChange('window-focus'); @@ -277,6 +278,28 @@ export class ModelResolver { ]; } + isLikelyWorkflowTabClickTarget(target) { + if (!(target instanceof Element)) return false; + + const tab = target.closest([ + '[data-workflow-id]', + '[data-workflow-name]', + '[data-tab-id*="workflow" i]', + '[aria-controls*="workflow" i]', + '[class*="workflow"][class*="tab" i]', + '[class*="tab"][class*="workflow" i]', + '[class*="workflow"][class*="item" i]' + ].join(',')); + if (tab) return true; + + const roleTab = target.closest('[role="tab"]'); + if (!roleTab) return false; + + const text = roleTab.textContent?.trim() || ''; + const aria = roleTab.getAttribute('aria-label') || roleTab.getAttribute('title') || ''; + return /workflow|unsaved|untitled/i.test(`${text} ${aria}`); + } + handleActiveWorkflowRouteChange(reason = 'workflow-change') { if (!this.dialog?.isVisible()) return; this.dialog.scheduleActiveWorkflowRefresh(reason); diff --git a/web/resolver/shell/workflow_state_methods.js b/web/resolver/shell/workflow_state_methods.js index 118770a..a1fabca 100644 --- a/web/resolver/shell/workflow_state_methods.js +++ b/web/resolver/shell/workflow_state_methods.js @@ -6,13 +6,61 @@ export const workflowStateMethods = { getWorkflowSignature(workflow) { if (!workflow) return null; try { - return JSON.stringify(workflow); + return JSON.stringify(this.getWorkflowSignatureData(workflow)); } catch (error) { console.warn('Model Resolver: workflow signature generation failed', error); return null; } }, + getWorkflowSignatureData(workflow) { + const normalizeNode = (node = {}) => ({ + id: node.id, + type: node.type, + mode: node.mode, + flags: node.flags, + inputs: Array.isArray(node.inputs) + ? node.inputs.map(input => ({ + name: input?.name, + type: input?.type, + link: input?.link + })) + : [], + outputs: Array.isArray(node.outputs) + ? node.outputs.map(output => ({ + name: output?.name, + type: output?.type, + links: output?.links || [] + })) + : [], + widgets_values: node.widgets_values || [], + properties: node.properties || {} + }); + + const normalizeDefinition = (definition = {}) => ({ + nodes: Array.isArray(definition.nodes) + ? definition.nodes.map(normalizeNode).sort((a, b) => String(a.id).localeCompare(String(b.id))) + : [], + links: definition.links || [] + }); + + const definitions = workflow.definitions && typeof workflow.definitions === 'object' + ? Object.fromEntries( + Object.entries(workflow.definitions) + .sort(([a], [b]) => String(a).localeCompare(String(b))) + .map(([key, definition]) => [key, normalizeDefinition(definition)]) + ) + : {}; + + return { + nodes: Array.isArray(workflow.nodes) + ? workflow.nodes.map(normalizeNode).sort((a, b) => String(a.id).localeCompare(String(b.id))) + : [], + links: workflow.links || [], + definitions + }; + }, + getActiveWorkflowRouteKey() { try { return window.location?.hash || ''; @@ -217,14 +265,8 @@ export const workflowStateMethods = { const routeChanged = currentRoute !== this.activeWorkflowRouteKey; const signatureChanged = signature && signature !== this.activeWorkflowSignature; const graphStillLooksOld = routeChanged && previousSignature && signature === previousSignature; - const possiblePendingGraphSwitch = ( - reason === 'document-click' && - previousSignature && - signature === previousSignature - ); - const maxAttempts = possiblePendingGraphSwitch ? 3 : 8; - if ((!signature || graphStillLooksOld || possiblePendingGraphSwitch) && attempt < maxAttempts) { + if ((!signature || graphStillLooksOld) && attempt < 8) { setTimeout(() => { this.refreshForActiveWorkflowChange({ reason,