mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
chore(ui): upgrade reactflow to v12
This commit is contained in:
@@ -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",
|
||||
|
||||
307
invokeai/frontend/web/pnpm-lock.yaml
generated
307
invokeai/frontend/web/pnpm-lock.yaml
generated
@@ -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'}
|
||||
|
||||
@@ -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);
|
||||
@@ -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';
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`,
|
||||
};
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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']>>());
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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 ?? [];
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user