From 0b515b13b440d935221f55b80f6d0aa2dc5c50f1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 2 Apr 2025 13:55:41 +1000 Subject: [PATCH 1/2] chore(ui): bump @xyflow/react to latest --- invokeai/frontend/web/package.json | 2 +- invokeai/frontend/web/pnpm-lock.yaml | 48 ++++++++++++++++------------ 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index 6d1efdce31c..165a7471241 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -62,7 +62,7 @@ "@nanostores/react": "^0.7.3", "@reduxjs/toolkit": "2.6.1", "@roarr/browser-log-writer": "^1.3.0", - "@xyflow/react": "^12.4.2", + "@xyflow/react": "^12.5.3", "async-mutex": "^0.5.0", "chakra-react-select": "^4.9.2", "cmdk": "^1.0.0", diff --git a/invokeai/frontend/web/pnpm-lock.yaml b/invokeai/frontend/web/pnpm-lock.yaml index 2cceb3bcfa0..ed07c5eae2f 100644 --- a/invokeai/frontend/web/pnpm-lock.yaml +++ b/invokeai/frontend/web/pnpm-lock.yaml @@ -36,8 +36,8 @@ dependencies: specifier: ^1.3.0 version: 1.3.0 '@xyflow/react': - specifier: ^12.4.2 - version: 12.4.2(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) + specifier: ^12.5.3 + version: 12.5.3(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) async-mutex: specifier: ^0.5.0 version: 0.5.0 @@ -3323,7 +3323,7 @@ packages: /@types/d3-drag@3.0.7: resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} dependencies: - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 dev: false /@types/d3-interpolate@3.0.4: @@ -3332,21 +3332,21 @@ packages: '@types/d3-color': 3.1.3 dev: false - /@types/d3-selection@3.0.10: - resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==} + /@types/d3-selection@3.0.11: + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} dev: false - /@types/d3-transition@3.0.8: - resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==} + /@types/d3-transition@3.0.9: + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} dependencies: - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 dev: false /@types/d3-zoom@3.0.8: resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} dependencies: '@types/d3-interpolate': 3.0.4 - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 dev: false /@types/diff-match-patch@1.0.36: @@ -3951,28 +3951,28 @@ packages: resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} dev: false - /@xyflow/react@12.4.2(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-AFJKVc/fCPtgSOnRst3xdYJwiEcUN9lDY7EO/YiRvFHYCJGgfzg+jpvZjkTOnBLGyrMJre9378pRxAc3fsR06A==} + /@xyflow/react@12.5.3(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-saovy/aQRoW8qQoIqMFUtmC3F6oEV7n6+J1pVbhSG45NI/hOFvK0qozsIPKqX5Va6lGQnkl/o53NHLja3NiweQ==} peerDependencies: react: '>=17' react-dom: '>=17' dependencies: - '@xyflow/system': 0.0.50 + '@xyflow/system': 0.0.53 classcat: 5.0.5 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.11)(react@18.3.1) + zustand: 4.5.6(@types/react@18.3.11)(react@18.3.1) transitivePeerDependencies: - '@types/react' - immer dev: false - /@xyflow/system@0.0.50: - resolution: {integrity: sha512-HVUZd4LlY88XAaldFh2nwVxDOcdIBxGpQ5txzwfJPf+CAjj2BfYug1fHs2p4yS7YO8H6A3EFJQovBE8YuHkAdg==} + /@xyflow/system@0.0.53: + resolution: {integrity: sha512-QTWieiTtvNYyQAz1fxpzgtUGXNpnhfh6vvZa7dFWpWS2KOz6bEHODo/DTK3s07lDu0Bq0Db5lx/5M5mNjb9VDQ==} dependencies: '@types/d3-drag': 3.0.7 - '@types/d3-selection': 3.0.10 - '@types/d3-transition': 3.0.8 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 '@types/d3-zoom': 3.0.8 d3-drag: 3.0.0 d3-selection: 3.0.0 @@ -9123,6 +9123,14 @@ packages: react: 18.3.1 dev: false + /use-sync-external-store@1.5.0(react@18.3.1): + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + dependencies: + react: 18.3.1 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true @@ -9567,8 +9575,8 @@ packages: /zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - /zustand@4.5.5(@types/react@18.3.11)(react@18.3.1): - resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} + /zustand@4.5.6(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==} engines: {node: '>=12.7.0'} peerDependencies: '@types/react': '>=16.8' @@ -9584,5 +9592,5 @@ packages: dependencies: '@types/react': 18.3.11 react: 18.3.1 - use-sync-external-store: 1.2.2(react@18.3.1) + use-sync-external-store: 1.5.0(react@18.3.1) dev: false From af22d58cfe32ff49007471c2e2b62996a9fc6cd2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 2 Apr 2025 14:10:48 +1000 Subject: [PATCH 2/2] fix(ui): handling for invalid edges when loading workflows Previously, reactflow appears to have handled an edge case when using its `applyChanges` utility. If a change was provided without an item, it would skip that change. For example, an "add edge" change that somehow passed `null` as the edge, instead of a valid edge. In our workflow loading and validation logic, invalid edges were removed from the array using `delete edges[i]`. This left "holes" in the array of edges. We then asked `reactflow` to add these edges to state. When it encountered one of the "holes", it skipped over it. In a recent release (unsure which, somewhere between the latest v11 and ~v12.4) this seems to have changed. It no longer skips over the "holes" and instead trusts the data. This can cause a couple issues: - Error when loading the workflow if `reactflow` attempt to do anything with the nonexistent edge. - If somehow the workflow makes it into state with "holes" in the array of edges, all sorts of other stuff breaks when our code does anything with the nonexistent edge. Two-part fix: - Update the invalid edge handling to not use `delete edges[i]`. Instead, as we check each edge, we add invalid ones to a set. Then, after all the checks are finished, filter out the invalid edges. The resultant edges array has no holes. - Simplify the logic around setting nodes and edges in redux. Previously we were using `reactflow`'s `applyChanges` utils, but this does literally nothing except take extra CPU cycles. We can simply set the loaded nodes and edges directly in redux. Perhaps we were using `applyChanges` because it addressed the "holes" issue? Not sure. But we don't need it now. Closes #7868 --- .../src/features/nodes/store/nodesSlice.ts | 27 ++----------------- .../nodes/util/workflow/validateWorkflow.ts | 14 +++++++--- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts index 32b2965895b..b311bc13ac0 100644 --- a/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts +++ b/invokeai/frontend/web/src/features/nodes/store/nodesSlice.ts @@ -470,31 +470,8 @@ export const nodesSlice = createSlice({ builder.addCase(workflowLoaded, (state, action) => { const { nodes, edges } = action.payload; - const changes: NodeChange[] = []; - for (const node of nodes) { - if (node.type === 'notes') { - changes.push({ - type: 'add', - item: { - ...SHARED_NODE_PROPERTIES, - ...node, - }, - }); - } else if (node.type === 'invocation') { - changes.push({ - type: 'add', - item: { - ...SHARED_NODE_PROPERTIES, - ...node, - }, - }); - } - } - state.nodes = applyNodeChanges(changes, []); - state.edges = applyEdgeChanges( - edges.map((edge) => ({ type: 'add', item: edge })), - [] - ); + state.nodes = nodes.map((node) => ({ ...SHARED_NODE_PROPERTIES, ...node })); + state.edges = edges; }); }, }); diff --git a/invokeai/frontend/web/src/features/nodes/util/workflow/validateWorkflow.ts b/invokeai/frontend/web/src/features/nodes/util/workflow/validateWorkflow.ts index f8978c74b14..d3220fcc42c 100644 --- a/invokeai/frontend/web/src/features/nodes/util/workflow/validateWorkflow.ts +++ b/invokeai/frontend/web/src/features/nodes/util/workflow/validateWorkflow.ts @@ -148,7 +148,11 @@ export const validateWorkflow = async (args: ValidateWorkflowArgs): Promise { + + // Stash invalid edges here to be deleted later + const edgesToDelete = new Set(); + + for (const edge of edges) { // Validate each edge. If the edge is invalid, we must remove it to prevent runtime errors with reactflow. const sourceNode = nodes.find(({ id }) => id === edge.source); const targetNode = nodes.find(({ id }) => id === edge.target); @@ -215,8 +219,7 @@ export const validateWorkflow = async (args: ValidateWorkflowArgs): Promise !edgesToDelete.has(id)); // Migrated exposed fields to form elements if they exist and the form does not // Note: If the form is invalid per its zod schema, it will be reset to a default, empty form!