chore(ui): upgrade reactflow to v12

This commit is contained in:
psychedelicious
2025-01-26 10:11:05 +11:00
parent 4b178fdeca
commit 0371881349
49 changed files with 277 additions and 510 deletions

View File

@@ -62,6 +62,7 @@
"@nanostores/react": "^0.7.3",
"@reduxjs/toolkit": "2.2.3",
"@roarr/browser-log-writer": "^1.3.0",
"@xyflow/react": "^12.4.2",
"async-mutex": "^0.5.0",
"chakra-react-select": "^4.9.2",
"cmdk": "^1.0.0",
@@ -98,7 +99,6 @@
"react-resizable-panels": "^2.1.4",
"react-use": "^17.5.1",
"react-virtuoso": "^4.10.4",
"reactflow": "^11.11.4",
"redux-dynamic-middlewares": "^2.2.0",
"redux-remember": "^5.1.0",
"redux-undo": "^1.1.0",

View File

@@ -35,6 +35,9 @@ dependencies:
'@roarr/browser-log-writer':
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)
async-mutex:
specifier: ^0.5.0
version: 0.5.0
@@ -143,9 +146,6 @@ dependencies:
react-virtuoso:
specifier: ^4.10.4
version: 4.10.4(react-dom@18.3.1)(react@18.3.1)
reactflow:
specifier: ^11.11.4
version: 11.11.4(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
redux-dynamic-middlewares:
specifier: ^2.2.0
version: 2.2.0
@@ -2395,114 +2395,6 @@ packages:
react: 18.3.1
dev: false
/@reactflow/background@11.3.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.4(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
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)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/controls@11.2.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.4(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
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)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/core@11.11.4(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@types/d3': 7.4.3
'@types/d3-drag': 3.0.7
'@types/d3-selection': 3.0.10
'@types/d3-zoom': 3.0.8
classcat: 5.0.5
d3-drag: 3.0.0
d3-selection: 3.0.0
d3-zoom: 3.0.0
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)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/minimap@11.7.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.4(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
'@types/d3-selection': 3.0.10
'@types/d3-zoom': 3.0.8
classcat: 5.0.5
d3-selection: 3.0.0
d3-zoom: 3.0.0
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)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/node-resizer@2.2.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.4(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
classcat: 5.0.5
d3-drag: 3.0.0
d3-selection: 3.0.0
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)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@reactflow/node-toolbar@1.3.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/core': 11.11.4(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
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)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@redocly/ajv@8.11.2:
resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==}
dependencies:
@@ -3673,137 +3565,26 @@ packages:
'@types/node': 20.16.10
dev: true
/@types/d3-array@3.2.1:
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
dev: false
/@types/d3-axis@3.0.6:
resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
dependencies:
'@types/d3-selection': 3.0.10
dev: false
/@types/d3-brush@3.0.6:
resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
dependencies:
'@types/d3-selection': 3.0.10
dev: false
/@types/d3-chord@3.0.6:
resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
dev: false
/@types/d3-color@3.1.3:
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
dev: false
/@types/d3-contour@3.0.6:
resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
dependencies:
'@types/d3-array': 3.2.1
'@types/geojson': 7946.0.14
dev: false
/@types/d3-delaunay@6.0.4:
resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
dev: false
/@types/d3-dispatch@3.0.6:
resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==}
dev: false
/@types/d3-drag@3.0.7:
resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
dependencies:
'@types/d3-selection': 3.0.10
dev: false
/@types/d3-dsv@3.0.7:
resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
dev: false
/@types/d3-ease@3.0.2:
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
dev: false
/@types/d3-fetch@3.0.7:
resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
dependencies:
'@types/d3-dsv': 3.0.7
dev: false
/@types/d3-force@3.0.10:
resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
dev: false
/@types/d3-format@3.0.4:
resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
dev: false
/@types/d3-geo@3.1.0:
resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
dependencies:
'@types/geojson': 7946.0.14
dev: false
/@types/d3-hierarchy@3.1.7:
resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
dev: false
/@types/d3-interpolate@3.0.4:
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
dependencies:
'@types/d3-color': 3.1.3
dev: false
/@types/d3-path@3.1.0:
resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==}
dev: false
/@types/d3-polygon@3.0.2:
resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
dev: false
/@types/d3-quadtree@3.0.6:
resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
dev: false
/@types/d3-random@3.0.3:
resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
dev: false
/@types/d3-scale-chromatic@3.0.3:
resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==}
dev: false
/@types/d3-scale@4.0.8:
resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==}
dependencies:
'@types/d3-time': 3.0.3
dev: false
/@types/d3-selection@3.0.10:
resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==}
dev: false
/@types/d3-shape@3.1.6:
resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==}
dependencies:
'@types/d3-path': 3.1.0
dev: false
/@types/d3-time-format@4.0.3:
resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
dev: false
/@types/d3-time@3.0.3:
resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==}
dev: false
/@types/d3-timer@3.0.2:
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
dev: false
/@types/d3-transition@3.0.8:
resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==}
dependencies:
@@ -3817,41 +3598,6 @@ packages:
'@types/d3-selection': 3.0.10
dev: false
/@types/d3@7.4.3:
resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
dependencies:
'@types/d3-array': 3.2.1
'@types/d3-axis': 3.0.6
'@types/d3-brush': 3.0.6
'@types/d3-chord': 3.0.6
'@types/d3-color': 3.1.3
'@types/d3-contour': 3.0.6
'@types/d3-delaunay': 6.0.4
'@types/d3-dispatch': 3.0.6
'@types/d3-drag': 3.0.7
'@types/d3-dsv': 3.0.7
'@types/d3-ease': 3.0.2
'@types/d3-fetch': 3.0.7
'@types/d3-force': 3.0.10
'@types/d3-format': 3.0.4
'@types/d3-geo': 3.1.0
'@types/d3-hierarchy': 3.1.7
'@types/d3-interpolate': 3.0.4
'@types/d3-path': 3.1.0
'@types/d3-polygon': 3.0.2
'@types/d3-quadtree': 3.0.6
'@types/d3-random': 3.0.3
'@types/d3-scale': 4.0.8
'@types/d3-scale-chromatic': 3.0.3
'@types/d3-selection': 3.0.10
'@types/d3-shape': 3.1.6
'@types/d3-time': 3.0.3
'@types/d3-time-format': 4.0.3
'@types/d3-timer': 3.0.2
'@types/d3-transition': 3.0.8
'@types/d3-zoom': 3.0.8
dev: false
/@types/dateformat@5.0.2:
resolution: {integrity: sha512-M95hNBMa/hnwErH+a+VOD/sYgTmo15OTYTM2Hr52/e0OdOuY+Crag+kd3/ioZrhg0WGbl9Sm3hR7UU+MH6rfOw==}
dev: true
@@ -4450,6 +4196,34 @@ 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==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@xyflow/system': 0.0.50
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)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/@xyflow/system@0.0.50:
resolution: {integrity: sha512-HVUZd4LlY88XAaldFh2nwVxDOcdIBxGpQ5txzwfJPf+CAjj2BfYug1fHs2p4yS7YO8H6A3EFJQovBE8YuHkAdg==}
dependencies:
'@types/d3-drag': 3.0.7
'@types/d3-selection': 3.0.10
'@types/d3-transition': 3.0.8
'@types/d3-zoom': 3.0.8
d3-drag: 3.0.0
d3-selection: 3.0.0
d3-zoom: 3.0.0
dev: false
/@zag-js/dom-query@0.31.1:
resolution: {integrity: sha512-oiuohEXAXhBxpzzNm9k2VHGEOLC1SXlXSbRPcfBZ9so5NRQUA++zCE7cyQJqGLTZR0t3itFLlZqDbYEXRrefwg==}
dev: false
@@ -8481,25 +8255,6 @@ packages:
dependencies:
loose-envify: 1.4.0
/reactflow@11.11.4(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==}
peerDependencies:
react: '>=17'
react-dom: '>=17'
dependencies:
'@reactflow/background': 11.3.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/controls': 11.2.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/core': 11.11.4(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/minimap': 11.7.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/node-resizer': 2.2.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
'@reactflow/node-toolbar': 1.3.14(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
- immer
dev: false
/readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}

View File

@@ -1,39 +0,0 @@
import { Box } from '@invoke-ai/ui-library';
import { memo, useMemo } from 'react';
type Props = {
isSelected: boolean;
isHovered: boolean;
};
const SelectionOverlay = ({ isSelected, isHovered }: Props) => {
const shadow = useMemo(() => {
if (isSelected && isHovered) {
return 'nodeHoveredSelected';
}
if (isSelected) {
return 'nodeSelected';
}
if (isHovered) {
return 'nodeHovered';
}
return undefined;
}, [isHovered, isSelected]);
return (
<Box
className="selection-box"
position="absolute"
top={0}
insetInlineEnd={0}
bottom={0}
insetInlineStart={0}
borderRadius="base"
opacity={isSelected || isHovered ? 1 : 0.5}
transitionProperty="common"
transitionDuration="0.1s"
pointerEvents="none"
shadow={shadow}
/>
);
};
export default memo(SelectionOverlay);

View File

@@ -1,4 +1,4 @@
import 'reactflow/dist/style.css';
import '@xyflow/react/dist/base.css';
import { Flex } from '@invoke-ai/ui-library';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';

View File

@@ -12,6 +12,7 @@ import {
Text,
} from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type { EdgeChange, NodeChange } from '@xyflow/react';
import { useAppSelector, useAppStore } from 'app/store/storeHooks';
import { CommandEmpty, CommandItem, CommandList, CommandRoot } from 'cmdk';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
@@ -31,6 +32,7 @@ import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupied
import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection';
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes';
import type { AnyEdge, AnyNode } from 'features/nodes/types/invocation';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import { toast } from 'features/toast/toast';
@@ -41,7 +43,6 @@ import type { ChangeEvent } from 'react';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCircuitryBold, PiFlaskBold, PiHammerBold, PiLightningFill } from 'react-icons/pi';
import type { EdgeChange, NodeChange } from 'reactflow';
import type { S } from 'services/api/types';
const useThrottle = <T,>(value: T, limit: number) => {
@@ -95,8 +96,8 @@ const useAddNode = () => {
node.selected = true;
// Deselect all other nodes and edges
const nodeChanges: NodeChange[] = [{ type: 'add', item: node }];
const edgeChanges: EdgeChange[] = [];
const nodeChanges: NodeChange<AnyNode>[] = [{ type: 'add', item: node }];
const edgeChanges: EdgeChange<AnyEdge>[] = [];
nodes.forEach(({ id, selected }) => {
if (selected) {
nodeChanges.push({ type: 'select', id, selected: false });

View File

@@ -1,5 +1,19 @@
import { useGlobalMenuClose, useToken } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type {
EdgeChange,
HandleType,
NodeChange,
OnEdgesChange,
OnInit,
OnMoveEnd,
OnNodesChange,
OnReconnect,
ProOptions,
ReactFlowProps,
ReactFlowState,
} from '@xyflow/react';
import { Background, ReactFlow, useStore as useReactFlowStore, useUpdateNodeInternals } from '@xyflow/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
import { useConnection } from 'features/nodes/hooks/useConnection';
@@ -30,23 +44,11 @@ import {
} from 'features/nodes/store/selectors';
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import { selectSelectionMode, selectShouldSnapToGrid } from 'features/nodes/store/workflowSettingsSlice';
import type { AnyEdge, AnyNode } from 'features/nodes/types/invocation';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import type { CSSProperties, MouseEvent } from 'react';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import type {
EdgeChange,
NodeChange,
OnEdgesChange,
OnEdgeUpdateFunc,
OnInit,
OnMoveEnd,
OnNodesChange,
ProOptions,
ReactFlowProps,
ReactFlowState,
} from 'reactflow';
import { Background, ReactFlow, useStore as useReactFlowStore, useUpdateNodeInternals } from 'reactflow';
import CustomConnectionLine from './connectionLines/CustomConnectionLine';
import InvocationCollapsedEdge from './edges/InvocationCollapsedEdge';
@@ -58,13 +60,13 @@ import NotesNode from './nodes/Notes/NotesNode';
const edgeTypes = {
collapsed: InvocationCollapsedEdge,
default: InvocationDefaultEdge,
};
} as const;
const nodeTypes = {
invocation: InvocationNodeWrapper,
current_image: CurrentImageNode,
notes: NotesNode,
};
} as const;
// TODO: can we support reactflow? if not, we could style the attribution so it matches the app
const proOptions: ProOptions = { hideAttribution: true };
@@ -97,7 +99,7 @@ export const Flow = memo(() => {
const [borderRadius] = useToken('radii', ['base']);
const flowStyles = useMemo<CSSProperties>(() => ({ borderRadius }), [borderRadius]);
const onNodesChange: OnNodesChange = useCallback(
const onNodesChange: OnNodesChange<AnyNode> = useCallback(
(nodeChanges) => {
dispatch(nodesChanged(nodeChanges));
const flow = $flow.get();
@@ -112,7 +114,7 @@ export const Flow = memo(() => {
[dispatch, needsFit]
);
const onEdgesChange: OnEdgesChange = useCallback(
const onEdgesChange: OnEdgesChange<AnyEdge> = useCallback(
(changes) => {
if (changes.length > 0) {
dispatch(edgesChanged(changes));
@@ -130,7 +132,7 @@ export const Flow = memo(() => {
onCloseGlobal();
}, [onCloseGlobal]);
const onInit: OnInit = useCallback((flow) => {
const onInit: OnInit<AnyNode, AnyEdge> = useCallback((flow) => {
$flow.set(flow);
flow.fitView();
}, []);
@@ -158,13 +160,13 @@ export const Flow = memo(() => {
* where the edge is deleted if you click it accidentally).
*/
const onEdgeUpdateStart: NonNullable<ReactFlowProps['onEdgeUpdateStart']> = useCallback((e, edge, _handleType) => {
const onReconnectStart = useCallback((event: MouseEvent, edge: AnyEdge, _handleType: HandleType) => {
$edgePendingUpdate.set(edge);
$didUpdateEdge.set(false);
$lastEdgeUpdateMouseEvent.set(e);
$lastEdgeUpdateMouseEvent.set(event);
}, []);
const onEdgeUpdate: OnEdgeUpdateFunc = useCallback(
const onReconnect: OnReconnect = useCallback(
(oldEdge, newConnection) => {
// This event is fired when an edge update is successful
$didUpdateEdge.set(true);
@@ -183,7 +185,7 @@ export const Flow = memo(() => {
[dispatch, updateNodeInternals]
);
const onEdgeUpdateEnd: NonNullable<ReactFlowProps['onEdgeUpdateEnd']> = useCallback(
const onReconnectEnd: NonNullable<ReactFlowProps['onReconnectEnd']> = useCallback(
(e, edge, _handleType) => {
const didUpdateEdge = $didUpdateEdge.get();
// Fall back to a reasonable default event
@@ -220,8 +222,8 @@ export const Flow = memo(() => {
const selectAll = useCallback(() => {
const { nodes, edges } = selectNodesSlice(store.getState());
const nodeChanges: NodeChange[] = [];
const edgeChanges: EdgeChange[] = [];
const nodeChanges: NodeChange<AnyNode>[] = [];
const edgeChanges: EdgeChange<AnyEdge>[] = [];
nodes.forEach(({ id, selected }) => {
if (!selected) {
nodeChanges.push({ type: 'select', id, selected: true });
@@ -294,8 +296,8 @@ export const Flow = memo(() => {
const deleteSelection = useCallback(() => {
const { nodes, edges } = selectNodesSlice(store.getState());
const nodeChanges: NodeChange[] = [];
const edgeChanges: EdgeChange[] = [];
const nodeChanges: NodeChange<AnyNode>[] = [];
const edgeChanges: EdgeChange<AnyEdge>[] = [];
nodes
.filter((n) => n.selected)
.forEach(({ id }) => {
@@ -322,7 +324,7 @@ export const Flow = memo(() => {
});
return (
<ReactFlow
<ReactFlow<AnyNode, AnyEdge>
id="workflow-editor"
ref={flowWrapper}
defaultViewport={viewport}
@@ -334,9 +336,9 @@ export const Flow = memo(() => {
onMouseMove={onMouseMove}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onReconnect={onReconnect}
onReconnectStart={onReconnectStart}
onReconnectEnd={onReconnectEnd}
onConnectStart={onConnectStart}
onConnect={onConnect}
onConnectEnd={onConnectEnd}

View File

@@ -1,4 +1,6 @@
import { useStore } from '@nanostores/react';
import type { ConnectionLineComponentProps } from '@xyflow/react';
import { getBezierPath } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
@@ -6,8 +8,6 @@ import { $pendingConnection } from 'features/nodes/store/nodesSlice';
import { selectShouldAnimateEdges, selectShouldColorEdges } from 'features/nodes/store/workflowSettingsSlice';
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import type { ConnectionLineComponentProps } from 'reactflow';
import { getBezierPath } from 'reactflow';
const pathStyles: CSSProperties = { opacity: 0.8 };

View File

@@ -1,13 +1,14 @@
import { Badge, Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type { EdgeProps } from '@xyflow/react';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { makeEdgeSelector } from 'features/nodes/components/flow/edges/util/makeEdgeSelector';
import { $templates } from 'features/nodes/store/nodesSlice';
import type { CollapsedInvocationNodeEdge } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import type { EdgeProps } from 'reactflow';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
const InvocationCollapsedEdge = ({
sourceX,
@@ -23,7 +24,7 @@ const InvocationCollapsedEdge = ({
sourceHandleId,
target,
targetHandleId,
}: EdgeProps<{ count: number }>) => {
}: EdgeProps<CollapsedInvocationNodeEdge>) => {
const templates = useStore($templates);
const selector = useMemo(
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId),

View File

@@ -1,12 +1,13 @@
import { Flex, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type { EdgeProps } from '@xyflow/react';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectShouldShowEdgeLabels } from 'features/nodes/store/workflowSettingsSlice';
import type { DefaultInvocationNodeEdge } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import type { EdgeProps } from 'reactflow';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
import { makeEdgeSelector } from './util/makeEdgeSelector';
@@ -23,7 +24,7 @@ const InvocationDefaultEdge = ({
target,
sourceHandleId,
targetHandleId,
}: EdgeProps) => {
}: EdgeProps<DefaultInvocationNodeEdge>) => {
const templates = useStore($templates);
const selector = useMemo(
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId),

View File

@@ -1,5 +1,6 @@
import { Flex, Image, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type { NodeProps } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { DndImage } from 'features/dnd/DndImage';
@@ -12,7 +13,6 @@ import { motion } from 'framer-motion';
import type { CSSProperties, PropsWithChildren } from 'react';
import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { NodeProps } from 'reactflow';
import { $lastProgressEvent } from 'services/events/stores';
const CurrentImageNode = (props: NodeProps) => {

View File

@@ -1,9 +1,9 @@
import { Handle, Position } from '@xyflow/react';
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { map } from 'lodash-es';
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import { Handle, Position } from 'reactflow';
interface Props {
nodeId: string;

View File

@@ -1,16 +1,16 @@
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import type { Node, NodeProps } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectNodes } from 'features/nodes/store/selectors';
import type { InvocationNodeData } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import type { NodeProps } from 'reactflow';
import InvocationNodeUnknownFallback from './InvocationNodeUnknownFallback';
const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
const InvocationNodeWrapper = (props: NodeProps<Node<InvocationNodeData>>) => {
const { data, selected } = props;
const { id: nodeId, type, isOpen, label } = data;
const templates = useStore($templates);

View File

@@ -1,5 +1,7 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Box, Tooltip } from '@invoke-ai/ui-library';
import type { HandleType } from '@xyflow/react';
import { Handle, Position } from '@xyflow/react';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
import type { ValidationResult } from 'features/nodes/store/util/validateConnection';
@@ -8,8 +10,6 @@ import type { FieldInputTemplate, FieldOutputTemplate } from 'features/nodes/typ
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import type { HandleType } from 'reactflow';
import { Handle, Position } from 'reactflow';
type Props = {
handleType: HandleType;
@@ -60,12 +60,12 @@ const handleStyleBase = {
const targetHandleStyle = {
...handleStyleBase,
insetInlineStart: '-1rem',
insetInlineStart: '-0.5rem',
} satisfies CSSProperties;
const sourceHandleStyle = {
...handleStyleBase,
insetInlineEnd: '-1rem',
insetInlineEnd: '-0.5rem',
} satisfies CSSProperties;
export const FieldHandle = memo((props: Props) => {

View File

@@ -23,7 +23,7 @@ export const useIntegerField = (props: FieldComponentProps<IntegerFieldInputInst
min = fieldTemplate.minimum;
}
if (!isNil(fieldTemplate.exclusiveMinimum)) {
min = fieldTemplate.exclusiveMinimum + 1
min = fieldTemplate.exclusiveMinimum + 1;
}
return min;
}, [fieldTemplate.exclusiveMinimum, fieldTemplate.minimum]);
@@ -34,7 +34,7 @@ export const useIntegerField = (props: FieldComponentProps<IntegerFieldInputInst
max = fieldTemplate.maximum;
}
if (!isNil(fieldTemplate.exclusiveMaximum)) {
max = fieldTemplate.exclusiveMaximum - 1
max = fieldTemplate.exclusiveMaximum - 1;
}
return max;
}, [fieldTemplate.exclusiveMaximum, fieldTemplate.maximum]);

View File

@@ -1,4 +1,5 @@
import { Box, Flex, Textarea } from '@invoke-ai/ui-library';
import type { Node,NodeProps } from '@xyflow/react';
import { useAppDispatch } from 'app/store/storeHooks';
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
import NodeTitle from 'features/nodes/components/flow/nodes/common/NodeTitle';
@@ -7,9 +8,8 @@ import { notesNodeValueChanged } from 'features/nodes/store/nodesSlice';
import type { NotesNodeData } from 'features/nodes/types/invocation';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import type { NodeProps } from 'reactflow';
const NotesNode = (props: NodeProps<NotesNodeData>) => {
const NotesNode = (props: NodeProps<Node<NotesNodeData>>) => {
const { id: nodeId, data, selected } = props;
const { notes, isOpen } = data;
const dispatch = useAppDispatch();

View File

@@ -1,9 +1,9 @@
import { Icon, IconButton } from '@invoke-ai/ui-library';
import { useUpdateNodeInternals } from '@xyflow/react';
import { useAppDispatch } from 'app/store/storeHooks';
import { nodeIsOpenChanged } from 'features/nodes/store/nodesSlice';
import { memo, useCallback } from 'react';
import { PiCaretUpBold } from 'react-icons/pi';
import { useUpdateNodeInternals } from 'reactflow';
interface Props {
nodeId: string;

View File

@@ -1,17 +1,17 @@
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, useGlobalMenuClose, useToken } from '@invoke-ai/ui-library';
import type { ChakraProps, SystemStyleObject } from '@invoke-ai/ui-library';
import { Box, useGlobalMenuClose } from '@invoke-ai/ui-library';
import type { NodeChange } from '@xyflow/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import { useNodeExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { nodesChanged } from 'features/nodes/store/nodesSlice';
import { selectNodes } from 'features/nodes/store/selectors';
import { selectNodeOpacity } from 'features/nodes/store/workflowSettingsSlice';
import { DRAG_HANDLE_CLASSNAME, NODE_WIDTH } from 'features/nodes/types/constants';
import type { AnyNode } from 'features/nodes/types/invocation';
import { zNodeStatus } from 'features/nodes/types/invocation';
import type { MouseEvent, PropsWithChildren } from 'react';
import { memo, useCallback } from 'react';
import type { NodeChange } from 'reactflow';
type NodeWrapperProps = PropsWithChildren & {
nodeId: string;
@@ -19,6 +19,67 @@ type NodeWrapperProps = PropsWithChildren & {
width?: ChakraProps['w'];
};
const containerSx: SystemStyleObject = {
h: 'full',
position: 'relative',
borderRadius: 'base',
transitionProperty: 'common',
transitionDuration: '0.1s',
cursor: 'grab',
};
const shadowsSx: SystemStyleObject = {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'base',
pointerEvents: 'none',
zIndex: -1,
shadow: 'var(--invoke-shadows-xl), var(--invoke-shadows-base), var(--invoke-shadows-base)',
};
const inProgressSx: SystemStyleObject = {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'md',
pointerEvents: 'none',
transitionProperty: 'common',
transitionDuration: '0.1s',
opacity: 0.7,
zIndex: -1,
visibility: 'hidden',
shadow: '0 0 0 2px var(--invoke-colors-yellow-400), 0 0 20px 2px var(--invoke-colors-orange-700)',
'&[data-is-in-progress="true"]': {
visibility: 'visible',
},
};
const selectionOverlaySx: SystemStyleObject = {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'base',
transitionProperty: 'common',
transitionDuration: '0.1s',
pointerEvents: 'none',
visibility: 'hidden',
opacity: 0.5,
'&[data-is-selected="true"], &[data-is-hovered="true"]': { visibility: 'visible' },
'&[data-is-selected="true"]': { shadow: '0 0 0 3px var(--invoke-colors-blue-500)' },
'&[data-is-hovered="true"]': { shadow: '0 0 0 2px var(--invoke-colors-blue-500)' },
'&[data-is-selected="true"][data-is-hovered="true"]': {
opacity: 1,
shadow: '0 0 0 3px var(--invoke-colors-blue-500)',
},
};
const NodeWrapper = (props: NodeWrapperProps) => {
const { nodeId, width, children, selected } = props;
const store = useAppStore();
@@ -27,12 +88,6 @@ const NodeWrapper = (props: NodeWrapperProps) => {
const executionState = useNodeExecutionState(nodeId);
const isInProgress = executionState?.status === zNodeStatus.enum.IN_PROGRESS;
const [nodeInProgress, shadowsXl, shadowsBase] = useToken('shadows', [
'nodeInProgress',
'shadows.xl',
'shadows.base',
]);
const dispatch = useAppDispatch();
const opacity = useAppSelector(selectNodeOpacity);
@@ -42,7 +97,7 @@ const NodeWrapper = (props: NodeWrapperProps) => {
(e: MouseEvent<HTMLDivElement>) => {
if (!e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey) {
const nodes = selectNodes(store.getState());
const nodeChanges: NodeChange[] = [];
const nodeChanges: NodeChange<AnyNode>[] = [];
nodes.forEach(({ id, selected }) => {
if (selected !== (id === nodeId)) {
nodeChanges.push({ type: 'select', id, selected: id === nodeId });
@@ -63,42 +118,14 @@ const NodeWrapper = (props: NodeWrapperProps) => {
onMouseEnter={handleMouseOver}
onMouseLeave={handleMouseOut}
className={DRAG_HANDLE_CLASSNAME}
h="full"
position="relative"
borderRadius="base"
w={width ? width : NODE_WIDTH}
transitionProperty="common"
transitionDuration="0.1s"
cursor="grab"
sx={containerSx}
width={width || NODE_WIDTH}
opacity={opacity}
>
<Box
position="absolute"
top={0}
insetInlineEnd={0}
bottom={0}
insetInlineStart={0}
borderRadius="base"
pointerEvents="none"
shadow={`${shadowsXl}, ${shadowsBase}, ${shadowsBase}`}
zIndex={-1}
/>
<Box
position="absolute"
top={0}
insetInlineEnd={0}
bottom={0}
insetInlineStart={0}
borderRadius="md"
pointerEvents="none"
transitionProperty="common"
transitionDuration="0.1s"
opacity={0.7}
shadow={isInProgress ? nodeInProgress : undefined}
zIndex={-1}
/>
<Box sx={shadowsSx} />
<Box sx={inProgressSx} data-is-in-progress={isInProgress} />
{children}
<NodeSelectionOverlay isSelected={selected} isHovered={isMouseOverNode} />
<Box sx={selectionOverlaySx} data-is-selected={selected} data-is-hovered={isMouseOverNode} />
</Box>
);
};

View File

@@ -1,4 +1,5 @@
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
import { useReactFlow } from '@xyflow/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import {
selectShouldShowMinimapPanel,
@@ -12,7 +13,6 @@ import {
PiMagnifyingGlassPlusBold,
PiMapPinBold,
} from 'react-icons/pi';
import { useReactFlow } from 'reactflow';
const ViewportControls = () => {
const { t } = useTranslation();

View File

@@ -1,9 +1,9 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { chakra, Flex } from '@invoke-ai/ui-library';
import { MiniMap } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { selectShouldShowMinimapPanel } from 'features/nodes/store/workflowSettingsSlice';
import { memo } from 'react';
import { MiniMap } from 'reactflow';
const ChakraMiniMap = chakra(MiniMap);

View File

@@ -15,6 +15,7 @@ import {
ModalOverlay,
Switch,
} from '@invoke-ai/ui-library';
import { SelectionMode } from '@xyflow/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { buildUseBoolean } from 'common/hooks/useBoolean';
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopRightPanel/ReloadSchemaButton';
@@ -35,7 +36,6 @@ import {
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { SelectionMode } from 'reactflow';
const formLabelProps: FormLabelProps = { flexGrow: 1 };
export const [useWorkflowEditorSettingsModal] = buildUseBoolean(false);

View File

@@ -1,4 +1,4 @@
import 'reactflow/dist/style.css';
import '@xyflow/react/dist/base.css';
import { Box } from '@invoke-ai/ui-library';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';

View File

@@ -1,4 +1,4 @@
import 'reactflow/dist/style.css';
import '@xyflow/react/dist/base.css';
import { Box, Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';

View File

@@ -47,7 +47,7 @@ export const NodeFieldElementComponentEditMode = memo(({ el }: { el: NodeFieldEl
return (
<FormElementEditModeWrapper element={el}>
<Flex id={id} className={NODE_FIELD_CLASS_NAME} w='full'>
<Flex id={id} className={NODE_FIELD_CLASS_NAME} w="full">
<InputFieldGate nodeId={fieldIdentifier.nodeId} fieldName={fieldIdentifier.fieldName}>
<InputFieldViewMode nodeId={fieldIdentifier.nodeId} fieldName={fieldIdentifier.fieldName} />
</InputFieldGate>

View File

@@ -1,4 +1,5 @@
import { useStore } from '@nanostores/react';
import { useReactFlow } from '@xyflow/react';
import { $templates } from 'features/nodes/store/nodesSlice';
import { NODE_WIDTH } from 'features/nodes/types/constants';
import type { AnyNode, InvocationTemplate } from 'features/nodes/types/invocation';
@@ -6,7 +7,6 @@ import { buildCurrentImageNode } from 'features/nodes/util/node/buildCurrentImag
import { buildInvocationNode } from 'features/nodes/util/node/buildInvocationNode';
import { buildNotesNode } from 'features/nodes/util/node/buildNotesNode';
import { useCallback } from 'react';
import { useReactFlow } from 'reactflow';
export const useBuildNode = () => {
const templates = useStore($templates);

View File

@@ -1,4 +1,6 @@
import { useStore } from '@nanostores/react';
import type { EdgeChange, OnConnect, OnConnectEnd, OnConnectStart } from '@xyflow/react';
import { useUpdateNodeInternals } from '@xyflow/react';
import { useAppStore } from 'app/store/storeHooks';
import { $mouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import {
@@ -12,9 +14,8 @@ import {
import { selectNodes, selectNodesSlice } from 'features/nodes/store/selectors';
import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection';
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import type { AnyEdge } from 'features/nodes/types/invocation';
import { useCallback, useMemo } from 'react';
import type { EdgeChange, OnConnect, OnConnectEnd, OnConnectStart } from 'reactflow';
import { useUpdateNodeInternals } from 'reactflow';
import { assert } from 'tsafe';
export const useConnection = () => {
@@ -94,7 +95,7 @@ export const useConnection = () => {
);
if (connection) {
const newEdge = connectionToEdge(connection);
const edgeChanges: EdgeChange[] = [{ type: 'add', item: newEdge }];
const edgeChanges: EdgeChange<AnyEdge>[] = [{ type: 'add', item: newEdge }];
const nodesToUpdate = [newEdge.source, newEdge.target];
if (edgePendingUpdate) {

View File

@@ -1,18 +1,19 @@
import { useStore } from '@nanostores/react';
import type { IsValidConnection } from '@xyflow/react';
import { useAppSelector, useAppStore } from 'app/store/storeHooks';
import { $edgePendingUpdate, $templates } from 'features/nodes/store/nodesSlice';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { validateConnection } from 'features/nodes/store/util/validateConnection';
import { selectShouldShouldValidateGraph } from 'features/nodes/store/workflowSettingsSlice';
import type { AnyEdge } from 'features/nodes/types/invocation';
import { useCallback } from 'react';
import type { Connection } from 'reactflow';
export const useIsValidConnection = () => {
export const useIsValidConnection = (): IsValidConnection<AnyEdge> => {
const store = useAppStore();
const templates = useStore($templates);
const shouldValidateGraph = useAppSelector(selectShouldShouldValidateGraph);
const isValidConnection = useCallback(
({ source, sourceHandle, target, targetHandle }: Connection): boolean => {
const isValidConnection = useCallback<IsValidConnection<AnyEdge>>(
({ source, sourceHandle, target, targetHandle }) => {
// Connection must have valid targets
if (!(source && sourceHandle && target && targetHandle)) {
return false;

View File

@@ -1,8 +1,8 @@
import type { Node } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
import type { Node } from 'reactflow';
export const useNode = (nodeId: string): Node => {
const selector = useMemo(

View File

@@ -1,3 +1,4 @@
import type { EdgeChange, NodeChange } from '@xyflow/react';
import { logger } from 'app/logging/logger';
import { getStore } from 'app/store/nanostores/store';
import { deepClone } from 'common/util/deepClone';
@@ -13,9 +14,9 @@ import {
import { selectNodesSlice } from 'features/nodes/store/selectors';
import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition';
import { validateConnection } from 'features/nodes/store/util/validateConnection';
import type { AnyEdge, AnyNode } from 'features/nodes/types/invocation';
import { t } from 'i18next';
import { isEqual, isNil, uniqWith } from 'lodash-es';
import type { EdgeChange, NodeChange } from 'reactflow';
import { assert } from 'tsafe';
import { v4 as uuidv4 } from 'uuid';
@@ -80,8 +81,8 @@ const _pasteSelection = (withEdgesToCopiedNodes?: boolean) => {
edge.id = uuidv4();
});
const nodeChanges: NodeChange[] = [];
const edgeChanges: EdgeChange[] = [];
const nodeChanges: NodeChange<AnyNode>[] = [];
const edgeChanges: EdgeChange<AnyEdge>[] = [];
// Deselect existing nodes
nodes.forEach(({ id, selected }) => {
if (selected) {

View File

@@ -1,5 +1,7 @@
import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit';
import { createSlice, isAnyOf } from '@reduxjs/toolkit';
import type { EdgeChange, NodeChange, Viewport, XYPosition } from '@xyflow/react';
import { applyEdgeChanges, applyNodeChanges, getConnectedEdges, getIncomers, getOutgoers } from '@xyflow/react';
import type { PersistConfig } from 'app/store/store';
import { buildUseBoolean } from 'common/hooks/useBoolean';
import { workflowLoaded } from 'features/nodes/store/actions';
@@ -72,12 +74,10 @@ import {
zT5EncoderModelFieldValue,
zVAEModelFieldValue,
} from 'features/nodes/types/field';
import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AnyEdge, AnyNode } from 'features/nodes/types/invocation';
import { isInvocationNode, isNotesNode } from 'features/nodes/types/invocation';
import { atom, computed } from 'nanostores';
import type { MouseEvent } from 'react';
import type { Edge, EdgeChange, NodeChange, Viewport, XYPosition } from 'reactflow';
import { applyEdgeChanges, applyNodeChanges, getConnectedEdges, getIncomers, getOutgoers } from 'reactflow';
import type { UndoableOptions } from 'redux-undo';
import type { z } from 'zod';
@@ -125,10 +125,10 @@ export const nodesSlice = createSlice({
name: 'nodes',
initialState: initialNodesState,
reducers: {
nodesChanged: (state, action: PayloadAction<NodeChange[]>) => {
state.nodes = applyNodeChanges(action.payload, state.nodes);
nodesChanged: (state, action: PayloadAction<NodeChange<AnyNode>[]>) => {
state.nodes = applyNodeChanges<AnyNode>(action.payload, state.nodes);
// Remove edges that are no longer valid, due to a removed or otherwise changed node
const edgeChanges: EdgeChange[] = [];
const edgeChanges: EdgeChange<AnyEdge>[] = [];
state.edges.forEach((e) => {
const sourceExists = state.nodes.some((n) => n.id === e.source);
const targetExists = state.nodes.some((n) => n.id === e.target);
@@ -136,10 +136,10 @@ export const nodesSlice = createSlice({
edgeChanges.push({ type: 'remove', id: e.id });
}
});
state.edges = applyEdgeChanges(edgeChanges, state.edges);
state.edges = applyEdgeChanges<AnyEdge>(edgeChanges, state.edges);
},
edgesChanged: (state, action: PayloadAction<EdgeChange[]>) => {
const changes: EdgeChange[] = [];
edgesChanged: (state, action: PayloadAction<EdgeChange<AnyEdge>[]>) => {
const changes: EdgeChange<AnyEdge>[] = [];
// We may need to massage the edge changes or otherwise handle them
action.payload.forEach((change) => {
if (change.type === 'remove' || change.type === 'select') {
@@ -251,7 +251,7 @@ export const nodesSlice = createSlice({
(node) => isInvocationNode(node) && node.data.isOpen === false
);
const collapsedEdgesToCreate: Edge<{ count: number }>[] = [];
const collapsedEdgesToCreate: AnyEdge[] = [];
// hide all edges
connectedEdges.forEach((edge) => {
@@ -271,7 +271,7 @@ export const nodesSlice = createSlice({
target: edge.target,
type: 'collapsed',
data: { count: 1 },
updatable: false,
reconnectable: false,
selected: edge.selected,
});
}
@@ -292,14 +292,14 @@ export const nodesSlice = createSlice({
target: edge.target,
type: 'collapsed',
data: { count: 1 },
updatable: false,
reconnectable: false,
selected: edge.selected,
});
}
}
});
if (collapsedEdgesToCreate.length) {
state.edges = applyEdgeChanges(
state.edges = applyEdgeChanges<AnyEdge>(
collapsedEdgesToCreate.map((edge) => ({ type: 'add', item: edge })),
state.edges
);
@@ -449,13 +449,28 @@ export const nodesSlice = createSlice({
extraReducers: (builder) => {
builder.addCase(workflowLoaded, (state, action) => {
const { nodes, edges } = action.payload;
state.nodes = applyNodeChanges(
nodes.map((node) => ({
type: 'add',
item: { ...node, ...SHARED_NODE_PROPERTIES },
})),
[]
);
const changes: NodeChange<AnyNode>[] = [];
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<AnyNode>(changes, []);
state.edges = applyEdgeChanges(
edges.map((edge) => ({ type: 'add', item: edge })),
[]
@@ -516,10 +531,10 @@ export const $cursorPos = atom<XYPosition | null>(null);
export const $templates = atom<Templates>({});
export const $hasTemplates = computed($templates, (templates) => Object.keys(templates).length > 0);
export const $copiedNodes = atom<AnyNode[]>([]);
export const $copiedEdges = atom<InvocationNodeEdge[]>([]);
export const $edgesToCopiedNodes = atom<InvocationNodeEdge[]>([]);
export const $copiedEdges = atom<AnyEdge[]>([]);
export const $edgesToCopiedNodes = atom<AnyEdge[]>([]);
export const $pendingConnection = atom<PendingConnection | null>(null);
export const $edgePendingUpdate = atom<Edge | null>(null);
export const $edgePendingUpdate = atom<AnyEdge | null>(null);
export const $didUpdateEdge = atom(false);
export const $lastEdgeUpdateMouseEvent = atom<MouseEvent | null>(null);

View File

@@ -1,5 +1,6 @@
import type { ReactFlowInstance } from '@xyflow/react';
import type { AnyEdge, AnyNode } from 'features/nodes/types/invocation';
import { atom } from 'nanostores';
import type { ReactFlowInstance } from 'reactflow';
export const $flow = atom<ReactFlowInstance | null>(null);
export const $flow = atom<ReactFlowInstance<AnyNode, AnyEdge> | null>(null);
export const $needsFit = atom<boolean>(true);

View File

@@ -3,12 +3,11 @@ import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import type { NodesState } from 'features/nodes/store/types';
import type { FieldInputInstance } from 'features/nodes/types/field';
import type { InvocationNode, InvocationNodeData } from 'features/nodes/types/invocation';
import type { AnyNode, InvocationNode, InvocationNodeData } from 'features/nodes/types/invocation';
import { isInvocationNode } from 'features/nodes/types/invocation';
import type { Node } from 'reactflow';
import { assert } from 'tsafe';
export const selectNode = (nodesSlice: NodesState, nodeId: string): Node => {
export const selectNode = (nodesSlice: NodesState, nodeId: string): AnyNode => {
const node = nodesSlice.nodes.find((node) => node.id === nodeId);
assert(node !== undefined, `Node ${nodeId} not found`);
return node;

View File

@@ -1,3 +1,4 @@
import type { HandleType } from '@xyflow/react';
import type {
FieldIdentifier,
FieldInputTemplate,
@@ -5,13 +6,12 @@ import type {
StatefulFieldValue,
} from 'features/nodes/types/field';
import type {
AnyEdge,
AnyNode,
InvocationNodeEdge,
InvocationTemplate,
NodeExecutionState,
} from 'features/nodes/types/invocation';
import type { WorkflowV3 } from 'features/nodes/types/workflow';
import type { HandleType } from 'reactflow';
import type { SQLiteDirection, WorkflowRecordOrderBy } from 'services/api/types';
export type Templates = Record<string, InvocationTemplate>;
@@ -27,7 +27,7 @@ export type PendingConnection = {
export type NodesState = {
_version: 1;
nodes: AnyNode[];
edges: InvocationNodeEdge[];
edges: AnyEdge[];
};
export type WorkflowMode = 'edit' | 'view';

View File

@@ -1,4 +1,4 @@
import type { Node } from 'reactflow';
import type { Node } from '@xyflow/react';
export const findUnoccupiedPosition = (nodes: Node[], x: number, y: number) => {
let newX = x;

View File

@@ -1,6 +1,6 @@
import type { Templates } from 'features/nodes/store/types';
import type { FieldType } from 'features/nodes/types/field';
import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AnyNode, AnyEdge } from 'features/nodes/types/invocation';
/**
* Given a collect node, return the type of the items it collects. The graph is traversed to find the first node and
@@ -15,7 +15,7 @@ import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocatio
export const getCollectItemType = (
templates: Templates,
nodes: AnyNode[],
edges: InvocationNodeEdge[],
edges: AnyEdge[],
nodeId: string
): FieldType | null => {
const firstEdgeToCollect = edges.find((edge) => edge.target === nodeId && edge.targetHandle === 'item');

View File

@@ -1,9 +1,9 @@
import type { Connection } from '@xyflow/react';
import type { Templates } from 'features/nodes/store/types';
import { validateConnection } from 'features/nodes/store/util/validateConnection';
import type { FieldInputTemplate, FieldOutputTemplate } from 'features/nodes/types/field';
import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AnyEdge,AnyNode } from 'features/nodes/types/invocation';
import { map } from 'lodash-es';
import type { Connection, Edge } from 'reactflow';
/**
*
@@ -23,9 +23,9 @@ export const getFirstValidConnection = (
target: string,
targetHandle: string | null,
nodes: AnyNode[],
edges: InvocationNodeEdge[],
edges: AnyEdge[],
templates: Templates,
edgePendingUpdate: Edge | null
edgePendingUpdate: AnyEdge | null
): Connection | null => {
if (source === target) {
return null;
@@ -81,9 +81,9 @@ export const getTargetCandidateFields = (
sourceHandle: string,
target: string,
nodes: AnyNode[],
edges: Edge[],
edges: AnyEdge[],
templates: Templates,
edgePendingUpdate: Edge | null
edgePendingUpdate: AnyEdge | null
): FieldInputTemplate[] => {
const sourceNode = nodes.find((n) => n.id === source);
const targetNode = nodes.find((n) => n.id === target);
@@ -117,9 +117,9 @@ export const getSourceCandidateFields = (
targetHandle: string,
source: string,
nodes: AnyNode[],
edges: Edge[],
edges: AnyEdge[],
templates: Templates,
edgePendingUpdate: Edge | null
edgePendingUpdate: AnyEdge | null
): FieldOutputTemplate[] => {
const targetNode = nodes.find((n) => n.id === target);
const sourceNode = nodes.find((n) => n.id === source);

View File

@@ -1,5 +1,5 @@
import graphlib from '@dagrejs/graphlib';
import type { Edge, Node } from 'reactflow';
import type { Edge, Node } from '@xyflow/react';
/**
* Check if adding an edge between the source and target nodes would create a cycle in the graph.

View File

@@ -1,9 +1,10 @@
import type { HandleType } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import type { RootState } from 'app/store/store';
import { selectNodesSlice } from 'features/nodes/store/selectors';
import type { NodesState, PendingConnection, Templates } from 'features/nodes/store/types';
import { buildRejectResult, validateConnection } from 'features/nodes/store/util/validateConnection';
import type { Edge, HandleType } from 'reactflow';
import type { AnyEdge } from 'features/nodes/types/invocation';
/**
* Creates a selector that validates a pending connection.
@@ -26,9 +27,9 @@ export const makeConnectionErrorSelector = (
return createMemoizedSelector(
selectNodesSlice,
(state: RootState, pendingConnection: PendingConnection | null) => pendingConnection,
(state: RootState, pendingConnection: PendingConnection | null, edgePendingUpdate: Edge | null) =>
(state: RootState, pendingConnection: PendingConnection | null, edgePendingUpdate: AnyEdge | null) =>
edgePendingUpdate,
(nodesSlice: NodesState, pendingConnection: PendingConnection | null, edgePendingUpdate: Edge | null) => {
(nodesSlice: NodesState, pendingConnection: PendingConnection | null, edgePendingUpdate: AnyEdge | null) => {
const { nodes, edges } = nodesSlice;
if (!pendingConnection) {

View File

@@ -1,4 +1,5 @@
import type { Connection, Edge } from 'reactflow';
import type { Connection } from '@xyflow/react';
import type { AnyEdge } from 'features/nodes/types/invocation';
import { assert } from 'tsafe';
/**
@@ -19,7 +20,7 @@ const getEdgeId = (connection: Connection): string => {
* @returns The edge
* @throws If the connection is invalid (e.g. missing source, sourcehandle, target, or targetHandle)
*/
export const connectionToEdge = (connection: Connection): Edge => {
export const connectionToEdge = (connection: Connection): AnyEdge => {
const { source, sourceHandle, target, targetHandle } = connection;
assert(source && sourceHandle && target && targetHandle, 'Invalid connection');
return {

View File

@@ -1,10 +1,9 @@
import type { Templates } from 'features/nodes/store/types';
import type { InvocationTemplate } from 'features/nodes/types/invocation';
import type { AnyEdge, InvocationTemplate } from 'features/nodes/types/invocation';
import { buildInvocationNode } from 'features/nodes/util/node/buildInvocationNode';
import type { OpenAPIV3_1 } from 'openapi-types';
import type { Edge } from 'reactflow';
export const buildEdge = (source: string, sourceHandle: string, target: string, targetHandle: string): Edge => ({
export const buildEdge = (source: string, sourceHandle: string, target: string, targetHandle: string): AnyEdge => ({
source,
sourceHandle,
target,

View File

@@ -1,10 +1,10 @@
import type { Connection as NullableConnection } from '@xyflow/react';
import type { Templates } from 'features/nodes/store/types';
import { areTypesEqual } from 'features/nodes/store/util/areTypesEqual';
import { getCollectItemType } from 'features/nodes/store/util/getCollectItemType';
import { getHasCycles } from 'features/nodes/store/util/getHasCycles';
import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes';
import type { AnyNode } from 'features/nodes/types/invocation';
import type { Connection as NullableConnection, Edge } from 'reactflow';
import type { AnyEdge, AnyNode } from 'features/nodes/types/invocation';
import type { SetNonNullable } from 'type-fest';
type Connection = SetNonNullable<NullableConnection>;
@@ -22,15 +22,15 @@ export type ValidationResult =
type ValidateConnectionFunc = (
connection: Connection,
nodes: AnyNode[],
edges: Edge[],
edges: AnyEdge[],
templates: Templates,
ignoreEdge: Edge | null,
ignoreEdge: AnyEdge | null,
strict?: boolean
) => ValidationResult;
const getEqualityPredicate =
(c: Connection) =>
(e: Edge): boolean => {
(e: AnyEdge): boolean => {
return (
e.target === c.target &&
e.targetHandle === c.targetHandle &&
@@ -41,7 +41,7 @@ const getEqualityPredicate =
const getTargetEqualityPredicate =
(c: Connection) =>
(e: Edge): boolean => {
(e: AnyEdge): boolean => {
return e.target === c.target && e.targetHandle === c.targetHandle;
};

View File

@@ -1,8 +1,8 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSelector, createSlice } from '@reduxjs/toolkit';
import { SelectionMode } from '@xyflow/react';
import type { PersistConfig, RootState } from 'app/store/store';
import type { Selector } from 'react-redux';
import { SelectionMode } from 'reactflow';
export type WorkflowSettingsState = {
_version: 1;

View File

@@ -1,4 +1,4 @@
import type { Node } from 'reactflow';
import type { AnyNode } from 'features/nodes/types/invocation';
/**
* How long to wait before showing a tooltip when hovering a field handle.
@@ -19,7 +19,7 @@ export const DRAG_HANDLE_CLASSNAME = 'node-drag-handle';
/**
* reactflow-specifc properties shared between all node types.
*/
export const SHARED_NODE_PROPERTIES: Partial<Node> = {
export const SHARED_NODE_PROPERTIES: Partial<AnyNode> = {
dragHandle: `.${DRAG_HANDLE_CLASSNAME}`,
};

View File

@@ -1,4 +1,4 @@
import type { Edge, Node } from 'reactflow';
import type { Edge, Node } from '@xyflow/react';
import { z } from 'zod';
import { zClassification, zProgressImage } from './common';
@@ -49,17 +49,15 @@ const zCurrentImageNodeData = z.object({
label: z.string(),
isOpen: z.boolean(),
});
const zAnyNodeData = z.union([zInvocationNodeData, zNotesNodeData, zCurrentImageNodeData]);
export type NotesNodeData = z.infer<typeof zNotesNodeData>;
export type InvocationNodeData = z.infer<typeof zInvocationNodeData>;
type CurrentImageNodeData = z.infer<typeof zCurrentImageNodeData>;
type AnyNodeData = z.infer<typeof zAnyNodeData>;
export type InvocationNode = Node<InvocationNodeData, 'invocation'>;
export type NotesNode = Node<NotesNodeData, 'notes'>;
export type CurrentImageNode = Node<CurrentImageNodeData, 'current_image'>;
export type AnyNode = Node<AnyNodeData>;
export type AnyNode = InvocationNode | NotesNode | CurrentImageNode;
export const isInvocationNode = (node?: AnyNode | null): node is InvocationNode =>
Boolean(node && node.type === 'invocation');
@@ -85,11 +83,13 @@ export type NodeExecutionState = z.infer<typeof zNodeExecutionState>;
// #endregion
// #region Edges
const zInvocationNodeEdgeExtra = z.object({
type: z.union([z.literal('default'), z.literal('collapsed')]),
const zInvocationNodeEdgeCollapsedData = z.object({
count: z.number().int().min(1),
});
type InvocationNodeEdgeExtra = z.infer<typeof zInvocationNodeEdgeExtra>;
export type InvocationNodeEdge = Edge<InvocationNodeEdgeExtra>;
type InvocationNodeEdgeCollapsedData = z.infer<typeof zInvocationNodeEdgeCollapsedData>;
export type DefaultInvocationNodeEdge = Edge<Record<string, never>, 'default'>;
export type CollapsedInvocationNodeEdge = Edge<InvocationNodeEdgeCollapsedData, 'collapsed'>;
export type AnyEdge = DefaultInvocationNodeEdge | CollapsedInvocationNodeEdge;
// #endregion
export const isBatchNode = (node: InvocationNode) => {

View File

@@ -1,5 +1,5 @@
import type { XYPosition as ReactFlowXYPosition } from '@xyflow/react';
import type { WorkflowCategory, WorkflowV3, XYPosition } from 'features/nodes/types/workflow';
import type * as ReactFlow from 'reactflow';
import type { S } from 'services/api/types';
import type { Equals, Extends } from 'tsafe';
import { assert } from 'tsafe';
@@ -11,7 +11,7 @@ import { describe, test } from 'vitest';
*/
describe('Workflow types', () => {
test('XYPosition', () => assert<Equals<XYPosition, ReactFlow.XYPosition>>());
test('XYPosition', () => assert<Equals<XYPosition, ReactFlowXYPosition>>());
test('WorkflowCategory', () => assert<Equals<WorkflowCategory, S['WorkflowCategory']>>());
// @ts-expect-error TODO(psyche): Need to revise server types!
test('WorkflowV3', () => assert<Extends<WorkflowV3, S['Workflow']>>());

View File

@@ -1,6 +1,6 @@
import type { XYPosition } from '@xyflow/react';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { CurrentImageNode } from 'features/nodes/types/invocation';
import type { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildCurrentImageNode = (position: XYPosition): CurrentImageNode => {

View File

@@ -1,9 +1,9 @@
import type { XYPosition } from '@xyflow/react';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { FieldInputInstance } from 'features/nodes/types/field';
import type { InvocationNode, InvocationTemplate } from 'features/nodes/types/invocation';
import { buildFieldInputInstance } from 'features/nodes/util/schema/buildFieldInputInstance';
import { reduce } from 'lodash-es';
import type { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildInvocationNode = (position: XYPosition, template: InvocationTemplate): InvocationNode => {

View File

@@ -1,6 +1,6 @@
import type { XYPosition } from '@xyflow/react';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { NotesNode } from 'features/nodes/types/invocation';
import type { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildNotesNode = (position: XYPosition): NotesNode => {

View File

@@ -1,4 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { getConnectedEdges } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import type { AppConfig } from 'app/types/invokeai';
import type { ParamsState } from 'features/controlLayers/store/paramsSlice';
@@ -40,14 +41,13 @@ import {
validateNumberFieldCollectionValue,
validateStringFieldCollectionValue,
} from 'features/nodes/types/fieldValidators';
import type { InvocationNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { InvocationNode, AnyEdge } from 'features/nodes/types/invocation';
import { isBatchNode, isExecutableNode, isInvocationNode } from 'features/nodes/types/invocation';
import type { UpscaleState } from 'features/parameters/store/upscaleSlice';
import { selectUpscaleSlice } from 'features/parameters/store/upscaleSlice';
import { selectConfigSlice } from 'features/system/store/configSlice';
import i18n from 'i18next';
import { forEach, groupBy, upperFirst } from 'lodash-es';
import { getConnectedEdges } from 'reactflow';
import { assert } from 'tsafe';
/**
@@ -70,7 +70,7 @@ export type Reason = { prefix?: string; content: string };
const disconnectedReason = (t: typeof i18n.t) => ({ content: t('parameters.invoke.systemDisconnected') });
export const resolveBatchValue = (batchNode: InvocationNode, nodes: InvocationNode[], edges: InvocationNodeEdge[]) => {
export const resolveBatchValue = (batchNode: InvocationNode, nodes: InvocationNode[], edges: AnyEdge[]) => {
if (batchNode.data.type === 'image_batch') {
assert(isImageFieldCollectionInputInstance(batchNode.data.inputs.images));
const ownValue = batchNode.data.inputs.images.value ?? [];

View File

@@ -1,10 +1,10 @@
import { ReactFlowProvider } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import NodeEditor from 'features/nodes/components/NodeEditor';
import { ViewContextProvider } from 'features/nodes/contexts/ViewContext';
import { selectWorkflowMode } from 'features/nodes/store/workflowSlice';
import { memo } from 'react';
import { ReactFlowProvider } from 'reactflow';
export const WorkflowsMainPanel = memo(() => {
const mode = useAppSelector(selectWorkflowMode);