2
gradio
2
gradio
Submodule gradio updated: 759fb3b1f2...bebfb72b35
Submodule litegraph updated: 39b040a0b1...b7ebbf57d3
@@ -47,6 +47,7 @@
|
||||
"@litegraph-ts/core": "workspace:*",
|
||||
"@litegraph-ts/nodes-basic": "workspace:*",
|
||||
"@litegraph-ts/nodes-events": "workspace:*",
|
||||
"@litegraph-ts/nodes-logic": "workspace:*",
|
||||
"@litegraph-ts/nodes-math": "workspace:*",
|
||||
"@litegraph-ts/nodes-strings": "workspace:*",
|
||||
"@litegraph-ts/tsconfig": "workspace:*",
|
||||
|
||||
359
pnpm-lock.yaml
generated
359
pnpm-lock.yaml
generated
@@ -46,6 +46,9 @@ importers:
|
||||
'@litegraph-ts/nodes-events':
|
||||
specifier: workspace:*
|
||||
version: link:litegraph/packages/nodes-events
|
||||
'@litegraph-ts/nodes-logic':
|
||||
specifier: workspace:*
|
||||
version: link:litegraph/packages/nodes-logic
|
||||
'@litegraph-ts/nodes-math':
|
||||
specifier: workspace:*
|
||||
version: link:litegraph/packages/nodes-math
|
||||
@@ -141,7 +144,18 @@ importers:
|
||||
specifier: ^0.25.8
|
||||
version: 0.25.8(sass@1.61.0)
|
||||
|
||||
gradio/client/js: {}
|
||||
gradio/client/js:
|
||||
dependencies:
|
||||
ws:
|
||||
specifier: ^8.13.0
|
||||
version: 8.13.0
|
||||
devDependencies:
|
||||
'@types/ws':
|
||||
specifier: ^8.5.4
|
||||
version: 8.5.4
|
||||
esbuild:
|
||||
specifier: ^0.17.14
|
||||
version: 0.17.18
|
||||
|
||||
gradio/js/_cdn-test:
|
||||
devDependencies:
|
||||
@@ -246,12 +260,6 @@ importers:
|
||||
postcss-prefix-selector:
|
||||
specifier: ^1.16.0
|
||||
version: 1.16.0(postcss@8.4.21)
|
||||
svelte:
|
||||
specifier: ^3.25.1
|
||||
version: 3.58.0
|
||||
svelte-i18n:
|
||||
specifier: ^3.3.13
|
||||
version: 3.3.13(svelte@3.58.0)
|
||||
|
||||
gradio/js/atoms:
|
||||
dependencies:
|
||||
@@ -311,7 +319,7 @@ importers:
|
||||
version: 4.0.2
|
||||
d3-shape:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
version: 3.2.0
|
||||
devDependencies:
|
||||
'@types/d3-dsv':
|
||||
specifier: ^3.0.0
|
||||
@@ -339,46 +347,46 @@ importers:
|
||||
dependencies:
|
||||
'@codemirror/autocomplete':
|
||||
specifier: ^6.3.0
|
||||
version: 6.3.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/common@1.0.2)
|
||||
version: 6.6.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/commands':
|
||||
specifier: ^6.1.2
|
||||
version: 6.1.2
|
||||
version: 6.2.4
|
||||
'@codemirror/lang-css':
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0(@codemirror/view@6.4.1)(@lezer/common@1.0.2)
|
||||
version: 6.2.0(@codemirror/view@6.11.0)
|
||||
'@codemirror/lang-html':
|
||||
specifier: ^6.4.2
|
||||
version: 6.4.2
|
||||
version: 6.4.3
|
||||
'@codemirror/lang-javascript':
|
||||
specifier: ^6.1.4
|
||||
version: 6.1.4
|
||||
version: 6.1.7
|
||||
'@codemirror/lang-json':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1
|
||||
'@codemirror/lang-markdown':
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0
|
||||
version: 6.1.1
|
||||
'@codemirror/lang-python':
|
||||
specifier: ^6.0.4
|
||||
version: 6.0.4
|
||||
version: 6.1.2(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/language':
|
||||
specifier: ^6.6.0
|
||||
version: 6.6.0
|
||||
'@codemirror/legacy-modes':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1
|
||||
version: 6.3.2
|
||||
'@codemirror/lint':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
version: 6.2.1
|
||||
'@codemirror/search':
|
||||
specifier: ^6.2.2
|
||||
version: 6.2.2
|
||||
version: 6.4.0
|
||||
'@codemirror/state':
|
||||
specifier: ^6.1.2
|
||||
version: 6.1.2
|
||||
version: 6.2.0
|
||||
'@codemirror/view':
|
||||
specifier: ^6.4.1
|
||||
version: 6.4.1
|
||||
version: 6.11.0
|
||||
'@gradio/atoms':
|
||||
specifier: workspace:^0.0.1
|
||||
version: link:../atoms
|
||||
@@ -393,16 +401,16 @@ importers:
|
||||
version: 1.0.2
|
||||
'@lezer/highlight':
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.3
|
||||
version: 1.1.4
|
||||
'@lezer/markdown':
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
cm6-theme-basic-dark:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/highlight@1.1.3)
|
||||
version: 0.2.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/highlight@1.1.4)
|
||||
cm6-theme-basic-light:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/highlight@1.1.3)
|
||||
version: 0.2.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/highlight@1.1.4)
|
||||
codemirror:
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(@lezer/common@1.0.2)
|
||||
@@ -721,19 +729,19 @@ importers:
|
||||
version: 8.4.21
|
||||
postcss-load-config:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1
|
||||
version: 3.1.4(postcss@8.4.21)
|
||||
svelte-check:
|
||||
specifier: ^2.2.6
|
||||
version: 2.2.6(postcss-load-config@3.1.1)(postcss@8.4.21)(svelte@3.58.0)
|
||||
version: 2.2.6(postcss-load-config@3.1.4)(postcss@8.4.21)(svelte@3.58.0)
|
||||
svelte-preprocess:
|
||||
specifier: ^4.10.1
|
||||
version: 4.10.1(postcss-load-config@3.1.1)(postcss@8.4.21)(svelte@3.58.0)(typescript@4.5.4)
|
||||
version: 4.10.1(postcss-load-config@3.1.4)(postcss@8.4.21)(svelte@3.58.0)(typescript@4.5.4)
|
||||
tailwindcss:
|
||||
specifier: ^3.0.12
|
||||
version: 3.3.1
|
||||
tslib:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1
|
||||
version: 2.5.0
|
||||
typescript:
|
||||
specifier: ~4.5.4
|
||||
version: 4.5.4
|
||||
@@ -801,6 +809,22 @@ importers:
|
||||
specifier: ^4.2.1
|
||||
version: 4.3.1
|
||||
|
||||
litegraph/packages/nodes-logic:
|
||||
dependencies:
|
||||
'@litegraph-ts/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
devDependencies:
|
||||
'@litegraph-ts/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../tsconfig
|
||||
typescript:
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3
|
||||
vite:
|
||||
specifier: ^4.2.1
|
||||
version: 4.3.1
|
||||
|
||||
litegraph/packages/nodes-math:
|
||||
dependencies:
|
||||
'@litegraph-ts/core':
|
||||
@@ -1181,8 +1205,8 @@ packages:
|
||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||
dev: true
|
||||
|
||||
/@codemirror/autocomplete@6.3.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/common@1.0.2):
|
||||
resolution: {integrity: sha512-4jEvh3AjJZTDKazd10J6ZsCIqaYxDMCeua5ouQxY8hlFIml+nr7le0SgBhT3SIytFBmdzPK3AUhXGuW3T79nVg==}
|
||||
/@codemirror/autocomplete@6.6.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/common@1.0.2):
|
||||
resolution: {integrity: sha512-RpsvnYOopnyNbZg487qoRD5bKg63KMMUVP5d8MQ4Luc7Mb6JBWTORovLi6cTvWaKlbmLW8Zd2dAJkIdrhBsXug==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
@@ -1190,54 +1214,54 @@ packages:
|
||||
'@lezer/common': ^1.0.0
|
||||
dependencies:
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
'@lezer/common': 1.0.2
|
||||
dev: false
|
||||
|
||||
/@codemirror/commands@6.1.2:
|
||||
resolution: {integrity: sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==}
|
||||
/@codemirror/commands@6.2.4:
|
||||
resolution: {integrity: sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==}
|
||||
dependencies:
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
'@lezer/common': 1.0.2
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-css@6.1.0(@codemirror/view@6.4.1)(@lezer/common@1.0.2):
|
||||
resolution: {integrity: sha512-GYn4TyMvQLrkrhdisFh8HCTDAjPY/9pzwN12hG9UdrTUxRUMicF+8GS24sFEYaleaG1KZClIFLCj0Rol/WO24w==}
|
||||
/@codemirror/lang-css@6.2.0(@codemirror/view@6.11.0):
|
||||
resolution: {integrity: sha512-oyIdJM29AyRPM3+PPq1I2oIk8NpUfEN3kAM05XWDDs6o3gSneIKaVJifT2P+fqONLou2uIgXynFyMUDQvo/szA==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.3.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/common@1.0.2)
|
||||
'@codemirror/autocomplete': 6.6.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/state': 6.2.0
|
||||
'@lezer/common': 1.0.2
|
||||
'@lezer/css': 1.1.1
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/view'
|
||||
- '@lezer/common'
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-html@6.4.2:
|
||||
resolution: {integrity: sha512-bqCBASkteKySwtIbiV/WCtGnn/khLRbbiV5TE+d9S9eQJD7BA4c5dTRm2b3bVmSpilff5EYxvB4PQaZzM/7cNw==}
|
||||
/@codemirror/lang-html@6.4.3:
|
||||
resolution: {integrity: sha512-VKzQXEC8nL69Jg2hvAFPBwOdZNvL8tMFOrdFwWpU+wc6a6KEkndJ/19R5xSaglNX6v2bttm8uIEFYxdQDcIZVQ==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.3.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/common@1.0.2)
|
||||
'@codemirror/lang-css': 6.1.0(@codemirror/view@6.4.1)(@lezer/common@1.0.2)
|
||||
'@codemirror/lang-javascript': 6.1.4
|
||||
'@codemirror/autocomplete': 6.6.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/lang-css': 6.2.0(@codemirror/view@6.11.0)
|
||||
'@codemirror/lang-javascript': 6.1.7
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
'@lezer/common': 1.0.2
|
||||
'@lezer/css': 1.1.1
|
||||
'@lezer/html': 1.3.4
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-javascript@6.1.4:
|
||||
resolution: {integrity: sha512-OxLf7OfOZBTMRMi6BO/F72MNGmgOd9B0vetOLvHsDACFXayBzW8fm8aWnDM0yuy68wTK03MBf4HbjSBNRG5q7A==}
|
||||
/@codemirror/lang-javascript@6.1.7:
|
||||
resolution: {integrity: sha512-KXKqxlZ4W6t5I7i2ScmITUD3f/F5Cllk3kj0De9P9mFeYVfhOVOWuDLgYiLpk357u7Xh4dhqjJAnsNPPoTLghQ==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.3.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/common@1.0.2)
|
||||
'@codemirror/autocomplete': 6.6.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/lint': 6.0.0
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@codemirror/lint': 6.2.1
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
'@lezer/common': 1.0.2
|
||||
'@lezer/javascript': 1.4.3
|
||||
dev: false
|
||||
@@ -1249,65 +1273,70 @@ packages:
|
||||
'@lezer/json': 1.0.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-markdown@6.1.0:
|
||||
resolution: {integrity: sha512-HQDJg1Js19fPKKsI3Rp1X0J6mxyrRy2NX6+Evh0+/jGm6IZHL5ygMGKBYNWKXodoDQFvgdofNRG33gWOwV59Ag==}
|
||||
/@codemirror/lang-markdown@6.1.1:
|
||||
resolution: {integrity: sha512-n87Ms6Y5UYb1UkFu8sRzTLfq/yyF1y2AYiWvaVdbBQi5WDj1tFk5N+AKA+WC0Jcjc1VxvrCCM0iizjdYYi9sFQ==}
|
||||
dependencies:
|
||||
'@codemirror/lang-html': 6.4.2
|
||||
'@codemirror/lang-html': 6.4.3
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
'@lezer/common': 1.0.2
|
||||
'@lezer/markdown': 1.0.2
|
||||
dev: false
|
||||
|
||||
/@codemirror/lang-python@6.0.4:
|
||||
resolution: {integrity: sha512-CuC7V6MVw4HshQuFaB1SMXHOSbKLnBnBXMzm9Zjb+uvkggyY8fXp79T9eYFzMn7fuadoPJcXyTcT/q/SRT7lvQ==}
|
||||
/@codemirror/lang-python@6.1.2(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/common@1.0.2):
|
||||
resolution: {integrity: sha512-nbQfifLBZstpt6Oo4XxA2LOzlSp4b/7Bc5cmodG1R+Cs5PLLCTUvsMNWDnziiCfTOG/SW1rVzXq/GbIr6WXlcw==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.6.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/language': 6.6.0
|
||||
'@lezer/python': 1.1.4
|
||||
'@lezer/python': 1.1.5
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/state'
|
||||
- '@codemirror/view'
|
||||
- '@lezer/common'
|
||||
dev: false
|
||||
|
||||
/@codemirror/language@6.6.0:
|
||||
resolution: {integrity: sha512-cwUd6lzt3MfNYOobdjf14ZkLbJcnv4WtndYaoBkbor/vF+rCNguMPK0IRtvZJG4dsWiaWPcK8x1VijhvSxnstg==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
'@lezer/common': 1.0.2
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@lezer/highlight': 1.1.4
|
||||
'@lezer/lr': 1.3.4
|
||||
style-mod: 4.0.3
|
||||
dev: false
|
||||
|
||||
/@codemirror/legacy-modes@6.3.1:
|
||||
resolution: {integrity: sha512-icXmCs4Mhst2F8mE0TNpmG6l7YTj1uxam3AbZaFaabINH5oWAdg2CfR/PVi+d/rqxJ+TuTnvkKK5GILHrNThtw==}
|
||||
/@codemirror/legacy-modes@6.3.2:
|
||||
resolution: {integrity: sha512-ki5sqNKWzKi5AKvpVE6Cna4Q+SgxYuYVLAZFSsMjGBWx5qSVa+D+xipix65GS3f2syTfAD9pXKMX4i4p49eneQ==}
|
||||
dependencies:
|
||||
'@codemirror/language': 6.6.0
|
||||
dev: false
|
||||
|
||||
/@codemirror/lint@6.0.0:
|
||||
resolution: {integrity: sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA==}
|
||||
/@codemirror/lint@6.2.1:
|
||||
resolution: {integrity: sha512-y1muai5U/uUPAGRyHMx9mHuHLypPcHWxzlZGknp/U5Mdb5Ol8Q5ZLp67UqyTbNFJJ3unVxZ8iX3g1fMN79S1JQ==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
crelt: 1.0.5
|
||||
dev: false
|
||||
|
||||
/@codemirror/search@6.2.2:
|
||||
resolution: {integrity: sha512-2pWY599zXk+lSoJ2iv9EuTO4gB7lhgBPLPwFb/zTbimFH4NmZSaKzJSV51okjABZ7/Rj0DYy5klWbIgaJh2LoQ==}
|
||||
/@codemirror/search@6.4.0:
|
||||
resolution: {integrity: sha512-zMDgaBXah+nMLK2dHz9GdCnGbQu+oaGRXS1qviqNZkvOCv/whp5XZFyoikLp/23PM9RBcbuKUUISUmQHM1eRHw==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
crelt: 1.0.5
|
||||
dev: false
|
||||
|
||||
/@codemirror/state@6.1.2:
|
||||
resolution: {integrity: sha512-Mxff85Hp5va+zuj+H748KbubXjrinX/k28lj43H14T2D0+4kuvEFIEIO7hCEcvBT8ubZyIelt9yGOjj2MWOEQA==}
|
||||
/@codemirror/state@6.2.0:
|
||||
resolution: {integrity: sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==}
|
||||
dev: false
|
||||
|
||||
/@codemirror/view@6.4.1:
|
||||
resolution: {integrity: sha512-QdBpD6E5HYx6YFXXhqwrRyQ83w7CxWZnchM4QpWBVkkmV7/oJT8N+yz2KAi2iRaLObc/aOf7C2RCQTO2yswF8A==}
|
||||
/@codemirror/view@6.11.0:
|
||||
resolution: {integrity: sha512-PRpPRkqMkAKKxEuiUBxapE0YR+wqs9At92ujbJo93PwTZ0jEJDzx9wahrDcXEhQ43Pe0RK9DdZMLWrt+QN80DA==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/state': 6.2.0
|
||||
style-mod: 4.0.3
|
||||
w3c-keyname: 2.2.6
|
||||
dev: false
|
||||
@@ -1544,40 +1573,6 @@ packages:
|
||||
'@floating-ui/core': 1.2.6
|
||||
dev: false
|
||||
|
||||
/@formatjs/ecma402-abstract@1.11.4:
|
||||
resolution: {integrity: sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==}
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.2.25
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@formatjs/fast-memoize@1.2.1:
|
||||
resolution: {integrity: sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==}
|
||||
dependencies:
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@formatjs/icu-messageformat-parser@2.1.0:
|
||||
resolution: {integrity: sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==}
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 1.11.4
|
||||
'@formatjs/icu-skeleton-parser': 1.3.6
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@formatjs/icu-skeleton-parser@1.3.6:
|
||||
resolution: {integrity: sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==}
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 1.11.4
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@formatjs/intl-localematcher@0.2.25:
|
||||
resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==}
|
||||
dependencies:
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/@humanwhocodes/config-array@0.11.8:
|
||||
resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
|
||||
engines: {node: '>=10.10.0'}
|
||||
@@ -1862,12 +1857,12 @@ packages:
|
||||
/@lezer/css@1.1.1:
|
||||
resolution: {integrity: sha512-mSjx+unLLapEqdOYDejnGBokB5+AiJKZVclmud0MKQOKx3DLJ5b5VTCstgDDknR6iIV4gVrN6euzsCnj0A2gQA==}
|
||||
dependencies:
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@lezer/highlight': 1.1.4
|
||||
'@lezer/lr': 1.3.4
|
||||
dev: false
|
||||
|
||||
/@lezer/highlight@1.1.3:
|
||||
resolution: {integrity: sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==}
|
||||
/@lezer/highlight@1.1.4:
|
||||
resolution: {integrity: sha512-IECkFmw2l7sFcYXrV8iT9GeY4W0fU4CxX0WMwhmhMIVjoDdD1Hr6q3G2NqVtLg/yVe5n7i4menG3tJ2r4eCrPQ==}
|
||||
dependencies:
|
||||
'@lezer/common': 1.0.2
|
||||
dev: false
|
||||
@@ -1876,21 +1871,21 @@ packages:
|
||||
resolution: {integrity: sha512-HdJYMVZcT4YsMo7lW3ipL4NoyS2T67kMPuSVS5TgLGqmaCjEU/D6xv7zsa1ktvTK5lwk7zzF1e3eU6gBZIPm5g==}
|
||||
dependencies:
|
||||
'@lezer/common': 1.0.2
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@lezer/highlight': 1.1.4
|
||||
'@lezer/lr': 1.3.4
|
||||
dev: false
|
||||
|
||||
/@lezer/javascript@1.4.3:
|
||||
resolution: {integrity: sha512-k7Eo9z9B1supZ5cCD4ilQv/RZVN30eUQL+gGbr6ybrEY3avBAL5MDiYi2aa23Aj0A79ry4rJRvPAwE2TM8bd+A==}
|
||||
dependencies:
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@lezer/highlight': 1.1.4
|
||||
'@lezer/lr': 1.3.4
|
||||
dev: false
|
||||
|
||||
/@lezer/json@1.0.0:
|
||||
resolution: {integrity: sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==}
|
||||
dependencies:
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@lezer/highlight': 1.1.4
|
||||
'@lezer/lr': 1.3.4
|
||||
dev: false
|
||||
|
||||
@@ -1904,13 +1899,13 @@ packages:
|
||||
resolution: {integrity: sha512-8CY0OoZ6V5EzPjSPeJ4KLVbtXdLBd8V6sRCooN5kHnO28ytreEGTyrtU/zUwo/XLRzGr/e1g44KlzKi3yWGB5A==}
|
||||
dependencies:
|
||||
'@lezer/common': 1.0.2
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@lezer/highlight': 1.1.4
|
||||
dev: false
|
||||
|
||||
/@lezer/python@1.1.4:
|
||||
resolution: {integrity: sha512-x82XgYxqqX0Yiw7uIemQJ3z2QyQme5BYpectkPfNg99OQrakqfwqVolqEVIrsj4QO9rVDLFZZ49J0Vbne7UbAA==}
|
||||
/@lezer/python@1.1.5:
|
||||
resolution: {integrity: sha512-h0DVr6IfrmKUbTc5PeetaC87IZYoHyn5JogsVYW5mRDpVRyEsvaLBMLyEN4Ufc2BKp1c9y2Pkr8ZNLxS8dTLsQ==}
|
||||
dependencies:
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@lezer/highlight': 1.1.4
|
||||
'@lezer/lr': 1.3.4
|
||||
dev: false
|
||||
|
||||
@@ -2186,7 +2181,7 @@ packages:
|
||||
/@types/concat-stream@1.6.1:
|
||||
resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==}
|
||||
dependencies:
|
||||
'@types/node': 8.10.66
|
||||
'@types/node': 18.16.0
|
||||
dev: false
|
||||
|
||||
/@types/cookie@0.5.1:
|
||||
@@ -2223,7 +2218,7 @@ packages:
|
||||
/@types/form-data@0.0.33:
|
||||
resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==}
|
||||
dependencies:
|
||||
'@types/node': 8.10.66
|
||||
'@types/node': 18.16.0
|
||||
dev: false
|
||||
|
||||
/@types/graceful-fs@4.1.6:
|
||||
@@ -2265,7 +2260,6 @@ packages:
|
||||
|
||||
/@types/node@18.16.0:
|
||||
resolution: {integrity: sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==}
|
||||
dev: true
|
||||
|
||||
/@types/node@8.10.66:
|
||||
resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==}
|
||||
@@ -2297,6 +2291,12 @@ packages:
|
||||
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
|
||||
dev: true
|
||||
|
||||
/@types/ws@8.5.4:
|
||||
resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
|
||||
dependencies:
|
||||
'@types/node': 18.16.0
|
||||
dev: true
|
||||
|
||||
/@types/yargs-parser@21.0.0:
|
||||
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
|
||||
dev: true
|
||||
@@ -2784,7 +2784,7 @@ packages:
|
||||
wrap-ansi: 7.0.0
|
||||
dev: true
|
||||
|
||||
/cm6-theme-basic-dark@0.2.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/highlight@1.1.3):
|
||||
/cm6-theme-basic-dark@0.2.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/highlight@1.1.4):
|
||||
resolution: {integrity: sha512-+mNNJecRtxS/KkloMDCQF0oTrT6aFGRZTjnBcdT5UG1pcDO4Brq8l1+0KR/8dZ7hub2gOGOzoi3rGFD8GzlH7Q==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': ^6.0.0
|
||||
@@ -2793,12 +2793,12 @@ packages:
|
||||
'@lezer/highlight': ^1.0.0
|
||||
dependencies:
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
'@lezer/highlight': 1.1.4
|
||||
dev: false
|
||||
|
||||
/cm6-theme-basic-light@0.2.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/highlight@1.1.3):
|
||||
/cm6-theme-basic-light@0.2.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/highlight@1.1.4):
|
||||
resolution: {integrity: sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': ^6.0.0
|
||||
@@ -2807,9 +2807,9 @@ packages:
|
||||
'@lezer/highlight': ^1.0.0
|
||||
dependencies:
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@lezer/highlight': 1.1.3
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
'@lezer/highlight': 1.1.4
|
||||
dev: false
|
||||
|
||||
/co@4.6.0:
|
||||
@@ -2824,13 +2824,13 @@ packages:
|
||||
/codemirror@6.0.1(@lezer/common@1.0.2):
|
||||
resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.3.0(@codemirror/language@6.6.0)(@codemirror/state@6.1.2)(@codemirror/view@6.4.1)(@lezer/common@1.0.2)
|
||||
'@codemirror/commands': 6.1.2
|
||||
'@codemirror/autocomplete': 6.6.1(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.11.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/commands': 6.2.4
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/lint': 6.0.0
|
||||
'@codemirror/search': 6.2.2
|
||||
'@codemirror/state': 6.1.2
|
||||
'@codemirror/view': 6.4.1
|
||||
'@codemirror/lint': 6.2.1
|
||||
'@codemirror/search': 6.4.0
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.11.0
|
||||
transitivePeerDependencies:
|
||||
- '@lezer/common'
|
||||
dev: false
|
||||
@@ -3091,13 +3091,6 @@ packages:
|
||||
d3-time-format: 4.1.0
|
||||
dev: false
|
||||
|
||||
/d3-shape@3.1.0:
|
||||
resolution: {integrity: sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
d3-path: 3.1.0
|
||||
dev: false
|
||||
|
||||
/d3-shape@3.2.0:
|
||||
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -4051,6 +4044,7 @@ packages:
|
||||
|
||||
/globalyzer@0.1.0:
|
||||
resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
|
||||
dev: true
|
||||
|
||||
/globby@11.1.0:
|
||||
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
|
||||
@@ -4066,6 +4060,7 @@ packages:
|
||||
|
||||
/globrex@0.1.2:
|
||||
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
||||
dev: true
|
||||
|
||||
/graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
@@ -4211,15 +4206,6 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/intl-messageformat@9.13.0:
|
||||
resolution: {integrity: sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==}
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 1.11.4
|
||||
'@formatjs/fast-memoize': 1.2.1
|
||||
'@formatjs/icu-messageformat-parser': 2.1.0
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/is-arrayish@0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
dev: true
|
||||
@@ -5116,6 +5102,7 @@ packages:
|
||||
/mri@1.2.0:
|
||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/mrmime@1.0.1:
|
||||
resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
|
||||
@@ -5377,19 +5364,6 @@ packages:
|
||||
camelcase-css: 2.0.1
|
||||
postcss: 8.4.21
|
||||
|
||||
/postcss-load-config@3.1.1:
|
||||
resolution: {integrity: sha512-c/9XYboIbSEUZpiD1UQD0IKiUe8n9WHYV7YFe7X7J+ZwCsEKkUJSFWjS9hBU1RR9THR7jMXst8sxiqP0jjo2mg==}
|
||||
engines: {node: '>= 10'}
|
||||
peerDependencies:
|
||||
ts-node: '>=9.0.0'
|
||||
peerDependenciesMeta:
|
||||
ts-node:
|
||||
optional: true
|
||||
dependencies:
|
||||
lilconfig: 2.1.0
|
||||
yaml: 1.10.2
|
||||
dev: true
|
||||
|
||||
/postcss-load-config@3.1.4(postcss@8.4.21):
|
||||
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -5709,6 +5683,7 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
mri: 1.2.0
|
||||
dev: true
|
||||
|
||||
/safe-buffer@5.1.2:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||
@@ -6016,7 +5991,7 @@ packages:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
/svelte-check@2.2.6(postcss-load-config@3.1.1)(postcss@8.4.21)(svelte@3.58.0):
|
||||
/svelte-check@2.2.6(postcss-load-config@3.1.4)(postcss@8.4.21)(svelte@3.58.0):
|
||||
resolution: {integrity: sha512-oJux/afbmcZO+N+ADXB88h6XANLie8Y2rh2qBlhgfkpr2c3t/q/T0w2JWrHqagaDL8zeNwO8a8RVFBkrRox8gg==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -6030,7 +6005,7 @@ packages:
|
||||
sade: 1.8.1
|
||||
source-map: 0.7.4
|
||||
svelte: 3.58.0
|
||||
svelte-preprocess: 4.10.1(postcss-load-config@3.1.1)(postcss@8.4.21)(svelte@3.58.0)(typescript@5.0.3)
|
||||
svelte-preprocess: 4.10.1(postcss-load-config@3.1.4)(postcss@8.4.21)(svelte@3.58.0)(typescript@5.0.3)
|
||||
typescript: 5.0.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -6095,22 +6070,7 @@ packages:
|
||||
dependencies:
|
||||
svelte: 3.58.0
|
||||
|
||||
/svelte-i18n@3.3.13(svelte@3.58.0):
|
||||
resolution: {integrity: sha512-RQM+ys4+Y9ztH//tX22H1UL2cniLNmIR+N4xmYygV6QpQ6EyQvloZiENRew8XrVzfvJ8HaE8NU6/yurLkl7z3g==}
|
||||
engines: {node: '>= 11.15.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
svelte: ^3.25.1
|
||||
dependencies:
|
||||
deepmerge: 4.3.1
|
||||
estree-walker: 2.0.2
|
||||
intl-messageformat: 9.13.0
|
||||
sade: 1.8.1
|
||||
svelte: 3.58.0
|
||||
tiny-glob: 0.2.9
|
||||
dev: false
|
||||
|
||||
/svelte-preprocess@4.10.1(postcss-load-config@3.1.1)(postcss@8.4.21)(svelte@3.58.0)(typescript@4.5.4):
|
||||
/svelte-preprocess@4.10.1(postcss-load-config@3.1.4)(postcss@8.4.21)(svelte@3.58.0)(typescript@4.5.4):
|
||||
resolution: {integrity: sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==}
|
||||
engines: {node: '>= 9.11.2'}
|
||||
requiresBuild: true
|
||||
@@ -6156,14 +6116,14 @@ packages:
|
||||
detect-indent: 6.1.0
|
||||
magic-string: 0.25.9
|
||||
postcss: 8.4.21
|
||||
postcss-load-config: 3.1.1
|
||||
postcss-load-config: 3.1.4(postcss@8.4.21)
|
||||
sorcery: 0.10.0
|
||||
strip-indent: 3.0.0
|
||||
svelte: 3.58.0
|
||||
typescript: 4.5.4
|
||||
dev: true
|
||||
|
||||
/svelte-preprocess@4.10.1(postcss-load-config@3.1.1)(postcss@8.4.21)(svelte@3.58.0)(typescript@5.0.3):
|
||||
/svelte-preprocess@4.10.1(postcss-load-config@3.1.4)(postcss@8.4.21)(svelte@3.58.0)(typescript@5.0.3):
|
||||
resolution: {integrity: sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==}
|
||||
engines: {node: '>= 9.11.2'}
|
||||
requiresBuild: true
|
||||
@@ -6209,7 +6169,7 @@ packages:
|
||||
detect-indent: 6.1.0
|
||||
magic-string: 0.25.9
|
||||
postcss: 8.4.21
|
||||
postcss-load-config: 3.1.1
|
||||
postcss-load-config: 3.1.4(postcss@8.4.21)
|
||||
sorcery: 0.10.0
|
||||
strip-indent: 3.0.0
|
||||
svelte: 3.58.0
|
||||
@@ -6399,6 +6359,7 @@ packages:
|
||||
dependencies:
|
||||
globalyzer: 0.1.0
|
||||
globrex: 0.1.2
|
||||
dev: true
|
||||
|
||||
/tiny-invariant@1.3.1:
|
||||
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
|
||||
@@ -6483,13 +6444,8 @@ packages:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
dev: true
|
||||
|
||||
/tslib@2.3.1:
|
||||
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
|
||||
dev: true
|
||||
|
||||
/tslib@2.5.0:
|
||||
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
|
||||
dev: false
|
||||
|
||||
/tsutils@3.21.0(typescript@5.0.3):
|
||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||
@@ -7454,6 +7410,19 @@ packages:
|
||||
signal-exit: 3.0.7
|
||||
dev: true
|
||||
|
||||
/ws@8.13.0:
|
||||
resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { get } from "svelte/store";
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { download } from "$lib/utils"
|
||||
|
||||
import { LGraph, LGraphNode } from "@litegraph-ts/core";
|
||||
import type { ComfyAPIStatus } from "$lib/api";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { App, View, Toolbar, Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
||||
import { App, View } from "framework7-svelte"
|
||||
|
||||
import { f7, f7ready } from 'framework7-svelte';
|
||||
|
||||
@@ -26,6 +17,9 @@
|
||||
import GraphPage from './mobile/routes/graph.svelte';
|
||||
import ListSubWorkflowsPage from './mobile/routes/list-subworkflows.svelte';
|
||||
import SubWorkflowPage from './mobile/routes/subworkflow.svelte';
|
||||
import type { Framework7Parameters } from "framework7/types";
|
||||
|
||||
export let app: ComfyApp;
|
||||
|
||||
function onBackKeyDown(e) {
|
||||
if(f7.view.current.router.currentRoute.path == '/'){
|
||||
@@ -39,6 +33,8 @@
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await app.setup();
|
||||
(window as any).app = app;
|
||||
window.addEventListener("backbutton", onBackKeyDown, false);
|
||||
window.addEventListener("popstate", onBackKeyDown, false);
|
||||
});
|
||||
@@ -48,11 +44,14 @@
|
||||
We need to pass them along with the F7 app parameters to <App> component
|
||||
*/
|
||||
|
||||
let f7params = {
|
||||
let f7params: Framework7Parameters = {
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
component: HomePage,
|
||||
options: {
|
||||
props: { app }
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/about/',
|
||||
@@ -65,14 +64,23 @@
|
||||
{
|
||||
path: '/graph/',
|
||||
component: GraphPage,
|
||||
options: {
|
||||
props: { app }
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/subworkflows/',
|
||||
component: ListSubWorkflowsPage,
|
||||
options: {
|
||||
props: { app }
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/subworkflows/:subworkflowID/',
|
||||
component: SubWorkflowPage,
|
||||
options: {
|
||||
props: { app }
|
||||
}
|
||||
},
|
||||
],
|
||||
popup: {
|
||||
@@ -91,16 +99,19 @@
|
||||
</script>
|
||||
|
||||
{#if app}
|
||||
<App theme="auto" name="ComfyBox" {...f7params}>
|
||||
<View
|
||||
url="/"
|
||||
main={true}
|
||||
class="safe-areas"
|
||||
masterDetailBreakpoint={768},
|
||||
browserHistory=true,
|
||||
browserHistoryRoot="/mobile/"
|
||||
>
|
||||
<GenToolbar/>
|
||||
</View>
|
||||
</App>
|
||||
<App theme="auto" name="ComfyBox" {...f7params}>
|
||||
<View
|
||||
url="/"
|
||||
main={true}
|
||||
class="safe-areas"
|
||||
masterDetailBreakpoint={768},
|
||||
browserHistory=true,
|
||||
browserHistoryRoot="/mobile/"
|
||||
>
|
||||
<GenToolbar {app} />
|
||||
</View>
|
||||
</App>
|
||||
<div class="canvas-wrapper pane-wrapper" style="display: none">
|
||||
<canvas id="graph-canvas" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LConnectionKind, LGraph, LGraphNode, type INodeSlot, type SlotIndex, LiteGraph, getStaticProperty, type LGraphAddNodeOptions } from "@litegraph-ts/core";
|
||||
import { LConnectionKind, LGraph, LGraphNode, type INodeSlot, type SlotIndex, LiteGraph, getStaticProperty, type LGraphAddNodeOptions, LGraphCanvas } from "@litegraph-ts/core";
|
||||
import GraphSync from "./GraphSync";
|
||||
import EventEmitter from "events";
|
||||
import type TypedEmitter from "typed-emitter";
|
||||
@@ -53,6 +53,17 @@ export default class ComfyGraph extends LGraph {
|
||||
layoutState.nodeAdded(node)
|
||||
this.graphSync.onNodeAdded(node);
|
||||
|
||||
// All nodes whether they come from base litegraph or ComfyBox should
|
||||
// have tags added to them. Can't override serialization for existing
|
||||
// node types to add `tags` as anew field so putting it in properties is better.
|
||||
if (node.properties.tags == null)
|
||||
node.properties.tags = []
|
||||
|
||||
if ((node as any).canInheritSlotTypes && node.inputs.length > 1) {
|
||||
node.color ||= LGraphCanvas.node_colors["green"].color;
|
||||
node.bgColor ||= LGraphCanvas.node_colors["green"].bgColor;
|
||||
}
|
||||
|
||||
if ("outputProperties" in node) {
|
||||
const widgetNode = node as ComfyWidgetNode;
|
||||
for (const propName of widgetNode.outputProperties) {
|
||||
|
||||
@@ -11,7 +11,7 @@ export type SerializedGraphCanvasState = {
|
||||
}
|
||||
|
||||
export default class ComfyGraphCanvas extends LGraphCanvas {
|
||||
app: ComfyApp
|
||||
app: ComfyApp | null;
|
||||
|
||||
constructor(
|
||||
app: ComfyApp,
|
||||
@@ -60,7 +60,8 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
||||
let color = null;
|
||||
if (node.id === +state.runningNodeId) {
|
||||
color = "#0f0";
|
||||
} else if (this.app.dragOverNode && node.id === this.app.dragOverNode.id) {
|
||||
// this.app can be null inside the constructor if rendering is taking place already
|
||||
} else if (this.app && this.app.dragOverNode && node.id === this.app.dragOverNode.id) {
|
||||
color = "dodgerblue";
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { startDrag, stopDrag } from "$lib/utils"
|
||||
import type { Writable } from "svelte/store";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let container: ContainerLayout | null = null;
|
||||
export let zIndex: number = 0;
|
||||
@@ -20,6 +21,7 @@
|
||||
export let showHandles: boolean = false;
|
||||
export let edit: boolean = false;
|
||||
export let dragDisabled: boolean = false;
|
||||
export let isMobile: boolean = false;
|
||||
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
let children: IDragItem[] | null = null;
|
||||
@@ -72,20 +74,20 @@
|
||||
on:finalize="{handleFinalize}"
|
||||
>
|
||||
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)}
|
||||
{@const hidden = item?.attrs?.hidden}
|
||||
{@const hidden = isHidden(item)}
|
||||
<div class="animation-wrapper"
|
||||
class:hidden={hidden}
|
||||
animate:flip={{duration:flipDurationMs}}
|
||||
style={item?.attrs?.flexGrow ? `flex-grow: ${item.attrs.flexGrow}` : ""}
|
||||
>
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} />
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if container.attrs.hidden && edit}
|
||||
{#if isHidden(container) && edit}
|
||||
<div class="handle handle-hidden" class:hidden={!edit} style="z-index: {zIndex+100}"/>
|
||||
{/if}
|
||||
{#if showHandles}
|
||||
@@ -97,7 +99,7 @@
|
||||
<Block elem_classes={["gradio-accordion"]}>
|
||||
<Accordion label={container.attrs.title} open={container.attrs.openOnStartup}>
|
||||
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)}
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} />
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
{/each}
|
||||
</Accordion>
|
||||
</Block>
|
||||
@@ -164,7 +166,8 @@
|
||||
}
|
||||
|
||||
:global(.label-wrap > span:not(.icon)) {
|
||||
color: var(--block-title-text-color);
|
||||
/* color: var(--block-title-text-color); */
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.handle {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { startDrag, stopDrag } from "$lib/utils"
|
||||
import type { Writable } from "svelte/store";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let container: ContainerLayout | null = null;
|
||||
export let zIndex: number = 0;
|
||||
@@ -19,6 +20,7 @@
|
||||
export let showHandles: boolean = false;
|
||||
export let edit: boolean = false;
|
||||
export let dragDisabled: boolean = false;
|
||||
export let isMobile: boolean = false;
|
||||
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
let children: IDragItem[] | null = null;
|
||||
@@ -74,20 +76,20 @@
|
||||
on:finalize="{handleFinalize}"
|
||||
>
|
||||
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)}
|
||||
{@const hidden = item?.attrs?.hidden}
|
||||
{@const hidden = isHidden(item)}
|
||||
<div class="animation-wrapper"
|
||||
class:hidden={hidden}
|
||||
animate:flip={{duration:flipDurationMs}}
|
||||
style={item?.attrs?.flexGrow ? `flex-grow: ${item.attrs.flexGrow}` : ""}
|
||||
>
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} />
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if container.attrs.hidden && edit}
|
||||
{#if isHidden(container) && edit}
|
||||
<div class="handle handle-hidden" class:hidden={!edit} style="z-index: {zIndex+100}"/>
|
||||
{/if}
|
||||
{#if showHandles}
|
||||
@@ -238,6 +240,10 @@
|
||||
flex-grow: 100;
|
||||
}
|
||||
|
||||
.handle-hidden {
|
||||
background-color: #40404080;
|
||||
}
|
||||
|
||||
.handle-widget:hover {
|
||||
background-color: #add8e680;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,10 @@
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import ComfyUnlockUIButton from "./ComfyUnlockUIButton.svelte";
|
||||
import ComfyGraphView from "./ComfyGraphView.svelte";
|
||||
import { download, jsonToJsObject } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
|
||||
export let app: ComfyApp = undefined;
|
||||
let imageViewer: ImageViewer;
|
||||
let queue: ComfyQueue = undefined;
|
||||
let mainElem: HTMLDivElement;
|
||||
let uiPane: ComfyUIPane = undefined;
|
||||
@@ -32,6 +33,7 @@
|
||||
let resizeTimeout: NodeJS.Timeout | null;
|
||||
let hasShownUIHelpToast: boolean = false;
|
||||
let uiTheme: string = "";
|
||||
let fileInput: HTMLInputElement = undefined;
|
||||
|
||||
let debugLayout: boolean = false;
|
||||
|
||||
@@ -100,8 +102,47 @@
|
||||
if (!app?.lGraph)
|
||||
return;
|
||||
|
||||
const promptFilename = true; // TODO
|
||||
|
||||
let filename = "workflow.json";
|
||||
if (promptFilename) {
|
||||
filename = prompt("Save workflow as:", filename);
|
||||
if (!filename) return;
|
||||
if (!filename.toLowerCase().endsWith(".json")) {
|
||||
filename += ".json";
|
||||
}
|
||||
}
|
||||
else {
|
||||
const date = new Date();
|
||||
const formattedDate = date.toISOString().replace(/:/g, '-').replace(/\.\d{3}/g, '').replace('T', '_').replace("Z", "");
|
||||
filename = `workflow-${formattedDate}.json`
|
||||
}
|
||||
|
||||
const indent = 2
|
||||
const json = JSON.stringify(app.serialize(), null, indent)
|
||||
|
||||
download(filename, json, "application/json")
|
||||
}
|
||||
|
||||
function doLoad(): void {
|
||||
if (!app?.lGraph || !fileInput)
|
||||
return;
|
||||
|
||||
fileInput.click();
|
||||
}
|
||||
|
||||
function loadWorkflow(): void {
|
||||
app.handleFile(fileInput.files[0]);
|
||||
fileInput.files = null;
|
||||
}
|
||||
|
||||
function doSaveLocal(): void {
|
||||
if (!app?.lGraph)
|
||||
return;
|
||||
|
||||
app.saveStateToLocalStorage();
|
||||
toast.push("Saved to local storage.")
|
||||
notify("Saved to local storage.")
|
||||
console.debug(jsonToJsObject(JSON.stringify(app.serialize(), null, 2)))
|
||||
//
|
||||
// const date = new Date();
|
||||
// const formattedDate = date.toISOString().replace(/:/g, '-').replace(/\.\d{3}/g, '').replace('T', '_').replace("Z", "");
|
||||
@@ -109,13 +150,6 @@
|
||||
// download(`workflow-${formattedDate}.json`, JSON.stringify(app.serialize()), "application/json")
|
||||
}
|
||||
|
||||
function doReset(): void {
|
||||
var confirmed = confirm("Are you sure you want to clear the current workflow?");
|
||||
if (confirmed) {
|
||||
app.reset();
|
||||
}
|
||||
}
|
||||
|
||||
async function doLoadDefault(): void {
|
||||
var confirmed = confirm("Are you sure you want to clear the current workflow and load the default graph?");
|
||||
if (confirmed) {
|
||||
@@ -123,9 +157,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
function doClear(): void {
|
||||
var confirmed = confirm("Are you sure you want to clear the current workflow?");
|
||||
if (confirmed) {
|
||||
app.clear();
|
||||
}
|
||||
}
|
||||
|
||||
$: if ($uiState.uiUnlocked && !hasShownUIHelpToast) {
|
||||
hasShownUIHelpToast = true;
|
||||
toast.push("Right-click to open context menu.")
|
||||
notify("Right-click to open context menu.")
|
||||
}
|
||||
|
||||
if (debugLayout) {
|
||||
@@ -134,14 +175,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
app.api.addEventListener("status", (ev: CustomEvent) => {
|
||||
queueState.statusUpdated(ev.detail as ComfyAPIStatus);
|
||||
});
|
||||
|
||||
$: if (app.rootEl && !imageViewer) {
|
||||
imageViewer = new ImageViewer(app.rootEl);
|
||||
}
|
||||
|
||||
$: if (containerElem) {
|
||||
const canvas = containerElem.querySelector<HTMLDivElement>("#graph-canvas")
|
||||
if (canvas) {
|
||||
@@ -169,7 +202,7 @@
|
||||
})
|
||||
|
||||
async function doRefreshCombos() {
|
||||
await app.refreshComboInNodes()
|
||||
await app.refreshComboInNodes(true)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -222,8 +255,14 @@
|
||||
<Button variant="secondary" on:click={doSave}>
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doReset}>
|
||||
Reset
|
||||
<Button variant="secondary" on:click={doSaveLocal}>
|
||||
Save Local
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doLoad}>
|
||||
Load
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doClear}>
|
||||
Clear
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doLoadDefault}>
|
||||
Load Default
|
||||
@@ -255,6 +294,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<LightboxModal />
|
||||
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
||||
</div>
|
||||
|
||||
<SvelteToast options={toastOptions} />
|
||||
@@ -358,4 +398,8 @@
|
||||
span.left {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
#comfy-file-input {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2, BuiltInSlotType } from "@litegraph-ts/core";
|
||||
import type { LConnectionKind, INodeSlot } from "@litegraph-ts/core";
|
||||
import ComfyAPI from "$lib/api"
|
||||
import ComfyAPI, { type ComfyAPIQueueStatus } from "$lib/api"
|
||||
import defaultGraph from "$lib/defaultGraph"
|
||||
import { getPngMetadata, importA1111 } from "$lib/pnginfo";
|
||||
import EventEmitter from "events";
|
||||
@@ -9,6 +9,7 @@ import type TypedEmitter from "typed-emitter";
|
||||
// Import nodes
|
||||
import "@litegraph-ts/nodes-basic"
|
||||
import "@litegraph-ts/nodes-events"
|
||||
import "@litegraph-ts/nodes-logic"
|
||||
import "@litegraph-ts/nodes-math"
|
||||
import "@litegraph-ts/nodes-strings"
|
||||
import "$lib/nodes/index"
|
||||
@@ -27,7 +28,8 @@ import ComfyGraph from "$lib/ComfyGraph";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import { get } from "svelte/store";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { promptToGraphVis } from "$lib/utils";
|
||||
import { promptToGraphVis, workflowToGraphVis } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
|
||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||
|
||||
@@ -69,6 +71,27 @@ export type Progress = {
|
||||
max: number
|
||||
}
|
||||
|
||||
function isActiveBackendNode(node: ComfyGraphNode, tag: string | null): boolean {
|
||||
if (!node.isBackendNode)
|
||||
return false;
|
||||
|
||||
if (tag && !hasTag(node, tag)) {
|
||||
console.debug("Skipping tagged node", tag, node.properties.tags, node)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.mode === NodeMode.NEVER) {
|
||||
// Don't serialize muted nodes
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function hasTag(node: LGraphNode, tag: string): boolean {
|
||||
return "tags" in node.properties && node.properties.tags.indexOf(tag) !== -1
|
||||
}
|
||||
|
||||
export default class ComfyApp {
|
||||
api: ComfyAPI;
|
||||
rootEl: HTMLDivElement | null = null;
|
||||
@@ -97,7 +120,9 @@ export default class ComfyApp {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rootEl = document.getElementById("main") as HTMLDivElement;
|
||||
this.setupColorScheme()
|
||||
|
||||
this.rootEl = document.getElementById("app") as HTMLDivElement;
|
||||
this.canvasEl = document.getElementById("graph-canvas") as HTMLCanvasElement;
|
||||
this.lGraph = new ComfyGraph();
|
||||
this.lCanvas = new ComfyGraphCanvas(this, this.canvasEl);
|
||||
@@ -137,17 +162,12 @@ export default class ComfyApp {
|
||||
this.addPasteHandler();
|
||||
this.addKeyboardHandler();
|
||||
|
||||
this.setupColorScheme()
|
||||
|
||||
// await this.#invokeExtensionsAsync("setup");
|
||||
|
||||
// Ensure the canvas fills the window
|
||||
this.resizeCanvas();
|
||||
window.addEventListener("resize", this.resizeCanvas.bind(this));
|
||||
|
||||
this.lGraph.start();
|
||||
this.lGraph.eventBus.on("afterExecute", () => this.lCanvas.draw(true))
|
||||
|
||||
this.alreadySetup = true;
|
||||
|
||||
return Promise.resolve();
|
||||
@@ -295,6 +315,10 @@ export default class ComfyApp {
|
||||
this.lGraph.setDirtyCanvas(true, false);
|
||||
});
|
||||
|
||||
this.api.addEventListener("status", (ev: CustomEvent) => {
|
||||
queueState.statusUpdated(ev.detail as ComfyAPIQueueStatus);
|
||||
});
|
||||
|
||||
this.api.addEventListener("executed", ({ detail }: CustomEvent) => {
|
||||
this.nodeOutputs[detail.node] = detail.output;
|
||||
const node = this.lGraph.getNodeById(detail.node) as ComfyGraphNode;
|
||||
@@ -322,8 +346,8 @@ export default class ComfyApp {
|
||||
|
||||
private setupColorScheme() {
|
||||
const setColor = (type: any, color: string) => {
|
||||
this.lCanvas.link_type_colors[type] = color
|
||||
this.lCanvas.default_connection_color_byType[type] = color
|
||||
LGraphCanvas.DEFAULT_LINK_TYPE_COLORS[type] = color
|
||||
LGraphCanvas.DEFAULT_CONNECTION_COLORS_BY_TYPE[type] = color
|
||||
}
|
||||
|
||||
// Distinguish frontend/backend connections
|
||||
@@ -374,6 +398,9 @@ export default class ComfyApp {
|
||||
this.lCanvas.deserialize(data.canvas)
|
||||
|
||||
await this.refreshComboInNodes();
|
||||
|
||||
this.lGraph.start();
|
||||
this.lGraph.eventBus.on("afterExecute", () => this.lCanvas.draw(true))
|
||||
}
|
||||
|
||||
async initDefaultGraph() {
|
||||
@@ -404,7 +431,7 @@ export default class ComfyApp {
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
clear() {
|
||||
this.clean();
|
||||
|
||||
const blankGraph: SerializedLGraph = {
|
||||
@@ -438,33 +465,26 @@ export default class ComfyApp {
|
||||
for (const node_ of this.lGraph.computeExecutionOrder<ComfyGraphNode>(false, null)) {
|
||||
const n = workflow.nodes.find((n) => n.id === node_.id);
|
||||
|
||||
if (!node_.isBackendNode) {
|
||||
// console.debug("Not serializing node: ", node_.type)
|
||||
if (!isActiveBackendNode(node_, tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const node = node_ as ComfyBackendNode;
|
||||
|
||||
if (tag && node.tags.indexOf(tag) === -1) {
|
||||
console.debug("Skipping tagged node", tag, node.tags)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.mode === NodeMode.NEVER) {
|
||||
// Don't serialize muted nodes
|
||||
continue;
|
||||
}
|
||||
|
||||
const inputs = {};
|
||||
|
||||
// Store all link values
|
||||
// Store input values passed by frontend-only nodes
|
||||
if (node.inputs) {
|
||||
for (let i = 0; i < node.inputs.length; i++) {
|
||||
const inp = node.inputs[i];
|
||||
const inputLink = node.getInputLink(i)
|
||||
const inputNode = node.getInputNode(i)
|
||||
|
||||
if (inputNode && tag && "tags" in inputNode && (inputNode.tags as string[]).indexOf(tag) === -1) {
|
||||
// We don't check tags for non-backend nodes.
|
||||
// Just check for node inactivity (so you can toggle groups of
|
||||
// tagged frontend nodes on/off)
|
||||
if (inputNode && inputNode.mode === NodeMode.NEVER) {
|
||||
console.debug("Skipping inactive node", inputNode)
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -500,31 +520,44 @@ export default class ComfyApp {
|
||||
}
|
||||
}
|
||||
|
||||
// Store all links between nodes
|
||||
// Store links between backend-only and hybrid nodes
|
||||
for (let i = 0; i < node.inputs.length; i++) {
|
||||
let parent: ComfyGraphNode = node.getInputNode(i) as ComfyGraphNode;
|
||||
if (parent) {
|
||||
const seen = {}
|
||||
let link = node.getInputLink(i);
|
||||
|
||||
const isValidParent = (parent: ComfyGraphNode) => {
|
||||
const isFrontendParent = (parent: ComfyGraphNode) => {
|
||||
if (!parent || parent.isBackendNode)
|
||||
return false;
|
||||
if ("tags" in parent && (parent.tags as string[]).indexOf(tag) === -1)
|
||||
if (tag && !hasTag(parent, tag))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
while (isValidParent(parent)) {
|
||||
link = parent.getInputLink(link.origin_slot);
|
||||
if (link && !seen[link.id]) {
|
||||
seen[link.id] = true
|
||||
const inputNode = parent.getInputNode(link.origin_slot) as ComfyGraphNode;
|
||||
if (inputNode && "tags" in inputNode && tag && (inputNode.tags as string[]).indexOf(tag) === -1) {
|
||||
console.debug("Skipping tagged parent node", tag, node.tags)
|
||||
// If there are frontend-only nodes between us and another
|
||||
// backend node, we have to traverse them first. This
|
||||
// behavior is dependent on the type of node. Reroute nodes
|
||||
// will simply follow their single input, while branching
|
||||
// nodes have conditional logic that determines which link
|
||||
// to follow backwards.
|
||||
while (isFrontendParent(parent)) {
|
||||
const nextLink = parent.getUpstreamLink()
|
||||
if (nextLink == null) {
|
||||
console.warn("[graphToPrompt] No upstream link found in frontend node", parent)
|
||||
break;
|
||||
}
|
||||
|
||||
if (nextLink && !seen[nextLink.id]) {
|
||||
seen[nextLink.id] = true
|
||||
const inputNode = parent.graph.getNodeById(nextLink.origin_id) as ComfyGraphNode;
|
||||
if (inputNode && tag && !hasTag(inputNode, tag)) {
|
||||
console.debug("[graphToPrompt] Skipping tagged intermediate frontend node", tag, node.properties.tags)
|
||||
parent = null;
|
||||
}
|
||||
else {
|
||||
console.debug("[graphToPrompt] Traverse upstream link", parent.id, inputNode?.id, inputNode?.isBackendNode)
|
||||
link = nextLink;
|
||||
parent = inputNode;
|
||||
}
|
||||
} else {
|
||||
@@ -533,9 +566,10 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
if (link && parent && parent.isBackendNode) {
|
||||
if ("tags" in parent && tag && (parent.tags as string[]).indexOf(tag) === -1)
|
||||
if (tag && !hasTag(parent, tag))
|
||||
continue;
|
||||
|
||||
console.debug("[graphToPrompt] final link", parent.id, node.id)
|
||||
const input = node.inputs[i]
|
||||
// TODO can null be a legitimate value in some cases?
|
||||
// Nodes like CLIPLoader will never have a value in the frontend, hence "null".
|
||||
@@ -552,14 +586,13 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
// Remove inputs connected to removed nodes
|
||||
|
||||
for (const o in output) {
|
||||
for (const i in output[o].inputs) {
|
||||
if (Array.isArray(output[o].inputs[i])
|
||||
&& output[o].inputs[i].length === 2
|
||||
&& !output[output[o].inputs[i][0]]) {
|
||||
console.debug("Prune removed node link", o, i, output[o].inputs[i])
|
||||
delete output[o].inputs[i];
|
||||
for (const nodeId in output) {
|
||||
for (const inputName in output[nodeId].inputs) {
|
||||
if (Array.isArray(output[nodeId].inputs[inputName])
|
||||
&& output[nodeId].inputs[inputName].length === 2
|
||||
&& !output[output[nodeId].inputs[inputName][0]]) {
|
||||
console.debug("Prune removed node link", nodeId, inputName, output[nodeId].inputs[inputName])
|
||||
delete output[nodeId].inputs[inputName];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -595,17 +628,15 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
const p = await this.graphToPrompt(tag);
|
||||
console.debug(promptToGraphVis(p))
|
||||
|
||||
try {
|
||||
await this.api.queuePrompt(num, p);
|
||||
} catch (error) {
|
||||
// this.ui.dialog.show(error.response || error.toString());
|
||||
const mes = error.response || error.toString()
|
||||
toast.push(`Error queuing prompt:\n${mes}`, {
|
||||
theme: {
|
||||
'--toastBackground': 'var(--color-red-500)',
|
||||
}
|
||||
})
|
||||
notify(`Error queuing prompt:\n${mes}`, null, "error")
|
||||
console.error(promptToGraphVis(p))
|
||||
console.error("Error queuing prompt", mes, num, p)
|
||||
break;
|
||||
}
|
||||
@@ -634,16 +665,21 @@ export default class ComfyApp {
|
||||
if (file.type === "image/png") {
|
||||
const pngInfo = await getPngMetadata(file);
|
||||
if (pngInfo) {
|
||||
if (pngInfo.workflow) {
|
||||
this.loadGraphData(JSON.parse(pngInfo.workflow));
|
||||
if (pngInfo.comfyBoxConfig) {
|
||||
this.deserialize(JSON.parse(pngInfo.comfyBoxConfig));
|
||||
} else if (pngInfo.parameters) {
|
||||
importA1111(this.lGraph, pngInfo.parameters, this.api);
|
||||
throw "TODO import A111 import!"
|
||||
// importA1111(this.lGraph, pngInfo.parameters, this.api);
|
||||
}
|
||||
else {
|
||||
console.error("No metadata found in image file.", pngInfo)
|
||||
notify("No metadata found in image file.")
|
||||
}
|
||||
}
|
||||
} else if (file.type === "application/json" || file.name.endsWith(".json")) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
this.loadGraphData(JSON.parse(reader.result as string));
|
||||
this.deserialize(JSON.parse(reader.result as string));
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
@@ -662,7 +698,7 @@ export default class ComfyApp {
|
||||
/**
|
||||
* Refresh combo list on whole nodes
|
||||
*/
|
||||
async refreshComboInNodes() {
|
||||
async refreshComboInNodes(flashUI: boolean = false) {
|
||||
const defs = await this.api.getNodeDefs();
|
||||
|
||||
for (let nodeNum in this.lGraph._nodes) {
|
||||
@@ -680,11 +716,13 @@ export default class ComfyApp {
|
||||
const inputNode = node.getInputNode(index)
|
||||
|
||||
if (inputNode && "doAutoConfig" in inputNode) {
|
||||
const comfyInputNode = inputNode as nodes.ComfyWidgetNode;
|
||||
comfyInputNode.doAutoConfig(comfyInput)
|
||||
if (!comfyInput.config.values.includes(get(comfyInputNode.value))) {
|
||||
comfyInputNode.setValue(comfyInput.config.defaultValue || comfyInput.config.values[0])
|
||||
const comfyComboNode = inputNode as nodes.ComfyComboNode;
|
||||
comfyComboNode.doAutoConfig(comfyInput)
|
||||
if (!comfyInput.config.values.includes(get(comfyComboNode.value))) {
|
||||
comfyComboNode.setValue(comfyInput.config.defaultValue || comfyInput.config.values[0])
|
||||
}
|
||||
if (flashUI)
|
||||
comfyComboNode.comboRefreshed.set(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
value = spec.deserialize(value)
|
||||
|
||||
target.attrs[name] = value
|
||||
target.attrsChanged.set(!get(target.attrsChanged))
|
||||
target.attrsChanged.set(get(target.attrsChanged) + 1)
|
||||
|
||||
if (node && "propsChanged" in node) {
|
||||
const comfyNode = node as ComfyWidgetNode
|
||||
@@ -151,27 +151,39 @@
|
||||
|
||||
console.warn(spec)
|
||||
if (spec.refreshPanelOnChange) {
|
||||
console.error("A! refresh")
|
||||
$refreshPanel += 1;
|
||||
doRefreshPanel()
|
||||
}
|
||||
}
|
||||
|
||||
function getProperty(node: LGraphNode, spec: AttributesSpec) {
|
||||
let value = node.properties[spec.name]
|
||||
if (value == null)
|
||||
value = spec.defaultValue
|
||||
else if (spec.serialize)
|
||||
value = spec.serialize(value)
|
||||
console.debug("[ComfyProperties] getProperty", spec, value, node)
|
||||
return value
|
||||
}
|
||||
|
||||
function updateProperty(spec: AttributesSpec, value: any) {
|
||||
if (node == null || !spec.editable)
|
||||
return
|
||||
|
||||
const name = spec.name
|
||||
console.warn("updateProperty", name, value)
|
||||
console.warn("[ComfyProperties] updateProperty", name, value)
|
||||
|
||||
if (spec.deserialize)
|
||||
value = spec.deserialize(value)
|
||||
|
||||
node.properties[name] = value;
|
||||
|
||||
if ("propsChanged" in node) {
|
||||
const comfyNode = node as ComfyWidgetNode
|
||||
comfyNode.propsChanged.set(get(comfyNode.propsChanged) + 1)
|
||||
comfyNode.notifyPropsChanged();
|
||||
}
|
||||
|
||||
if (spec.refreshPanelOnChange)
|
||||
$refreshPanel += 1;
|
||||
doRefreshPanel()
|
||||
}
|
||||
|
||||
function getVar(node: LGraphNode, spec: AttributesSpec) {
|
||||
@@ -201,8 +213,14 @@
|
||||
comfyNode.propsChanged.set(get(comfyNode.propsChanged) + 1)
|
||||
}
|
||||
|
||||
if (spec.refreshPanelOnChange)
|
||||
$refreshPanel += 1;
|
||||
if (spec.refreshPanelOnChange) {
|
||||
doRefreshPanel()
|
||||
}
|
||||
}
|
||||
|
||||
function doRefreshPanel() {
|
||||
console.warn("[ComfyProperties] doRefreshPanel")
|
||||
$refreshPanel += 1;
|
||||
}
|
||||
|
||||
function updateWorkflowAttribute(spec: AttributesSpec, value: any) {
|
||||
@@ -214,6 +232,9 @@
|
||||
|
||||
$layoutState.attrs[name] = value
|
||||
$layoutState = $layoutState
|
||||
|
||||
if (spec.refreshPanelOnChange)
|
||||
doRefreshPanel()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -281,7 +302,7 @@
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={node.properties[spec.name] || spec.defaultValue}
|
||||
value={getProperty(node, spec)}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
on:input={(e) => updateProperty(spec, e.detail)}
|
||||
label={spec.name}
|
||||
@@ -290,7 +311,7 @@
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={node.properties[spec.name] || spec.defaultValue}
|
||||
value={getProperty(node, spec)}
|
||||
label={spec.name}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
@@ -298,7 +319,7 @@
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={node.properties[spec.name] || spec.defaultValue}
|
||||
value={getProperty(node, spec)}
|
||||
step={spec.step || 1}
|
||||
min={spec.min || -1024}
|
||||
max={spec.max || 1024}
|
||||
@@ -308,7 +329,7 @@
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={node.properties[spec.name] || spec.defaultValue}
|
||||
value={getProperty(node, spec)}
|
||||
values={spec.values}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<script lang="ts">
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import ProgressBar from "./ProgressBar.svelte";
|
||||
|
||||
function getNodeInfo(nodeId: number): string {
|
||||
let app = (window as any).app;
|
||||
if (!app)
|
||||
return String(nodeId);
|
||||
|
||||
const title = app.lGraph.getNodeById(nodeId)?.title || String(nodeId);
|
||||
return title + " (" + nodeId + ")"
|
||||
}
|
||||
import { getNodeInfo } from "$lib/utils"
|
||||
|
||||
const entries = [
|
||||
{
|
||||
@@ -89,7 +81,7 @@
|
||||
<span>Node: {getNodeInfo($queueState.runningNodeId)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<ProgressBar value={$queueState.progress?.value} max={$queueState.progress?.max} />
|
||||
<ProgressBar value={$queueState.progress?.value} max={$queueState.progress?.max} styles="height: 30px;" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0}
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
import { flip } from 'svelte/animate';
|
||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { startDrag, stopDrag } from "$lib/utils"
|
||||
import type { Writable } from "svelte/store";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let container: ContainerLayout | null = null;
|
||||
export let zIndex: number = 0;
|
||||
export let classes: string[] = [];
|
||||
export let showHandles: boolean = false;
|
||||
export let isMobile: boolean = false
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
|
||||
$: if (container) {
|
||||
@@ -33,12 +36,14 @@
|
||||
{@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets" && zIndex > 1}
|
||||
{@const dragDisabled = zIndex === 0 || $layoutState.currentSelection.length > 2 || !$uiState.uiUnlocked}
|
||||
{#key $attrsChanged}
|
||||
{#if container.attrs.variant === "tabs"}
|
||||
<TabsContainer {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} />
|
||||
{:else if container.attrs.variant === "accordion"}
|
||||
<AccordionContainer {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} />
|
||||
{:else}
|
||||
<BlockContainer {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} />
|
||||
{#if edit || !isHidden(container)}
|
||||
{#if container.attrs.variant === "tabs"}
|
||||
<TabsContainer {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} {isMobile} />
|
||||
{:else if container.attrs.variant === "accordion"}
|
||||
<AccordionContainer {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} {isMobile} />
|
||||
{:else}
|
||||
<BlockContainer {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} {isMobile} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
@@ -23,15 +23,17 @@
|
||||
|
||||
<style>
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-color: lightgrey;
|
||||
padding: 3px;
|
||||
padding: 0px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bar {
|
||||
height: 100%;
|
||||
background-color: #B3D8A9;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { startDrag, stopDrag } from "$lib/utils"
|
||||
import type { Writable } from "svelte/store";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let container: ContainerLayout | null = null;
|
||||
export let zIndex: number = 0;
|
||||
@@ -20,6 +21,7 @@
|
||||
export let showHandles: boolean = false;
|
||||
export let edit: boolean = false;
|
||||
export let dragDisabled: boolean = false;
|
||||
export let isMobile: boolean = false;
|
||||
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
let children: IDragItem[] | null = null;
|
||||
@@ -85,7 +87,7 @@
|
||||
on:finalize="{handleFinalize}"
|
||||
>
|
||||
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item, i(item.id)}
|
||||
{@const hidden = item?.attrs?.hidden}
|
||||
{@const hidden = isHidden(item)}
|
||||
{@const tabName = getTabName(container, i)}
|
||||
<div class="animation-wrapper"
|
||||
class:hidden={hidden}
|
||||
@@ -95,7 +97,7 @@
|
||||
<label for={String(item.id)}>
|
||||
<BlockTitle><strong>Tab {i+1}:</strong> {tabName}</BlockTitle>
|
||||
</label>
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} />
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
||||
{/if}
|
||||
@@ -103,7 +105,7 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if container.attrs.hidden && edit}
|
||||
{#if isHidden(container) && edit}
|
||||
<div class="handle handle-hidden" class:hidden={!edit} style="z-index: {zIndex+100}"/>
|
||||
{/if}
|
||||
{#if showHandles}
|
||||
@@ -115,7 +117,7 @@
|
||||
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item, i(item.id)}
|
||||
{@const tabName = getTabName(container, i)}
|
||||
<TabItem name={tabName} on:select={() => console.log("tab " + i)}>
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} />
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
</TabItem>
|
||||
{/each}
|
||||
</Tabs>
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
import Container from "./Container.svelte"
|
||||
import { type Writable } from "svelte/store"
|
||||
import type { ComfyWidgetNode } from "$lib/nodes";
|
||||
import { NodeMode } from "@litegraph-ts/core";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let dragItem: IDragItem | null = null;
|
||||
export let zIndex: number = 0;
|
||||
export let classes: string[] = [];
|
||||
export let isMobile: boolean = false;
|
||||
let container: ContainerLayout | null = null;
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
@@ -59,21 +62,22 @@
|
||||
|
||||
{#if container}
|
||||
{#key $attrsChanged}
|
||||
<Container {container} {classes} {zIndex} {showHandles} />
|
||||
<Container {container} {classes} {zIndex} {showHandles} {isMobile} />
|
||||
{/key}
|
||||
{:else if widget && widget.node}
|
||||
{@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets" && zIndex > 1}
|
||||
{@const hidden = isHidden(widget)}
|
||||
{#key $attrsChanged}
|
||||
{#key $propsChanged}
|
||||
<div class="widget {widget.attrs.classes} {getWidgetClass()}"
|
||||
class:edit={edit}
|
||||
class:selected={$uiState.uiUnlocked && $layoutState.currentSelection.includes(widget.id)}
|
||||
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.node.id}
|
||||
class:hidden={widget.attrs.hidden}
|
||||
class:hidden={hidden}
|
||||
>
|
||||
<svelte:component this={widget.node.svelteComponentType} {widget} />
|
||||
<svelte:component this={widget.node.svelteComponentType} {widget} {isMobile} />
|
||||
</div>
|
||||
{#if widget.attrs.hidden && edit}
|
||||
{#if hidden && edit}
|
||||
<div class="handle handle-hidden" class:hidden={!edit} style="z-index: {zIndex+100}"/>
|
||||
{/if}
|
||||
{#if showHandles}
|
||||
|
||||
130
src/lib/components/gradio/form/Range.svelte
Normal file
130
src/lib/components/gradio/form/Range.svelte
Normal file
@@ -0,0 +1,130 @@
|
||||
<script context="module">
|
||||
let _id = 0;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
|
||||
export let value: number = 0;
|
||||
export let minimum: number = 0;
|
||||
export let maximum: number = 100;
|
||||
export let step: number = 1;
|
||||
export let disabled: boolean = false;
|
||||
export let label: string;
|
||||
export let info: string | undefined = undefined;
|
||||
export let show_label: boolean;
|
||||
|
||||
const id = `range_id_${_id++}`;
|
||||
const dispatch = createEventDispatcher<{ change: number; release: number }>();
|
||||
let inputValue = value;
|
||||
|
||||
function handle_input(e: Event) {
|
||||
const element = e.currentTarget as HTMLInputElement;
|
||||
let newValue = parseFloat(element.value);
|
||||
if (isNaN(newValue)) {
|
||||
newValue = minimum;
|
||||
}
|
||||
inputValue = Math.min(Math.max(inputValue, minimum), maximum);
|
||||
value = inputValue;
|
||||
dispatch("release", value);
|
||||
}
|
||||
|
||||
function handle_release(e: MouseEvent) {
|
||||
dispatch("release", value);
|
||||
}
|
||||
|
||||
$: {
|
||||
inputValue = value;
|
||||
dispatch("change", value);
|
||||
}
|
||||
const clamp = () => {
|
||||
dispatch("release", value);
|
||||
value = Math.min(Math.max(value, minimum), maximum);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="wrap">
|
||||
<div class="head">
|
||||
<label for={id}>
|
||||
<BlockTitle {show_label} {info}>{label}</BlockTitle>
|
||||
</label>
|
||||
<input
|
||||
data-testid="number-input"
|
||||
type="number"
|
||||
bind:value={inputValue}
|
||||
on:input={handle_input}
|
||||
min={minimum}
|
||||
max={maximum}
|
||||
on:blur={clamp}
|
||||
{step}
|
||||
{disabled}
|
||||
on:pointerup={handle_release}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="range"
|
||||
{id}
|
||||
name="cowbell"
|
||||
bind:value
|
||||
min={minimum}
|
||||
max={maximum}
|
||||
{step}
|
||||
{disabled}
|
||||
on:pointerup={handle_release}
|
||||
on:pointerdown
|
||||
on:pointermove
|
||||
/>
|
||||
|
||||
<style lang="scss">
|
||||
.wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
input[type="number"] {
|
||||
display: block;
|
||||
position: relative;
|
||||
outline: none !important;
|
||||
box-shadow: var(--input-shadow);
|
||||
border: var(--input-border-width) solid var(--input-border-color);
|
||||
border-radius: var(--input-radius);
|
||||
background: var(--input-background-fill);
|
||||
padding: var(--size-2) var(--size-2);
|
||||
height: var(--size-6);
|
||||
color: var(--body-text-color);
|
||||
font-size: var(--input-text-size);
|
||||
line-height: var(--line-sm);
|
||||
text-align: center;
|
||||
}
|
||||
input:disabled {
|
||||
-webkit-text-fill-color: var(--body-text-color);
|
||||
-webkit-opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
input[type="number"]:focus {
|
||||
box-shadow: var(--input-shadow-focus);
|
||||
border-color: var(--input-border-color-focus);
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: var(--input-placeholder-color);
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
width: 100%;
|
||||
accent-color: var(--slider-color);
|
||||
}
|
||||
|
||||
input[disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
1
src/lib/components/gradio/form/index.ts
Normal file
1
src/lib/components/gradio/form/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Range } from "./Range.svelte"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,24 +1,17 @@
|
||||
import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, BuiltInSlotType, type ITextWidget, type SerializedLGraphNode } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
||||
import { get } from "svelte/store";
|
||||
import notify from "$lib/notify";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
|
||||
export interface ComfyQueueEventsProperties extends Record<any, any> {
|
||||
}
|
||||
import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SerializedLGraphNode, type SlotLayout } from "@litegraph-ts/core";
|
||||
import { get } from "svelte/store";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||
import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes";
|
||||
|
||||
export class ComfyQueueEvents extends ComfyGraphNode {
|
||||
override properties: ComfyQueueEventsProperties = {
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
outputs: [
|
||||
{ name: "beforeQueued", type: BuiltInSlotType.EVENT },
|
||||
{ name: "afterQueued", type: BuiltInSlotType.EVENT },
|
||||
{ name: "prompt", type: "*" }
|
||||
{ name: "afterQueued", type: BuiltInSlotType.EVENT }
|
||||
],
|
||||
}
|
||||
|
||||
@@ -55,24 +48,21 @@ LiteGraph.registerNodeType({
|
||||
type: "actions/queue_events"
|
||||
})
|
||||
|
||||
export interface ComfyOnExecutedEventProperties extends Record<any, any> {
|
||||
images: GalleryOutput | null,
|
||||
filename: string | null
|
||||
export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperties {
|
||||
images: GalleryOutput | null
|
||||
}
|
||||
|
||||
export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
||||
override properties: ComfyOnExecutedEventProperties = {
|
||||
images: null,
|
||||
filename: null
|
||||
export class ComfyStoreImagesAction extends ComfyGraphNode {
|
||||
override properties: ComfyStoreImagesActionProperties = {
|
||||
images: null
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
{ name: "images", type: "IMAGE" }
|
||||
{ name: "output", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } }
|
||||
],
|
||||
outputs: [
|
||||
{ name: "images", type: "OUTPUT" },
|
||||
{ name: "onExecuted", type: BuiltInSlotType.EVENT },
|
||||
],
|
||||
}
|
||||
|
||||
@@ -81,29 +71,30 @@ export class ComfyOnExecutedEvent extends ComfyGraphNode {
|
||||
this.setOutputData(0, this.properties.images)
|
||||
}
|
||||
|
||||
override receiveOutput(output: any) {
|
||||
if (output && "images" in output) {
|
||||
this.setProperty("images", output as GalleryOutput)
|
||||
this.setOutputData(0, this.properties.images)
|
||||
this.triggerSlot(1, "bang")
|
||||
}
|
||||
override onAction(action: any, param: any) {
|
||||
if (action !== "store" || !param || !("images" in param))
|
||||
return;
|
||||
|
||||
this.setProperty("images", param as GalleryOutput)
|
||||
this.setOutputData(0, this.properties.images)
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfyOnExecutedEvent,
|
||||
title: "Comfy.OnExecutedEvent",
|
||||
desc: "Triggers a 'bang' event when a prompt output is received.",
|
||||
type: "actions/on_executed"
|
||||
class: ComfyStoreImagesAction,
|
||||
title: "Comfy.StoreImagesAction",
|
||||
desc: "Stores images from an onExecuted callback",
|
||||
type: "actions/store_images"
|
||||
})
|
||||
|
||||
export interface ComfyCopyActionProperties extends Record<any, any> {
|
||||
export interface ComfyCopyActionProperties extends ComfyGraphNodeProperties {
|
||||
value: any
|
||||
}
|
||||
|
||||
export class ComfyCopyAction extends ComfyGraphNode {
|
||||
override properties: ComfyCopyActionProperties = {
|
||||
value: null
|
||||
value: null,
|
||||
tags: []
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
@@ -149,7 +140,7 @@ LiteGraph.registerNodeType({
|
||||
type: "actions/copy"
|
||||
})
|
||||
|
||||
export interface ComfySwapActionProperties extends Record<any, any> {
|
||||
export interface ComfySwapActionProperties extends ComfyGraphNodeProperties {
|
||||
}
|
||||
|
||||
export class ComfySwapAction extends ComfyGraphNode {
|
||||
@@ -163,16 +154,16 @@ export class ComfySwapAction extends ComfyGraphNode {
|
||||
{ name: "swap", type: BuiltInSlotType.ACTION }
|
||||
],
|
||||
outputs: [
|
||||
{ name: "B", type: "*" },
|
||||
{ name: "A", type: "*" }
|
||||
{ name: "B", type: BuiltInSlotType.EVENT },
|
||||
{ name: "A", type: BuiltInSlotType.EVENT }
|
||||
],
|
||||
}
|
||||
|
||||
override onAction(action: any, param: any) {
|
||||
const a = this.getInputData(0)
|
||||
const b = this.getInputData(1)
|
||||
this.setOutputData(0, a)
|
||||
this.setOutputData(1, b)
|
||||
this.triggerSlot(0, a)
|
||||
this.triggerSlot(1, b)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -183,13 +174,14 @@ LiteGraph.registerNodeType({
|
||||
type: "actions/swap"
|
||||
})
|
||||
|
||||
export interface ComfyNotifyActionProperties extends Record<any, any> {
|
||||
export interface ComfyNotifyActionProperties extends ComfyGraphNodeProperties {
|
||||
message: string
|
||||
}
|
||||
|
||||
export class ComfyNotifyAction extends ComfyGraphNode {
|
||||
override properties: ComfyNotifyActionProperties = {
|
||||
message: "Nya."
|
||||
message: "Nya.",
|
||||
tags: []
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
@@ -202,7 +194,7 @@ export class ComfyNotifyAction extends ComfyGraphNode {
|
||||
override onAction(action: any, param: any) {
|
||||
const message = this.getInputData(0);
|
||||
if (message) {
|
||||
toast.push(message);
|
||||
notify(message);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -214,7 +206,7 @@ LiteGraph.registerNodeType({
|
||||
type: "actions/notify"
|
||||
})
|
||||
|
||||
export interface ComfyExecuteSubgraphActionProperties extends Record<any, any> {
|
||||
export interface ComfyExecuteSubgraphActionProperties extends ComfyGraphNodeProperties {
|
||||
tag: string | null,
|
||||
}
|
||||
|
||||
@@ -253,3 +245,83 @@ LiteGraph.registerNodeType({
|
||||
desc: "Runs a part of the graph based on a tag",
|
||||
type: "actions/execute_subgraph"
|
||||
})
|
||||
|
||||
export interface ComfySetNodeModeActionProperties extends ComfyGraphNodeProperties {
|
||||
targetTags: string,
|
||||
enable: boolean,
|
||||
}
|
||||
|
||||
export class ComfySetNodeModeAction extends ComfyGraphNode {
|
||||
override properties: ComfySetNodeModeActionProperties = {
|
||||
targetTags: "",
|
||||
enable: false,
|
||||
tags: []
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
{ name: "enabled", type: "boolean" },
|
||||
{ name: "set", type: BuiltInSlotType.ACTION },
|
||||
],
|
||||
}
|
||||
|
||||
displayWidget: ITextWidget;
|
||||
enableWidget: IToggleWidget;
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title)
|
||||
this.displayWidget = this.addWidget("text", "Tags", this.properties.targetTags, "targetTags")
|
||||
}
|
||||
|
||||
override onPropertyChanged(property: any, value: any) {
|
||||
if (property === "enabled") {
|
||||
this.enableWidget.value = value
|
||||
}
|
||||
}
|
||||
|
||||
override onAction(action: any, param: any) {
|
||||
let enabled = this.getInputData(0)
|
||||
|
||||
if (typeof param === "object" && "enabled" in param)
|
||||
enabled = param["enabled"]
|
||||
|
||||
const tags = this.properties.targetTags.split(",").map(s => s.trim());
|
||||
|
||||
for (const node of this.graph._nodes) {
|
||||
if ("tags" in node.properties) {
|
||||
const comfyNode = node as ComfyGraphNode;
|
||||
const hasTag = tags.some(t => comfyNode.properties.tags.indexOf(t) != -1);
|
||||
if (hasTag) {
|
||||
let newMode: NodeMode;
|
||||
if (enabled) {
|
||||
newMode = NodeMode.ALWAYS;
|
||||
} else {
|
||||
newMode = NodeMode.NEVER;
|
||||
}
|
||||
console.warn("CHANGEMODE", newMode == NodeMode.ALWAYS ? "ALWAYS" : "NEVER", tags, node)
|
||||
node.changeMode(newMode);
|
||||
if ("notifyPropsChanged" in node)
|
||||
(node as ComfyWidgetNode).notifyPropsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const entry of Object.values(get(layoutState).allItems)) {
|
||||
if (entry.dragItem.type === "container") {
|
||||
const container = entry.dragItem;
|
||||
const hasTag = tags.some(t => container.attrs.tags.indexOf(t) != -1);
|
||||
if (hasTag) {
|
||||
container.attrs.hidden = !enabled;
|
||||
}
|
||||
container.attrsChanged.set(get(container.attrsChanged) + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfySetNodeModeAction,
|
||||
title: "Comfy.SetNodeModeAction",
|
||||
desc: "Sets a group of nodes/UI containers as enabled/disabled based on their tags (comma-separated)",
|
||||
type: "actions/set_node_mode"
|
||||
})
|
||||
|
||||
@@ -2,7 +2,9 @@ import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
import ComfyWidgets from "$lib/widgets"
|
||||
import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
|
||||
import type { SerializedLGraphNode } from "@litegraph-ts/core";
|
||||
import { BuiltInSlotType, type SerializedLGraphNode } from "@litegraph-ts/core";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import type { ComfyInputConfig } from "$lib/IComfyInputSlot";
|
||||
|
||||
/*
|
||||
* Base class for any node with configuration sent by the backend.
|
||||
@@ -26,15 +28,11 @@ export class ComfyBackendNode extends ComfyGraphNode {
|
||||
// It just returns a hash like { "ui": { "images": results } } internally.
|
||||
// So this will need to be hardcoded for now.
|
||||
if (["PreviewImage", "SaveImage"].indexOf(comfyClass) !== -1) {
|
||||
this.addOutput("output", "IMAGE");
|
||||
this.addOutput("onExecuted", BuiltInSlotType.EVENT, { color_off: "rebeccapurple", color_on: "rebeccapurple" });
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Tags this node belongs to
|
||||
* Allows you to run subsections of the graph
|
||||
*/
|
||||
tags: string[] = []
|
||||
private static defaultInputConfigs: Record<string, Record<string, ComfyInputConfig>> = {}
|
||||
|
||||
private setup(nodeData: any) {
|
||||
var inputs = nodeData["input"]["required"];
|
||||
@@ -42,8 +40,11 @@ export class ComfyBackendNode extends ComfyGraphNode {
|
||||
inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"])
|
||||
}
|
||||
|
||||
const config = { minWidth: 1, minHeight: 1 };
|
||||
ComfyBackendNode.defaultInputConfigs[this.type] = {}
|
||||
|
||||
for (const inputName in inputs) {
|
||||
const config = { minWidth: 1, minHeight: 1 };
|
||||
|
||||
const inputData = inputs[inputName];
|
||||
const type = inputData[0];
|
||||
|
||||
@@ -64,6 +65,9 @@ export class ComfyBackendNode extends ComfyGraphNode {
|
||||
this.addInput(inputName, type);
|
||||
}
|
||||
}
|
||||
|
||||
if ("widgetNodeType" in config)
|
||||
ComfyBackendNode.defaultInputConfigs[this.type][config.name] = config.config
|
||||
}
|
||||
|
||||
for (const o in nodeData["output"]) {
|
||||
@@ -72,39 +76,42 @@ export class ComfyBackendNode extends ComfyGraphNode {
|
||||
this.addOutput(outputName, output);
|
||||
}
|
||||
|
||||
const s = this.computeSize();
|
||||
s[0] = Math.max(config.minWidth, s[0] * 1.5);
|
||||
s[1] = Math.max(config.minHeight, s[1]);
|
||||
this.size = s;
|
||||
this.serialize_widgets = false;
|
||||
|
||||
// app.#invokeExtensionsAsync("nodeCreated", this);
|
||||
}
|
||||
|
||||
override onSerialize(o: SerializedLGraphNode) {
|
||||
super.onSerialize(o);
|
||||
(o as any).tags = this.tags
|
||||
for (const input of o.inputs) {
|
||||
// strip user-identifying data, it will be reinstantiated later
|
||||
if ((input as any).config != null) {
|
||||
(input as any).config = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override onConfigure(o: SerializedLGraphNode) {
|
||||
super.onConfigure(o);
|
||||
this.tags = (o as any).tags || []
|
||||
|
||||
const configs = ComfyBackendNode.defaultInputConfigs[o.type]
|
||||
for (let index = 0; index < this.inputs.length; index++) {
|
||||
const input = this.inputs[index] as IComfyInputSlot
|
||||
const config = configs[input.name]
|
||||
if (config != null && index >= 0 && index < this.inputs.length) {
|
||||
if (input.config == null || Object.keys(input.config).length !== Object.keys(config).length) {
|
||||
console.debug("[ComfyBackendNode] restore input config", input, config)
|
||||
input.config = config
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.debug("[ComfyBackendNode] Missing input config in onConfigure()", input, configs)
|
||||
input.config = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override onExecuted(outputData: any) {
|
||||
console.warn("onExecuted outputs", outputData)
|
||||
for (let index = 0; index < this.outputs.length; index++) {
|
||||
const output = this.outputs[index]
|
||||
if (output.type === "IMAGE") {
|
||||
this.setOutputData(index, outputData)
|
||||
for (const node of this.getOutputNodes(index)) {
|
||||
console.warn(node)
|
||||
if ("receiveOutput" in node) {
|
||||
const widgetNode = node as ComfyGraphNode;
|
||||
widgetNode.receiveOutput(outputData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.triggerSlot(0, outputData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComfyInputConfig } from "$lib/IComfyInputSlot";
|
||||
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
||||
import type ComfyWidget from "$lib/components/widgets/ComfyWidget";
|
||||
import { LGraph, LGraphNode, LiteGraph, type SerializedLGraphNode, type Vector2 } from "@litegraph-ts/core";
|
||||
import { LGraph, LGraphNode, LLink, LiteGraph, NodeMode, type INodeInputSlot, type SerializedLGraphNode, type Vector2, type INodeOutputSlot, LConnectionKind, type SlotType, LGraphCanvas, getStaticPropertyOnInstance, type PropertyLayout, type SlotLayout } from "@litegraph-ts/core";
|
||||
import type { SvelteComponentDev } from "svelte/internal";
|
||||
import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
@@ -17,7 +17,15 @@ export type DefaultWidgetLayout = {
|
||||
inputs?: Record<number, DefaultWidgetSpec>,
|
||||
}
|
||||
|
||||
export interface ComfyGraphNodeProperties extends Record<string, any> {
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export default class ComfyGraphNode extends LGraphNode {
|
||||
override properties: ComfyGraphNodeProperties = {
|
||||
tags: []
|
||||
}
|
||||
|
||||
isBackendNode?: boolean;
|
||||
|
||||
beforeQueued?(subgraph: string | null): void;
|
||||
@@ -26,8 +34,198 @@ export default class ComfyGraphNode extends LGraphNode {
|
||||
|
||||
defaultWidgets?: DefaultWidgetLayout
|
||||
|
||||
/** Called when a backend node sends a ComfyUI output over a link */
|
||||
receiveOutput(output: any) {
|
||||
/*
|
||||
* If true, attempt to reconcile wildcard types in slots ("*")
|
||||
* when a new input/output is connected
|
||||
*
|
||||
* Only set this to true if all output slots are wildcard typed in the
|
||||
* static slotLayout property by default!
|
||||
*/
|
||||
canInheritSlotTypes: boolean = false;
|
||||
|
||||
/*
|
||||
* If false, don't serialize user-set properties into the workflow.
|
||||
* Useful for removing personal information from shared workflows.
|
||||
*/
|
||||
saveUserState: boolean = true;
|
||||
|
||||
/*
|
||||
* Called to remove user-set properties from this node.
|
||||
*/
|
||||
stripUserState(o: SerializedLGraphNode) {
|
||||
o.widgets_values = []
|
||||
}
|
||||
|
||||
/*
|
||||
* Traverses this node backwards in the graph in order to reach a connecting
|
||||
* backend node, if any. For example, reroute nodes will simply follow their
|
||||
* single input, while branching nodes have conditional logic that
|
||||
* determines which link to follow backwards.
|
||||
*/
|
||||
getUpstreamLink(): LLink | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title)
|
||||
this.addProperty("tags", [], "array")
|
||||
}
|
||||
|
||||
private inheritSlotTypes(type: LConnectionKind, isConnected: boolean) {
|
||||
// Prevent multiple connections to different types when we have no input
|
||||
if (isConnected && type === LConnectionKind.OUTPUT) {
|
||||
// Ignore wildcard nodes as these will be updated to real types
|
||||
const types = new Set(this.outputs.flatMap(o => o.links.map((l) => this.graph.links[l].type).filter((t) => t !== "*")));
|
||||
if (types.size > 1) {
|
||||
for (let j = 0; j < this.outputs.length; j++) {
|
||||
for (let i = 0; i < this.outputs[j].links.length - 1; i++) {
|
||||
const linkId = this.outputs[j].links[i];
|
||||
const link = this.graph.links[linkId];
|
||||
const node = this.graph.getNodeById(link.target_id);
|
||||
node.disconnectInput(link.target_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find root input
|
||||
let currentNode: ComfyGraphNode = this;
|
||||
let updateNodes: ComfyGraphNode[] = [];
|
||||
let inputType: SlotType | null = null;
|
||||
let inputNode = null;
|
||||
|
||||
while (currentNode) {
|
||||
updateNodes.unshift(currentNode);
|
||||
const link = currentNode.getUpstreamLink();
|
||||
if (link !== null) {
|
||||
const node = this.graph.getNodeById(link.origin_id) as ComfyGraphNode;
|
||||
console.warn(node.type)
|
||||
if (node.canInheritSlotTypes) {
|
||||
console.log("REROUTE2", node)
|
||||
if (node === this) {
|
||||
// We've found a circle
|
||||
currentNode.disconnectInput(link.target_slot);
|
||||
currentNode = null;
|
||||
}
|
||||
else {
|
||||
// Move the previous node
|
||||
currentNode = node;
|
||||
}
|
||||
} else {
|
||||
// We've found the end
|
||||
inputNode = currentNode;
|
||||
inputType = node.outputs[link.origin_slot]?.type ?? null;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// This path has no input node
|
||||
currentNode = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all outputs
|
||||
const nodes: ComfyGraphNode[] = [this];
|
||||
let outputType: SlotType | null = null;
|
||||
while (nodes.length) {
|
||||
currentNode = nodes.pop();
|
||||
if (currentNode.outputs) {
|
||||
for (let i = 0; i < currentNode.outputs.length; i++) {
|
||||
const outputs = currentNode.outputs[i].links || [];
|
||||
if (outputs.length) {
|
||||
for (const linkId of outputs) {
|
||||
const link = this.graph.links[linkId];
|
||||
|
||||
// When disconnecting sometimes the link is still registered
|
||||
if (!link) continue;
|
||||
|
||||
const node = this.graph.getNodeById(link.target_id) as ComfyGraphNode;
|
||||
|
||||
if (node.canInheritSlotTypes) {
|
||||
console.log("REROUTE", node)
|
||||
// Follow reroute nodes
|
||||
nodes.push(node);
|
||||
updateNodes.push(node);
|
||||
} else {
|
||||
// We've found an output
|
||||
const nodeOutType = node.inputs && node.inputs[link?.target_slot] && node.inputs[link.target_slot].type ? node.inputs[link.target_slot].type : null;
|
||||
if (inputType && nodeOutType !== inputType) {
|
||||
// The output doesnt match our input so disconnect it
|
||||
node.disconnectInput(link.target_slot);
|
||||
} else {
|
||||
outputType = nodeOutType;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No more outputs for this path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const displayType = inputType || outputType || "*";
|
||||
const color = LGraphCanvas.DEFAULT_LINK_TYPE_COLORS[displayType];
|
||||
|
||||
// Update the types of each node
|
||||
for (const node of updateNodes) {
|
||||
// in lieu of static abstract properties
|
||||
const slotLayout = getStaticPropertyOnInstance<SlotLayout>(node, "slotLayout");
|
||||
if (!slotLayout)
|
||||
continue
|
||||
|
||||
const layoutOutputs = slotLayout.outputs || []
|
||||
|
||||
for (let i = 0; i < node.outputs.length; i++) {
|
||||
// Check if this output was defined as starting off as a
|
||||
// wildcard. If for example it was something else like a string,
|
||||
// it wouldn't make sense to change its type dynamically.
|
||||
const isWildcardOutput = layoutOutputs.length > i && layoutOutputs[i].type === "*";
|
||||
if (!isWildcardOutput) {
|
||||
console.error("not wildcard", node.outputs[i], layoutOutputs[i])
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we dont have an input type we are always wildcard but we'll show the output type
|
||||
// This lets you change the output link to a different type and all nodes will update
|
||||
node.outputs[i].type = inputType || "*";
|
||||
(node as any).__outputType = displayType;
|
||||
node.outputs[i].name = node.properties.showOutputText ? String(displayType) : "";
|
||||
node.size = node.computeSize();
|
||||
|
||||
// TODO from ComfyReroute
|
||||
if ("applyOrientation" in node && typeof node.applyOrientation === "function")
|
||||
node.applyOrientation();
|
||||
|
||||
for (const l of node.outputs[i].links || []) {
|
||||
const link = this.graph.links[l];
|
||||
if (link) {
|
||||
link.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputNode) {
|
||||
for (let i = 0; i < inputNode.inputs.length; i++) {
|
||||
const link = this.graph.links[inputNode.inputs[i].link];
|
||||
if (link) {
|
||||
link.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override onConnectionsChange(
|
||||
type: LConnectionKind,
|
||||
slotIndex: number,
|
||||
isConnected: boolean,
|
||||
link: LLink,
|
||||
ioSlot: (INodeInputSlot | INodeOutputSlot)
|
||||
) {
|
||||
if (this.canInheritSlotTypes) {
|
||||
this.inheritSlotTypes(type, isConnected);
|
||||
}
|
||||
}
|
||||
|
||||
override onResize(size: Vector2) {
|
||||
@@ -56,6 +254,11 @@ export default class ComfyGraphNode extends LGraphNode {
|
||||
(serInput as any).defaultWidgetNode = null
|
||||
}
|
||||
}
|
||||
(o as any).saveUserState = this.saveUserState
|
||||
if (!this.saveUserState) {
|
||||
this.stripUserState(o)
|
||||
console.warn("[ComfyGraphNode] stripUserState", this, o)
|
||||
}
|
||||
}
|
||||
|
||||
override onConfigure(o: SerializedLGraphNode) {
|
||||
@@ -71,5 +274,9 @@ export default class ComfyGraphNode extends LGraphNode {
|
||||
comfyInput.defaultWidgetNode = widgetNode.class as any
|
||||
}
|
||||
}
|
||||
|
||||
this.saveUserState = (o as any).saveUserState;
|
||||
if (this.saveUserState == null)
|
||||
this.saveUserState = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout, clamp, type PropertyLayout, type IComboWidget } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||
import type { GalleryOutput } from "./ComfyWidgetNodes";
|
||||
|
||||
export interface ComfyImageCacheNodeProperties extends Record<any, any> {
|
||||
export interface ComfyImageCacheNodeProperties extends ComfyGraphNodeProperties {
|
||||
images: GalleryOutput | null,
|
||||
index: number,
|
||||
filenames: Record<number, { filename: string | null, status: ImageCacheState }>,
|
||||
@@ -18,6 +18,7 @@ type ImageCacheState = "none" | "uploading" | "failed" | "cached"
|
||||
*/
|
||||
export default class ComfyImageCacheNode extends ComfyGraphNode {
|
||||
override properties: ComfyImageCacheNodeProperties = {
|
||||
tags: [],
|
||||
images: null,
|
||||
index: 0,
|
||||
filenames: {},
|
||||
@@ -29,7 +30,7 @@ export default class ComfyImageCacheNode extends ComfyGraphNode {
|
||||
inputs: [
|
||||
{ name: "images", type: "OUTPUT" },
|
||||
{ name: "index", type: "number" },
|
||||
{ name: "store", type: BuiltInSlotType.ACTION },
|
||||
{ name: "store", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } },
|
||||
{ name: "clear", type: BuiltInSlotType.ACTION }
|
||||
],
|
||||
outputs: [
|
||||
@@ -158,7 +159,7 @@ export default class ComfyImageCacheNode extends ComfyGraphNode {
|
||||
else {
|
||||
this.properties.filenames[newIndex] = { filename: null, status: "uploading" }
|
||||
this.onPropertyChanged("filenames", this.properties.filenames)
|
||||
const url = "http://localhost:8188" // TODO make configurable
|
||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
||||
const params = new URLSearchParams(data)
|
||||
|
||||
const promise = fetch(url + "/view?" + params)
|
||||
@@ -204,7 +205,7 @@ export default class ComfyImageCacheNode extends ComfyGraphNode {
|
||||
}
|
||||
}
|
||||
|
||||
override onAction(action: any) {
|
||||
override onAction(action: any, param: any) {
|
||||
if (action === "clear") {
|
||||
this.setProperty("images", null)
|
||||
this.setProperty("filenames", {})
|
||||
@@ -213,12 +214,10 @@ export default class ComfyImageCacheNode extends ComfyGraphNode {
|
||||
return
|
||||
}
|
||||
|
||||
const link = this.getInputLink(0)
|
||||
|
||||
if (link.data && "images" in link.data) {
|
||||
if (param && "images" in param) {
|
||||
this.setProperty("genNumber", this.properties.genNumber + 1)
|
||||
|
||||
const output = link.data as GalleryOutput;
|
||||
const output = param as GalleryOutput;
|
||||
|
||||
if (this.properties.updateMode === "append" && this.properties.images != null) {
|
||||
const newImages = this.properties.images.images.concat(output.images)
|
||||
@@ -226,7 +225,7 @@ export default class ComfyImageCacheNode extends ComfyGraphNode {
|
||||
this.setProperty("images", this.properties.images)
|
||||
}
|
||||
else {
|
||||
this.setProperty("images", link.data as GalleryOutput)
|
||||
this.setProperty("images", param as GalleryOutput)
|
||||
this.setProperty("filenames", {})
|
||||
}
|
||||
|
||||
|
||||
161
src/lib/nodes/ComfyPickFirstNode.ts
Normal file
161
src/lib/nodes/ComfyPickFirstNode.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { BuiltInSlotType, LiteGraph, NodeMode, type INodeInputSlot, type SlotLayout, type INodeOutputSlot, LLink, LConnectionKind, type ITextWidget } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||
|
||||
export interface ComfyPickFirstNodeProperties extends ComfyGraphNodeProperties {
|
||||
acceptNullLinkData: boolean
|
||||
}
|
||||
|
||||
function nextLetter(s: string): string {
|
||||
return s.replace(/([a-zA-Z])[^a-zA-Z]*$/, function(a) {
|
||||
var c = a.charCodeAt(0);
|
||||
switch (c) {
|
||||
case 90: return 'A';
|
||||
case 122: return 'a';
|
||||
default: return String.fromCharCode(++c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default class ComfyPickFirstNode extends ComfyGraphNode {
|
||||
override properties: ComfyPickFirstNodeProperties = {
|
||||
tags: [],
|
||||
acceptNullLinkData: false
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
{ name: "A", type: "*" },
|
||||
{ name: "B", type: "*" },
|
||||
],
|
||||
outputs: [
|
||||
{ name: "out", type: "*" }
|
||||
],
|
||||
}
|
||||
|
||||
override canInheritSlotTypes = true;
|
||||
|
||||
private selected: number = -1;
|
||||
|
||||
displayWidget: ITextWidget;
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
this.displayWidget = this.addWidget("text", "Value", "")
|
||||
this.displayWidget.disabled = true;
|
||||
}
|
||||
|
||||
override onDrawBackground(ctx: CanvasRenderingContext2D) {
|
||||
if (this.flags.collapsed || this.selected === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.fillStyle = "#AFB";
|
||||
var y = (this.selected) * LiteGraph.NODE_SLOT_HEIGHT + 6;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(50, y);
|
||||
ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT);
|
||||
ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5);
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
override onConnectionsChange(
|
||||
type: LConnectionKind,
|
||||
slotIndex: number,
|
||||
isConnected: boolean,
|
||||
link: LLink,
|
||||
ioSlot: (INodeInputSlot | INodeOutputSlot)
|
||||
) {
|
||||
super.onConnectionsChange(type, slotIndex, isConnected, link, ioSlot);
|
||||
|
||||
if (type !== LConnectionKind.INPUT)
|
||||
return;
|
||||
|
||||
if (isConnected) {
|
||||
if (link != null && slotIndex === this.inputs.length - 1) {
|
||||
// Add a new input
|
||||
const lastInputName = this.inputs[this.inputs.length - 1].name
|
||||
const inputName = nextLetter(lastInputName);
|
||||
this.addInput(inputName, this.inputs[0].type)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.getInputLink(this.inputs.length - 1) != null)
|
||||
return;
|
||||
|
||||
// Remove empty inputs
|
||||
for (let i = this.inputs.length - 2; i > 0; i--) {
|
||||
if (i <= 0)
|
||||
break;
|
||||
|
||||
if (this.getInputLink(i) == null)
|
||||
this.removeInput(i)
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
let name = "A"
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
this.inputs[i].name = name;
|
||||
name = nextLetter(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isValidLink(link: LLink | null): boolean {
|
||||
if (!link)
|
||||
return false;
|
||||
|
||||
const node = this.graph.getNodeById(link.origin_id);
|
||||
|
||||
// Links to deactivated nodes won't count.
|
||||
if (!node || node.mode !== NodeMode.ALWAYS)
|
||||
return false;
|
||||
|
||||
if ((node as ComfyGraphNode).isBackendNode) {
|
||||
// Backend nodes won't set data, we can safely assume they're valid.
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return link.data != null || this.properties.acceptNullLinkData;
|
||||
}
|
||||
}
|
||||
|
||||
override getUpstreamLink(): LLink | null {
|
||||
for (let index = 0; index < this.inputs.length; index++) {
|
||||
const link = this.getInputLink(index);
|
||||
if (this.isValidLink(link)) {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
override onExecute() {
|
||||
for (let index = 0; index < this.inputs.length; index++) {
|
||||
const link = this.getInputLink(index);
|
||||
if (this.isValidLink(link)) {
|
||||
// Copy frontend-only inputs.
|
||||
const node = this.getInputNode(index);
|
||||
if (node != null) {
|
||||
this.selected = index;
|
||||
if (!(node as any).isBackendNode) {
|
||||
this.displayWidget.value = Watch.toString(link.data)
|
||||
this.setOutputData(0, link.data)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.selected = -1;
|
||||
this.setOutputData(0, null)
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfyPickFirstNode,
|
||||
title: "Comfy.PickFirst",
|
||||
desc: "Picks the first active input connected to this node (top to bottom)",
|
||||
type: "utils/pick_first"
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, NodeMode } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||
|
||||
export interface ComfyRerouteProperties extends Record<any, any> {
|
||||
export interface ComfyRerouteProperties extends ComfyGraphNodeProperties {
|
||||
showOutputText: boolean;
|
||||
horizontal: boolean;
|
||||
}
|
||||
@@ -22,6 +22,7 @@ export default class ComfyReroute extends ComfyGraphNode {
|
||||
override collapsable: boolean = false;
|
||||
|
||||
override properties: ComfyRerouteProperties = {
|
||||
tags: [],
|
||||
showOutputText: ComfyReroute.defaultVisibility,
|
||||
horizontal: false
|
||||
}
|
||||
@@ -47,124 +48,20 @@ export default class ComfyReroute extends ComfyGraphNode {
|
||||
}
|
||||
}
|
||||
|
||||
override onConnectionsChange(type: LConnectionKind, slotIndex: number, isConnected: boolean, _link: LLink) {
|
||||
override getUpstreamLink(): LLink | null {
|
||||
const link = this.getInputLink(0)
|
||||
const node = this.getInputNode(0)
|
||||
if (link && node && node.mode === NodeMode.ALWAYS)
|
||||
return link;
|
||||
return null;
|
||||
}
|
||||
|
||||
override canInheritSlotTypes = true;
|
||||
|
||||
override onConnectionsChange(type: LConnectionKind, slotIndex: number, isConnected: boolean, link: LLink, ioSlot: (INodeInputSlot | INodeOutputSlot)) {
|
||||
this.applyOrientation();
|
||||
|
||||
// Prevent multiple connections to different types when we have no input
|
||||
if (isConnected && type === LConnectionKind.OUTPUT) {
|
||||
// Ignore wildcard nodes as these will be updated to real types
|
||||
const types = new Set(this.outputs[0].links.map((l) => this.graph.links[l].type).filter((t) => t !== "*"));
|
||||
if (types.size > 1) {
|
||||
for (let i = 0; i < this.outputs[0].links.length - 1; i++) {
|
||||
const linkId = this.outputs[0].links[i];
|
||||
const link = this.graph.links[linkId];
|
||||
const node = this.graph.getNodeById(link.target_id);
|
||||
node.disconnectInput(link.target_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find root input
|
||||
let currentNode: ComfyReroute = this;
|
||||
let updateNodes: ComfyReroute[] = [];
|
||||
let inputType: SlotType | null = null;
|
||||
let inputNode = null;
|
||||
while (currentNode) {
|
||||
updateNodes.unshift(currentNode);
|
||||
const linkId = currentNode.inputs[0].link;
|
||||
if (linkId !== null) {
|
||||
const link = this.graph.links[linkId];
|
||||
const node = this.graph.getNodeById(link.origin_id);
|
||||
console.warn(node.type)
|
||||
if (node.class === ComfyReroute) {
|
||||
console.log("REROUTE2")
|
||||
if (node === this) {
|
||||
// We've found a circle
|
||||
currentNode.disconnectInput(link.target_slot);
|
||||
currentNode = null;
|
||||
}
|
||||
else {
|
||||
// Move the previous node
|
||||
currentNode = node as ComfyReroute;
|
||||
}
|
||||
} else {
|
||||
// We've found the end
|
||||
inputNode = currentNode;
|
||||
inputType = node.outputs[link.origin_slot]?.type ?? null;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// This path has no input node
|
||||
currentNode = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all outputs
|
||||
const nodes: ComfyReroute[] = [this];
|
||||
let outputType: SlotType | null = null;
|
||||
while (nodes.length) {
|
||||
currentNode = nodes.pop();
|
||||
const outputs = (currentNode.outputs ? currentNode.outputs[0].links : []) || [];
|
||||
if (outputs.length) {
|
||||
for (const linkId of outputs) {
|
||||
const link = this.graph.links[linkId];
|
||||
|
||||
// When disconnecting sometimes the link is still registered
|
||||
if (!link) continue;
|
||||
|
||||
const node = this.graph.getNodeById(link.target_id);
|
||||
|
||||
if (node.class === ComfyReroute) {
|
||||
console.log("REROUTE")
|
||||
// Follow reroute nodes
|
||||
nodes.push(node as ComfyReroute);
|
||||
updateNodes.push(node as ComfyReroute);
|
||||
} else {
|
||||
// We've found an output
|
||||
const nodeOutType = node.inputs && node.inputs[link?.target_slot] && node.inputs[link.target_slot].type ? node.inputs[link.target_slot].type : null;
|
||||
if (inputType && nodeOutType !== inputType) {
|
||||
// The output doesnt match our input so disconnect it
|
||||
node.disconnectInput(link.target_slot);
|
||||
} else {
|
||||
outputType = nodeOutType;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No more outputs for this path
|
||||
}
|
||||
}
|
||||
|
||||
const displayType = inputType || outputType || "*";
|
||||
const color = LGraphCanvas.link_type_colors[displayType];
|
||||
|
||||
// Update the types of each node
|
||||
for (const node of updateNodes) {
|
||||
// If we dont have an input type we are always wildcard but we'll show the output type
|
||||
// This lets you change the output link to a different type and all nodes will update
|
||||
node.outputs[0].type = inputType || "*";
|
||||
(node as any).__outputType = displayType;
|
||||
node.outputs[0].name = node.properties.showOutputText ? String(displayType) : "";
|
||||
node.size = node.computeSize();
|
||||
|
||||
if ("applyOrientation" in node && typeof node.applyOrientation === "function")
|
||||
node.applyOrientation();
|
||||
|
||||
for (const l of node.outputs[0].links || []) {
|
||||
const link = this.graph.links[l];
|
||||
if (link) {
|
||||
link.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputNode) {
|
||||
const link = this.graph.links[inputNode.inputs[0].link];
|
||||
if (link) {
|
||||
link.color = color;
|
||||
}
|
||||
}
|
||||
super.onConnectionsChange(type, slotIndex, isConnected, link, ioSlot);
|
||||
};
|
||||
|
||||
override clone(): LGraphNode {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
import { BuiltInSlotType, LConnectionKind, LLink, LiteGraph, NodeMode, type INodeInputSlot, type SlotLayout, type INodeOutputSlot } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||
|
||||
export interface ComfySelectorProperties extends Record<any, any> {
|
||||
export interface ComfySelectorProperties extends ComfyGraphNodeProperties {
|
||||
value: any
|
||||
}
|
||||
|
||||
export default class ComfySelector extends ComfyGraphNode {
|
||||
override properties: ComfySelectorProperties = {
|
||||
tags: [],
|
||||
value: null
|
||||
}
|
||||
|
||||
@@ -23,12 +24,29 @@ export default class ComfySelector extends ComfyGraphNode {
|
||||
],
|
||||
}
|
||||
|
||||
override canInheritSlotTypes = true;
|
||||
|
||||
private selected: number = 0;
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
}
|
||||
|
||||
override getUpstreamLink(): LLink | null {
|
||||
var sel = this.getInputData(0);
|
||||
if (sel == null || sel.constructor !== Number)
|
||||
sel = 0;
|
||||
|
||||
this.selected = sel = Math.round(sel) % (this.inputs.length - 1);
|
||||
|
||||
var link = this.getInputLink(sel + 1);
|
||||
var node = this.getInputNode(sel + 1);
|
||||
if (link != null && node != null && node.mode === NodeMode.ALWAYS)
|
||||
return link;
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override onDrawBackground(ctx: CanvasRenderingContext2D) {
|
||||
if (this.flags.collapsed) {
|
||||
return;
|
||||
@@ -61,12 +79,13 @@ LiteGraph.registerNodeType({
|
||||
type: "utils/selector"
|
||||
})
|
||||
|
||||
export interface ComfySelectorTwoProperties extends Record<any, any> {
|
||||
export interface ComfySelectorTwoProperties extends ComfyGraphNodeProperties {
|
||||
value: any
|
||||
}
|
||||
|
||||
export class ComfySelectorTwo extends ComfyGraphNode {
|
||||
override properties: ComfySelectorTwoProperties = {
|
||||
tags: [],
|
||||
value: null
|
||||
}
|
||||
|
||||
@@ -81,12 +100,41 @@ export class ComfySelectorTwo extends ComfyGraphNode {
|
||||
],
|
||||
}
|
||||
|
||||
override canInheritSlotTypes = true;
|
||||
|
||||
private selected: number = 0;
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
}
|
||||
|
||||
override getUpstreamLink(): LLink | null {
|
||||
var sel = this.getInputData(0);
|
||||
if (sel == null || sel.constructor !== Boolean)
|
||||
sel = 0;
|
||||
|
||||
this.selected = sel ? 0 : 1;
|
||||
|
||||
var link = this.getInputLink(this.selected + 1);
|
||||
var node = this.getInputNode(this.selected + 1);
|
||||
if (link != null && node != null && node.mode === NodeMode.ALWAYS)
|
||||
return link
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override onConnectionsChange(
|
||||
type: LConnectionKind,
|
||||
slotIndex: number,
|
||||
isConnected: boolean,
|
||||
link: LLink,
|
||||
ioSlot: (INodeInputSlot | INodeOutputSlot)
|
||||
) {
|
||||
if (type === LConnectionKind.INPUT) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override onDrawBackground(ctx: CanvasRenderingContext2D) {
|
||||
if (this.flags.collapsed) {
|
||||
return;
|
||||
@@ -107,6 +155,8 @@ export class ComfySelectorTwo extends ComfyGraphNode {
|
||||
this.selected = sel ? 0 : 1;
|
||||
var v = this.getInputData(this.selected + 1);
|
||||
if (v !== undefined) {
|
||||
const link = this.getInputLink(this.selected + 1);
|
||||
const node = this.getInputNode(this.selected + 1);
|
||||
this.setOutputData(0, v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode, { type DefaultWidgetLayout } from "./ComfyGraphNode";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties, type DefaultWidgetLayout } from "./ComfyGraphNode";
|
||||
import { clamp } from "$lib/utils";
|
||||
import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
||||
import { ComfyComboNode } from "./ComfyWidgetNodes";
|
||||
|
||||
export interface ComfyValueControlProperties extends Record<any, any> {
|
||||
export interface ComfyValueControlProperties extends ComfyGraphNodeProperties {
|
||||
value: any,
|
||||
action: "fixed" | "increment" | "decrement" | "randomize",
|
||||
min: number,
|
||||
@@ -16,6 +16,7 @@ const INT_MAX = 1125899906842624;
|
||||
|
||||
export default class ComfyValueControl extends ComfyGraphNode {
|
||||
override properties: ComfyValueControlProperties = {
|
||||
tags: [],
|
||||
value: null,
|
||||
action: "fixed",
|
||||
min: -INT_MAX,
|
||||
@@ -33,7 +34,8 @@ export default class ComfyValueControl extends ComfyGraphNode {
|
||||
{ name: "step", type: "number" }
|
||||
],
|
||||
outputs: [
|
||||
{ name: "value", type: "*" }
|
||||
{ name: "value", type: "*" },
|
||||
{ name: "changed", type: BuiltInSlotType.EVENT }
|
||||
],
|
||||
}
|
||||
|
||||
@@ -49,6 +51,11 @@ export default class ComfyValueControl extends ComfyGraphNode {
|
||||
}
|
||||
}
|
||||
|
||||
delayChangeEvent: boolean = true;
|
||||
|
||||
private _aboutToChange: number = 0;
|
||||
private _aboutToChangeValue: any = null;
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
}
|
||||
@@ -58,6 +65,16 @@ export default class ComfyValueControl extends ComfyGraphNode {
|
||||
this.setProperty("min", this.getInputData(3))
|
||||
this.setProperty("max", this.getInputData(4))
|
||||
this.setProperty("step", this.getInputData(5) || 1)
|
||||
|
||||
if (this._aboutToChange > 0) {
|
||||
this._aboutToChange -= 1;
|
||||
if (this._aboutToChange <= 0) {
|
||||
const value = this._aboutToChangeValue;
|
||||
this._aboutToChange = 0;
|
||||
this._aboutToChangeValue = null;
|
||||
this.triggerSlot(1, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override onAction(action: any, param: any) {
|
||||
@@ -95,6 +112,14 @@ export default class ComfyValueControl extends ComfyGraphNode {
|
||||
this.setProperty("value", v)
|
||||
this.setOutputData(0, v)
|
||||
|
||||
if (this.delayChangeEvent) {
|
||||
this._aboutToChange = 2;
|
||||
this._aboutToChangeValue = v;
|
||||
}
|
||||
else {
|
||||
this.triggerSlot(1, v)
|
||||
}
|
||||
|
||||
console.debug("ValueControl", v, this.properties)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,21 +1,39 @@
|
||||
import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget, type INodeOutputSlot, type SerializedLGraphNode, BuiltInSlotType, type PropertyLayout, type IComboWidget } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, LGraph, type INodeInputSlot, type ITextWidget, type INodeOutputSlot, type SerializedLGraphNode, BuiltInSlotType, type PropertyLayout, type IComboWidget, NodeMode, type INumberWidget } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
||||
import type { SvelteComponentDev } from "svelte/internal";
|
||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import { writable, type Unsubscriber, type Writable, get } from "svelte/store";
|
||||
import { clamp, convertComfyOutputToGradio, range } from "$lib/utils"
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
|
||||
import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
||||
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
||||
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
||||
import GalleryWidget from "$lib/widgets/GalleryWidget.svelte";
|
||||
import ButtonWidget from "$lib/widgets/ButtonWidget.svelte";
|
||||
import CheckboxWidget from "$lib/widgets/CheckboxWidget.svelte";
|
||||
import type { SvelteComponentDev } from "svelte/internal";
|
||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import { writable, type Unsubscriber, type Writable, get } from "svelte/store";
|
||||
import { clamp, range } from "$lib/utils"
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import RadioWidget from "$lib/widgets/RadioWidget.svelte";
|
||||
|
||||
export interface ComfyWidgetProperties extends Record<string, any> {
|
||||
/*
|
||||
* NOTE: If you want to add a new widget but it has the same input/output type
|
||||
* as another one of the existing widgets, best to create a new "variant" of
|
||||
* that widget instead.
|
||||
*
|
||||
* - Go to layoutState, look for `ALL_ATTRIBUTES,` insert or find a "variant"
|
||||
* attribute and set `validNodeTypes` to the type of the litegraph node
|
||||
* - Add a new entry in the `values` array, like "knob" or "dial" for ComfySliderWidget
|
||||
* - Add an {#if widget.attrs.variant === <...>} statement in the corresponding Svelte component
|
||||
*
|
||||
* Also, BEWARE of calling setOutputData() and triggerSlot() on the same frame!
|
||||
* You will have to either implement an internal delay on the event triggering
|
||||
* or use an Event Delay node to ensure the output slot data can propagate to
|
||||
* the rest of the graph first (see `delayChangedEvent` for details)
|
||||
*/
|
||||
|
||||
export interface ComfyWidgetProperties extends ComfyGraphNodeProperties {
|
||||
defaultValue: any
|
||||
}
|
||||
|
||||
@@ -39,6 +57,18 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
|
||||
copyFromInputLink: boolean = true;
|
||||
|
||||
/**
|
||||
* If true wait until next frame update to trigger the changed event.
|
||||
* Reason is, if the event is triggered immediately then other stuff that wants to run
|
||||
* their own onExecute on the output value won't have completed yet.
|
||||
*/
|
||||
delayChangedEvent: boolean = true;
|
||||
|
||||
private _aboutToChange: number = 0;
|
||||
private _aboutToChangeValue: any = null;
|
||||
|
||||
abstract defaultValue: T;
|
||||
|
||||
/** Names of properties to add as inputs */
|
||||
// shownInputProperties: string[] = []
|
||||
|
||||
@@ -91,6 +121,12 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
return Watch.toString(value)
|
||||
}
|
||||
|
||||
override changeMode(modeTo: NodeMode): boolean {
|
||||
const result = super.changeMode(modeTo);
|
||||
this.notifyPropsChanged();
|
||||
return result;
|
||||
}
|
||||
|
||||
private onValueUpdated(value: any) {
|
||||
console.debug("[Widget] valueUpdated", this, value)
|
||||
this.displayWidget.value = this.formatValue(value)
|
||||
@@ -98,13 +134,23 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
if (this.outputIndex !== null && this.outputs.length >= this.outputIndex) {
|
||||
this.setOutputData(this.outputIndex, get(this.value))
|
||||
}
|
||||
|
||||
if (this.changedIndex !== null && this.outputs.length >= this.changedIndex) {
|
||||
const changedOutput = this.outputs[this.changedIndex]
|
||||
if (changedOutput.type === BuiltInSlotType.EVENT)
|
||||
this.triggerSlot(this.changedIndex, "changed")
|
||||
if (!this.delayChangedEvent)
|
||||
this.triggerChangeEvent(get(this.value))
|
||||
else {
|
||||
this._aboutToChange = 2; // wait 1.5-2 frames, in case we're already in the middle of one
|
||||
this._aboutToChangeValue = get(this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private triggerChangeEvent(value: any) {
|
||||
const changedOutput = this.outputs[this.changedIndex]
|
||||
if (changedOutput.type === BuiltInSlotType.EVENT)
|
||||
this.triggerSlot(this.changedIndex, value)
|
||||
}
|
||||
|
||||
setValue(value: any) {
|
||||
this.value.set(value)
|
||||
}
|
||||
@@ -118,7 +164,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
/*
|
||||
* Logic to run if this widget can be treated as output (slider, combo, text)
|
||||
*/
|
||||
override onExecute() {
|
||||
override onExecute(param: any, options: object) {
|
||||
if (this.copyFromInputLink) {
|
||||
if (this.inputs.length >= this.inputIndex) {
|
||||
const data = this.getInputData(this.inputIndex)
|
||||
@@ -134,6 +180,18 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
const data = this.shownOutputProperties[propName]
|
||||
this.setOutputData(data.index, this.properties[propName])
|
||||
}
|
||||
|
||||
// Fire a pending change event after one full step of the graph has
|
||||
// finished processing
|
||||
if (this._aboutToChange > 0) {
|
||||
this._aboutToChange -= 1
|
||||
if (this._aboutToChange <= 0) {
|
||||
const value = this._aboutToChangeValue;
|
||||
this._aboutToChange = 0;
|
||||
this._aboutToChangeValue = null;
|
||||
this.triggerChangeEvent(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onConnectOutput(
|
||||
@@ -169,16 +227,27 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
console.debug("Property copy", input, this.properties)
|
||||
|
||||
this.setValue(get(this.value))
|
||||
this.propsChanged.set(get(this.propsChanged) + 1)
|
||||
this.notifyPropsChanged();
|
||||
}
|
||||
|
||||
onConnectionsChange(
|
||||
notifyPropsChanged() {
|
||||
const layoutEntry = layoutState.findLayoutEntryForNode(this.id)
|
||||
if (layoutEntry && layoutEntry.parent) {
|
||||
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
|
||||
}
|
||||
console.debug("propsChanged", this)
|
||||
this.propsChanged.set(get(this.propsChanged) + 1)
|
||||
|
||||
}
|
||||
|
||||
override onConnectionsChange(
|
||||
type: LConnectionKind,
|
||||
slotIndex: number,
|
||||
isConnected: boolean,
|
||||
link: LLink,
|
||||
ioSlot: (INodeOutputSlot | INodeInputSlot)
|
||||
): void {
|
||||
super.onConnectionsChange(type, slotIndex, isConnected, link, ioSlot);
|
||||
this.clampConfig();
|
||||
}
|
||||
|
||||
@@ -198,21 +267,27 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
}
|
||||
|
||||
// Force reactivity change so the frontend can be updated with the new props
|
||||
this.propsChanged.set(get(this.propsChanged) + 1)
|
||||
this.notifyPropsChanged();
|
||||
}
|
||||
|
||||
clampOneConfig(input: IComfyInputSlot) { }
|
||||
|
||||
override onSerialize(o: SerializedLGraphNode) {
|
||||
super.onSerialize(o);
|
||||
(o as any).comfyValue = get(this.value);
|
||||
(o as any).shownOutputProperties = this.shownOutputProperties
|
||||
super.onSerialize(o);
|
||||
}
|
||||
|
||||
override onConfigure(o: SerializedLGraphNode) {
|
||||
this.value.set((o as any).comfyValue);
|
||||
this.shownOutputProperties = (o as any).shownOutputProperties;
|
||||
}
|
||||
|
||||
override stripUserState(o: SerializedLGraphNode) {
|
||||
super.stripUserState(o);
|
||||
(o as any).comfyValue = this.defaultValue;
|
||||
o.properties.defaultValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ComfySliderProperties extends ComfyWidgetProperties {
|
||||
@@ -224,6 +299,7 @@ export interface ComfySliderProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfySliderNode extends ComfyWidgetNode<number> {
|
||||
override properties: ComfySliderProperties = {
|
||||
tags: [],
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
max: 10,
|
||||
@@ -232,6 +308,7 @@ export class ComfySliderNode extends ComfyWidgetNode<number> {
|
||||
}
|
||||
|
||||
override svelteComponentType = RangeWidget
|
||||
override defaultValue = 0;
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
@@ -287,6 +364,7 @@ export interface ComfyComboProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
override properties: ComfyComboProperties = {
|
||||
tags: [],
|
||||
defaultValue: "A",
|
||||
values: ["A", "B", "C", "D"]
|
||||
}
|
||||
@@ -303,9 +381,14 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
}
|
||||
|
||||
override svelteComponentType = ComboWidget
|
||||
override defaultValue = "A";
|
||||
override saveUserState = false;
|
||||
|
||||
comboRefreshed: Writable<boolean>;
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name, "A")
|
||||
this.comboRefreshed = writable(false)
|
||||
}
|
||||
|
||||
onConnectOutput(
|
||||
@@ -346,13 +429,20 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
}
|
||||
|
||||
override clampOneConfig(input: IComfyInputSlot) {
|
||||
if (input.config.values.indexOf(this.properties.value) === -1) {
|
||||
if (!input.config.values)
|
||||
this.setValue("")
|
||||
else if (input.config.values.indexOf(this.properties.value) === -1) {
|
||||
if (input.config.values.length === 0)
|
||||
this.setValue("")
|
||||
else
|
||||
this.setValue(input.config.defaultValue || input.config.values[0])
|
||||
}
|
||||
}
|
||||
|
||||
override stripUserState(o: SerializedLGraphNode) {
|
||||
super.stripUserState(o);
|
||||
o.properties.values = []
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
@@ -368,6 +458,7 @@ export interface ComfyTextProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfyTextNode extends ComfyWidgetNode<string> {
|
||||
override properties: ComfyTextProperties = {
|
||||
tags: [],
|
||||
defaultValue: "",
|
||||
multiline: false
|
||||
}
|
||||
@@ -384,6 +475,7 @@ export class ComfyTextNode extends ComfyWidgetNode<string> {
|
||||
}
|
||||
|
||||
override svelteComponentType = TextWidget
|
||||
override defaultValue = "";
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name, "")
|
||||
@@ -425,6 +517,7 @@ export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||
override properties: ComfyGalleryProperties = {
|
||||
tags: [],
|
||||
defaultValue: [],
|
||||
index: 0,
|
||||
updateMode: "replace"
|
||||
@@ -433,7 +526,7 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
{ name: "images", type: "OUTPUT" },
|
||||
{ name: "store", type: BuiltInSlotType.ACTION },
|
||||
{ name: "store", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } },
|
||||
{ name: "clear", type: BuiltInSlotType.ACTION }
|
||||
],
|
||||
outputs: [
|
||||
@@ -446,7 +539,9 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||
]
|
||||
|
||||
override svelteComponentType = GalleryWidget
|
||||
override defaultValue = []
|
||||
override copyFromInputLink = false;
|
||||
override saveUserState = false;
|
||||
override outputIndex = null;
|
||||
override changedIndex = null;
|
||||
|
||||
@@ -472,12 +567,11 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||
this.setValue([])
|
||||
}
|
||||
else if (action === "store") {
|
||||
const link = this.getInputLink(0)
|
||||
if (link.data && "images" in link.data) {
|
||||
const data = link.data as GalleryOutput
|
||||
if (param && "images" in param) {
|
||||
const data = param as GalleryOutput
|
||||
console.debug("[ComfyGalleryNode] Received output!", data)
|
||||
|
||||
const galleryItems: GradioFileData[] = this.convertItems(link.data)
|
||||
const galleryItems: GradioFileData[] = convertComfyOutputToGradio(data)
|
||||
|
||||
if (this.properties.updateMode === "append") {
|
||||
const currentValue = get(this.value)
|
||||
@@ -495,18 +589,6 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||
return `Images: ${value?.length || 0}`
|
||||
}
|
||||
|
||||
private convertItems(output: GalleryOutput): GradioFileData[] {
|
||||
return output.images.map(r => {
|
||||
// TODO configure backend URL
|
||||
const url = "http://localhost:8188/view?"
|
||||
const params = new URLSearchParams(r)
|
||||
return {
|
||||
name: null,
|
||||
data: url + params
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override setValue(value: any) {
|
||||
if (Array.isArray(value)) {
|
||||
super.setValue(value)
|
||||
@@ -535,6 +617,7 @@ export interface ComfyButtonProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfyButtonNode extends ComfyWidgetNode<boolean> {
|
||||
override properties: ComfyButtonProperties = {
|
||||
tags: [],
|
||||
defaultValue: false,
|
||||
param: "bang"
|
||||
}
|
||||
@@ -546,8 +629,13 @@ export class ComfyButtonNode extends ComfyWidgetNode<boolean> {
|
||||
]
|
||||
}
|
||||
|
||||
override outputIndex = 1;
|
||||
override svelteComponentType = ButtonWidget;
|
||||
override defaultValue = false;
|
||||
override outputIndex = 1;
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name, false)
|
||||
}
|
||||
|
||||
override setValue(value: any) {
|
||||
super.setValue(Boolean(value))
|
||||
@@ -558,10 +646,6 @@ export class ComfyButtonNode extends ComfyWidgetNode<boolean> {
|
||||
this.triggerSlot(0, this.properties.param);
|
||||
this.setValue(false) // TODO onRelease
|
||||
}
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name, false)
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
@@ -576,6 +660,7 @@ export interface ComfyCheckboxProperties extends ComfyWidgetProperties {
|
||||
|
||||
export class ComfyCheckboxNode extends ComfyWidgetNode<boolean> {
|
||||
override properties: ComfyCheckboxProperties = {
|
||||
tags: [],
|
||||
defaultValue: false,
|
||||
}
|
||||
|
||||
@@ -587,13 +672,14 @@ export class ComfyCheckboxNode extends ComfyWidgetNode<boolean> {
|
||||
}
|
||||
|
||||
override svelteComponentType = CheckboxWidget;
|
||||
override defaultValue = false;
|
||||
|
||||
override setValue(value: any) {
|
||||
value = Boolean(value)
|
||||
const changed = value != get(this.value);
|
||||
super.setValue(Boolean(value))
|
||||
if (changed)
|
||||
this.triggerSlot(1)
|
||||
this.triggerSlot(1, value)
|
||||
}
|
||||
|
||||
constructor(name?: string) {
|
||||
@@ -607,3 +693,61 @@ LiteGraph.registerNodeType({
|
||||
desc: "Checkbox that stores a boolean value",
|
||||
type: "ui/checkbox"
|
||||
})
|
||||
|
||||
export interface ComfyRadioProperties extends ComfyWidgetProperties {
|
||||
choices: string[]
|
||||
}
|
||||
|
||||
export class ComfyRadioNode extends ComfyWidgetNode<string> {
|
||||
override properties: ComfyRadioProperties = {
|
||||
tags: [],
|
||||
choices: ["Choice A", "Choice B", "Choice C"],
|
||||
defaultValue: "Choice A",
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
outputs: [
|
||||
{ name: "value", type: "string" },
|
||||
{ name: "index", type: "number" },
|
||||
{ name: "changed", type: BuiltInSlotType.EVENT },
|
||||
]
|
||||
}
|
||||
|
||||
override svelteComponentType = RadioWidget;
|
||||
override defaultValue = "";
|
||||
override changedIndex = 2;
|
||||
|
||||
indexWidget: INumberWidget;
|
||||
|
||||
index = 0;
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name, "Choice A")
|
||||
this.indexWidget = this.addWidget("number", "Index", this.index)
|
||||
this.indexWidget.disabled = true;
|
||||
}
|
||||
|
||||
override onExecute(param: any, options: object) {
|
||||
super.onExecute(param, options);
|
||||
this.setOutputData(1, this.index)
|
||||
}
|
||||
|
||||
override setValue(value: string) {
|
||||
const index = this.properties.choices.indexOf(value)
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
this.index = index;
|
||||
this.indexWidget.value = index;
|
||||
this.setOutputData(1, this.index)
|
||||
|
||||
super.setValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfyRadioNode,
|
||||
title: "UI.Radio",
|
||||
desc: "Radio that outputs a string and index",
|
||||
type: "ui/radio"
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export { default as ComfyReroute } from "./ComfyReroute"
|
||||
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
|
||||
export { ComfyQueueEvents, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyOnExecutedEvent, ComfyExecuteSubgraphAction } from "./ComfyActionNodes"
|
||||
export { ComfyQueueEvents, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyStoreImagesAction, ComfyExecuteSubgraphAction } from "./ComfyActionNodes"
|
||||
export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode"
|
||||
export { default as ComfyValueControl } from "./ComfyValueControl"
|
||||
export { default as ComfySelector } from "./ComfySelector"
|
||||
export { default as ComfyImageCacheNode } from "./ComfyImageCacheNode"
|
||||
|
||||
40
src/lib/notify.ts
Normal file
40
src/lib/notify.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { toast } from "@zerodevx/svelte-toast";
|
||||
import type { SvelteToastOptions } from "@zerodevx/svelte-toast/stores";
|
||||
import { f7 } from "framework7-svelte"
|
||||
|
||||
let notification;
|
||||
|
||||
function notifyf7(text: string, title?: string) {
|
||||
if (!f7)
|
||||
return;
|
||||
|
||||
if (!notification) {
|
||||
notification = f7.notification.create({
|
||||
title: title,
|
||||
titleRightText: 'now',
|
||||
// subtitle: 'Notification with close on click',
|
||||
text: text,
|
||||
closeOnClick: true,
|
||||
closeTimeout: 3000,
|
||||
});
|
||||
}
|
||||
// Open it
|
||||
notification.open();
|
||||
}
|
||||
|
||||
function notifyToast(text: string, type?: string) {
|
||||
const options: SvelteToastOptions = {}
|
||||
|
||||
if (type === "error") {
|
||||
options.theme = {
|
||||
'--toastBackground': 'var(--color-red-500)',
|
||||
}
|
||||
}
|
||||
|
||||
toast.push(text, options);
|
||||
}
|
||||
|
||||
export default function notify(text: string, title?: string, type?: string) {
|
||||
notifyf7(text, title);
|
||||
notifyToast(text, title);
|
||||
}
|
||||
48
src/lib/stores/interfaceState.ts
Normal file
48
src/lib/stores/interfaceState.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { debounce } from '$lib/utils';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
|
||||
export type InterfaceState = {
|
||||
// Show a large indicator of the currently editing number value for mobile
|
||||
// use (sliders).
|
||||
pointerNearTop: boolean,
|
||||
pointerNearLeft: boolean,
|
||||
showIndicator: boolean,
|
||||
indicatorValue: any,
|
||||
}
|
||||
|
||||
type InterfaceStateOps = {
|
||||
showIndicator: (pointerX: number, pointerY: number, value: any) => void,
|
||||
}
|
||||
|
||||
export type WritableInterfaceStateStore = Writable<InterfaceState> & InterfaceStateOps;
|
||||
const store: Writable<InterfaceState> = writable(
|
||||
{
|
||||
pointerNearTop: false,
|
||||
pointerNearLeft: false,
|
||||
showIndicator: false,
|
||||
indicatorValue: null,
|
||||
})
|
||||
|
||||
const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000)
|
||||
|
||||
function showIndicator(pointerX: number, pointerY: number, value: any) {
|
||||
if (!window)
|
||||
return;
|
||||
|
||||
const state = get(store)
|
||||
|
||||
let middleWidth = window.innerWidth / 2;
|
||||
let middleHeight = window.innerHeight / 2;
|
||||
const pointerNearLeft = pointerX < middleWidth;
|
||||
const pointerNearTop = pointerY < middleHeight;
|
||||
store.update(s => { return { ...s, pointerNearTop, pointerNearLeft, showIndicator: true, indicatorValue: value } });
|
||||
debounceDrag();
|
||||
}
|
||||
|
||||
const interfaceStateStore: WritableInterfaceStateStore =
|
||||
{
|
||||
...store,
|
||||
showIndicator
|
||||
}
|
||||
export default interfaceStateStore;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { get, writable } from 'svelte/store';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import type ComfyApp from "$lib/components/ComfyApp"
|
||||
import type { LGraphNode, IWidget, LGraph } from "@litegraph-ts/core"
|
||||
import { type LGraphNode, type IWidget, type LGraph, NodeMode } from "@litegraph-ts/core"
|
||||
import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||
import type { ComfyWidgetNode } from '$lib/nodes';
|
||||
|
||||
@@ -116,6 +116,12 @@ export type Attributes = {
|
||||
*/
|
||||
containerVariant?: "block" | "hidden",
|
||||
|
||||
/*
|
||||
* Tags for hiding containers with
|
||||
* For WidgetLayouts this will be ignored, it will use node.properties.tags instead
|
||||
*/
|
||||
tags: string[],
|
||||
|
||||
/*
|
||||
* If true, don't show this component in the UI
|
||||
*/
|
||||
@@ -142,6 +148,12 @@ export type Attributes = {
|
||||
*/
|
||||
variant?: string,
|
||||
|
||||
/*
|
||||
* What state to set this widget to in the frontend if its corresponding
|
||||
* node is disabled in the graph.
|
||||
*/
|
||||
nodeDisabledState: "visible" | "disabled" | "hidden",
|
||||
|
||||
/*********************************************/
|
||||
/* Special attributes for widgets/containers */
|
||||
/*********************************************/
|
||||
@@ -154,6 +166,9 @@ export type Attributes = {
|
||||
buttonSize?: "large" | "small"
|
||||
}
|
||||
|
||||
/*
|
||||
* Defines something that can be edited in the properties side panel.
|
||||
*/
|
||||
export type AttributesSpec = {
|
||||
/*
|
||||
* ID necessary for svelte's keyed each, autoset at the top level in this source file.
|
||||
@@ -256,9 +271,13 @@ export type AttributesCategorySpec = {
|
||||
|
||||
export type AttributesSpecList = AttributesCategorySpec[]
|
||||
|
||||
const serializeStringArray = (arg: string[]) => arg.join(",")
|
||||
const serializeStringArray = (arg: string[]) => {
|
||||
if (arg == null)
|
||||
arg = []
|
||||
return arg.join(",")
|
||||
}
|
||||
const deserializeStringArray = (arg: string) => {
|
||||
if (arg === "")
|
||||
if (arg === "" || arg == null)
|
||||
return []
|
||||
return arg.split(",").map(s => s.trim())
|
||||
}
|
||||
@@ -308,6 +327,13 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
||||
defaultValue: 100,
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
name: "height",
|
||||
type: "string",
|
||||
location: "widget",
|
||||
defaultValue: "auto",
|
||||
editable: true
|
||||
},
|
||||
{
|
||||
name: "classes",
|
||||
type: "string",
|
||||
@@ -315,6 +341,15 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
||||
defaultValue: "",
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
name: "nodeDisabledState",
|
||||
type: "enum",
|
||||
location: "widget",
|
||||
editable: true,
|
||||
values: ["visible", "disabled", "hidden"],
|
||||
defaultValue: "disabled",
|
||||
canShow: (di: IDragItem) => di.type === "widget"
|
||||
},
|
||||
|
||||
// Container variants
|
||||
{
|
||||
@@ -366,22 +401,65 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
||||
values: ["large", "small"],
|
||||
defaultValue: "large"
|
||||
},
|
||||
|
||||
// Gallery
|
||||
{
|
||||
name: "variant",
|
||||
type: "enum",
|
||||
location: "widget",
|
||||
editable: true,
|
||||
validNodeTypes: ["ui/gallery"],
|
||||
values: ["gallery", "image"],
|
||||
defaultValue: "gallery",
|
||||
refreshPanelOnChange: true
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
categoryName: "behavior",
|
||||
specs: [
|
||||
// Node variables
|
||||
{
|
||||
name: "saveUserState",
|
||||
type: "boolean",
|
||||
location: "nodeVars",
|
||||
editable: true,
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: "mode",
|
||||
type: "enum",
|
||||
location: "nodeVars",
|
||||
editable: true,
|
||||
values: ["ALWAYS", "NEVER"],
|
||||
defaultValue: "ALWAYS",
|
||||
serialize: (s) => s === NodeMode.ALWAYS ? "ALWAYS" : "NEVER",
|
||||
deserialize: (m) => m === "ALWAYS" ? NodeMode.ALWAYS : NodeMode.NEVER
|
||||
},
|
||||
|
||||
// Node properties
|
||||
{
|
||||
name: "tags",
|
||||
type: "string",
|
||||
location: "nodeVars",
|
||||
location: "nodeProps",
|
||||
editable: true,
|
||||
defaultValue: [],
|
||||
serialize: serializeStringArray,
|
||||
deserialize: deserializeStringArray
|
||||
},
|
||||
|
||||
// Container tags are contained in the widget attributes
|
||||
{
|
||||
name: "tags",
|
||||
type: "string",
|
||||
location: "widget",
|
||||
editable: true,
|
||||
defaultValue: [],
|
||||
serialize: serializeStringArray,
|
||||
deserialize: deserializeStringArray,
|
||||
canShow: (di: IDragItem) => di.type === "container"
|
||||
},
|
||||
|
||||
// Range
|
||||
{
|
||||
name: "min",
|
||||
@@ -424,7 +502,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
||||
defaultValue: "bang"
|
||||
},
|
||||
|
||||
// gallery
|
||||
// Gallery
|
||||
{
|
||||
name: "updateMode",
|
||||
type: "enum",
|
||||
@@ -435,6 +513,18 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
||||
defaultValue: "replace"
|
||||
},
|
||||
|
||||
// Radio
|
||||
{
|
||||
name: "choices",
|
||||
type: "string",
|
||||
location: "nodeProps",
|
||||
editable: true,
|
||||
validNodeTypes: ["ui/radio"],
|
||||
defaultValue: ["Choice A", "Choice B", "Choice C"],
|
||||
serialize: serializeStringArray,
|
||||
deserialize: deserializeStringArray,
|
||||
},
|
||||
|
||||
// Workflow
|
||||
{
|
||||
name: "defaultSubgraph",
|
||||
@@ -450,14 +540,23 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
||||
// This is needed so the specs can be iterated with svelte's keyed #each.
|
||||
let i = 0;
|
||||
for (const cat of Object.values(ALL_ATTRIBUTES)) {
|
||||
for (const val of Object.values(cat.specs)) {
|
||||
val.id = i;
|
||||
for (const spec of Object.values(cat.specs)) {
|
||||
spec.id = i;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
export { ALL_ATTRIBUTES };
|
||||
|
||||
const defaultWidgetAttributes: Attributes = {} as any
|
||||
for (const cat of Object.values(ALL_ATTRIBUTES)) {
|
||||
for (const spec of Object.values(cat.specs)) {
|
||||
if (spec.location === "widget" && spec.defaultValue != null) {
|
||||
defaultWidgetAttributes[spec.name] = spec.defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Something that can be dragged around in the frontend - a widget or a container.
|
||||
*/
|
||||
@@ -487,7 +586,7 @@ export interface IDragItem {
|
||||
* Hackish thing to indicate to Svelte that an attribute changed.
|
||||
* TODO Use Writeable<Attributes> instead!
|
||||
*/
|
||||
attrsChanged: Writable<boolean>
|
||||
attrsChanged: Writable<number>
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -521,7 +620,8 @@ type LayoutStateOps = {
|
||||
groupItems: (dragItems: IDragItem[], attrs?: Partial<Attributes>) => ContainerLayout,
|
||||
ungroup: (container: ContainerLayout) => void,
|
||||
getCurrentSelection: () => IDragItem[],
|
||||
findLayoutForNode: (nodeId: number) => IDragItem | null;
|
||||
findLayoutEntryForNode: (nodeId: number) => DragItemEntry | null,
|
||||
findLayoutForNode: (nodeId: number) => IDragItem | null,
|
||||
serialize: () => SerializedLayoutState,
|
||||
deserialize: (data: SerializedLayoutState, graph: LGraph) => void,
|
||||
initDefaultLayout: () => void,
|
||||
@@ -568,13 +668,10 @@ function addContainer(parent: ContainerLayout | null, attrs: Partial<Attributes>
|
||||
const dragItem: ContainerLayout = {
|
||||
type: "container",
|
||||
id: `${state.currentId++}`,
|
||||
attrsChanged: writable(false),
|
||||
attrsChanged: writable(0),
|
||||
attrs: {
|
||||
...defaultWidgetAttributes,
|
||||
title: "Container",
|
||||
direction: "vertical",
|
||||
classes: "",
|
||||
containerVariant: "block",
|
||||
flexGrow: 100,
|
||||
...attrs
|
||||
}
|
||||
}
|
||||
@@ -595,12 +692,11 @@ function addWidget(parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partia
|
||||
type: "widget",
|
||||
id: `${state.currentId++}`,
|
||||
node: node,
|
||||
attrsChanged: writable(false),
|
||||
attrsChanged: writable(0),
|
||||
attrs: {
|
||||
...defaultWidgetAttributes,
|
||||
title: widgetName,
|
||||
direction: "horizontal",
|
||||
classes: "",
|
||||
flexGrow: 100,
|
||||
nodeDisabledState: "disabled",
|
||||
...attrs
|
||||
}
|
||||
}
|
||||
@@ -771,16 +867,23 @@ function ungroup(container: ContainerLayout) {
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function findLayoutForNode(nodeId: number): WidgetLayout | null {
|
||||
function findLayoutEntryForNode(nodeId: number): DragItemEntry | null {
|
||||
const state = get(store)
|
||||
const found = Object.entries(state.allItems).find(pair =>
|
||||
pair[1].dragItem.type === "widget"
|
||||
&& (pair[1].dragItem as WidgetLayout).node.id === nodeId)
|
||||
if (found)
|
||||
return found[1].dragItem as WidgetLayout
|
||||
return found[1]
|
||||
return null;
|
||||
}
|
||||
|
||||
function findLayoutForNode(nodeId: number): WidgetLayout | null {
|
||||
const found = findLayoutEntryForNode(nodeId);
|
||||
if (!found)
|
||||
return null;
|
||||
return found.dragItem as WidgetLayout
|
||||
}
|
||||
|
||||
function initDefaultLayout() {
|
||||
store.set({
|
||||
root: null,
|
||||
@@ -790,7 +893,10 @@ function initDefaultLayout() {
|
||||
currentSelection: [],
|
||||
currentSelectionNodes: [],
|
||||
isMenuOpen: false,
|
||||
isConfiguring: false
|
||||
isConfiguring: false,
|
||||
attrs: {
|
||||
defaultSubgraph: ""
|
||||
}
|
||||
})
|
||||
|
||||
const root = addContainer(null, { direction: "horizontal", title: "" });
|
||||
@@ -859,8 +965,8 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
||||
const dragItem: IDragItem = {
|
||||
type: entry.dragItem.type,
|
||||
id: entry.dragItem.id,
|
||||
attrs: entry.dragItem.attrs,
|
||||
attrsChanged: writable(false)
|
||||
attrs: { ...defaultWidgetAttributes, ...entry.dragItem.attrs },
|
||||
attrsChanged: writable(0)
|
||||
};
|
||||
|
||||
const dragEntry: DragItemEntry = {
|
||||
@@ -929,6 +1035,7 @@ const layoutStateStore: WritableLayoutStateStore =
|
||||
nodeRemoved,
|
||||
getCurrentSelection,
|
||||
groupItems,
|
||||
findLayoutEntryForNode,
|
||||
findLayoutForNode,
|
||||
ungroup,
|
||||
initDefaultLayout,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import type ComfyApp from "$lib/components/ComfyApp"
|
||||
|
||||
export type UIEditMode = "widgets" | "containers" | "layout";
|
||||
|
||||
export type UIState = {
|
||||
app: ComfyApp,
|
||||
nodesLocked: boolean,
|
||||
graphLocked: boolean,
|
||||
autoAddUI: boolean,
|
||||
@@ -16,7 +14,6 @@ export type UIState = {
|
||||
export type WritableUIStateStore = Writable<UIState>;
|
||||
const store: WritableUIStateStore = writable(
|
||||
{
|
||||
app: null,
|
||||
graphLocked: false,
|
||||
nodesLocked: false,
|
||||
autoAddUI: true,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { get } from "svelte/store"
|
||||
import layoutState from "$lib/stores/layoutState"
|
||||
import type { SvelteComponentDev } from "svelte/internal";
|
||||
import type { SerializedLGraph } from "@litegraph-ts/core";
|
||||
import type { GalleryOutput } from "./nodes/ComfyWidgetNodes";
|
||||
|
||||
export function clamp(n: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(n, min), max)
|
||||
@@ -91,7 +92,7 @@ export function promptToGraphVis(prompt: SerializedPrompt): string {
|
||||
}
|
||||
else {
|
||||
// Value
|
||||
out += `"${id}-${inpName}-${typeof i}" -> "${outNode.title}"\n`
|
||||
out += `"${id}-${inpName}-${i}" -> "${outNode.title}"\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,3 +100,48 @@ export function promptToGraphVis(prompt: SerializedPrompt): string {
|
||||
out += "}"
|
||||
return out
|
||||
}
|
||||
|
||||
export function getNodeInfo(nodeId: number): string {
|
||||
let app = (window as any).app;
|
||||
if (!app)
|
||||
return String(nodeId);
|
||||
|
||||
const title = app.lGraph.getNodeById(nodeId)?.title || String(nodeId);
|
||||
return title + " (" + nodeId + ")"
|
||||
}
|
||||
|
||||
export const debounce = (callback: Function, wait = 250) => {
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
return (...args: Array<unknown>) => {
|
||||
const next = () => callback(...args);
|
||||
if (timeout) clearTimeout(timeout);
|
||||
|
||||
timeout = setTimeout(next, wait);
|
||||
};
|
||||
};
|
||||
|
||||
export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileData[] {
|
||||
return output.images.map(r => {
|
||||
// TODO configure backend URL
|
||||
const url = `http://${location.hostname}:8188` // TODO make configurable
|
||||
const params = new URLSearchParams(r)
|
||||
return {
|
||||
name: null,
|
||||
data: url + "/view?" + params
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function jsonToJsObject(json: string): string {
|
||||
// Try to parse, to see if it's real JSON
|
||||
JSON.parse(json);
|
||||
|
||||
const regex = /\"([^"]+)\":/g;
|
||||
const hyphenRegex = /-([a-z])/g;
|
||||
|
||||
return json.replace(regex, match => {
|
||||
return match
|
||||
.replace(hyphenRegex, g => g[1].toUpperCase())
|
||||
.replace(regex, "$1:");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
import { Button } from "@gradio/button";
|
||||
import { get, type Writable, writable } from "svelte/store";
|
||||
import { isDisabled } from "./utils"
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfyButtonNode | null = null;
|
||||
let nodeValue: Writable<boolean> | null = null;
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
@@ -21,6 +23,7 @@
|
||||
|
||||
function onClick(e: MouseEvent) {
|
||||
node.onClick();
|
||||
navigator.vibrate(20)
|
||||
}
|
||||
|
||||
const style = {
|
||||
@@ -30,9 +33,9 @@
|
||||
|
||||
<div class="wrapper gradio-button">
|
||||
{#key $attrsChanged}
|
||||
{#if node !== null}
|
||||
{#if widget !== null && node !== null}
|
||||
<Button
|
||||
disabled={widget.attrs.disabled}
|
||||
disabled={isDisabled(widget)}
|
||||
on:click={onClick}
|
||||
variant={widget.attrs.buttonVariant || "primary"}
|
||||
size={widget.attrs.buttonSize === "small" ? "sm" : "lg"}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { ComfyCheckboxNode } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Checkbox } from "@gradio/form";
|
||||
import { get, type Writable, writable } from "svelte/store";
|
||||
import { isDisabled } from "./utils"
|
||||
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfyCheckboxNode | null = null;
|
||||
let nodeValue: Writable<boolean> | null = null;
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
@@ -19,6 +21,10 @@
|
||||
attrsChanged = widget.attrsChanged;
|
||||
}
|
||||
};
|
||||
|
||||
function onSelect() {
|
||||
navigator.vibrate(20)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper gradio-checkbox">
|
||||
@@ -26,7 +32,12 @@
|
||||
{#key $attrsChanged}
|
||||
{#if node !== null}
|
||||
<Block>
|
||||
<Checkbox disabled={widget.attrs.disabled} label={widget.attrs.title} bind:value={$nodeValue} />
|
||||
<Checkbox
|
||||
disabled={isDisabled(widget)}
|
||||
label={widget.attrs.title}
|
||||
bind:value={$nodeValue}
|
||||
on:select={onSelect}
|
||||
/>
|
||||
</Block>
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
@@ -4,13 +4,18 @@
|
||||
import type { ComfyComboNode } from "$lib/nodes/index";
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
import { get, type Writable } from "svelte/store";
|
||||
import { isDisabled } from "./utils"
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfyComboNode | null = null;
|
||||
let nodeValue: Writable<string> | null = null;
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
let comboRefreshed: Writable<boolean> | null = null;
|
||||
let wasComboRefreshed: boolean = false;
|
||||
let option: any
|
||||
|
||||
export let debug: boolean = false;
|
||||
let input: HTMLInputElement | null = null
|
||||
|
||||
$: widget && setNodeValue(widget);
|
||||
|
||||
@@ -32,6 +37,9 @@
|
||||
node = widget.node as ComfyComboNode
|
||||
nodeValue = node.value;
|
||||
propsChanged = node.propsChanged;
|
||||
comboRefreshed = node.comboRefreshed;
|
||||
if ($comboRefreshed)
|
||||
flashOnRefreshed();
|
||||
setOption($nodeValue) // don't react on option
|
||||
}
|
||||
}
|
||||
@@ -44,6 +52,12 @@
|
||||
$nodeValue = option.value;
|
||||
}
|
||||
|
||||
$: $comboRefreshed && flashOnRefreshed();
|
||||
|
||||
function flashOnRefreshed() {
|
||||
setTimeout(() => ($comboRefreshed = false), 1000);
|
||||
}
|
||||
|
||||
function getLinkValue() {
|
||||
if (!node)
|
||||
return "???";
|
||||
@@ -53,42 +67,48 @@
|
||||
return links[0].data
|
||||
}
|
||||
|
||||
let lastPropsChanged: number = 0;
|
||||
let werePropsChanged: boolean = false;
|
||||
function onFocus() {
|
||||
navigator.vibrate(20)
|
||||
}
|
||||
|
||||
$: if ($propsChanged !== lastPropsChanged) {
|
||||
werePropsChanged = true;
|
||||
lastPropsChanged = $propsChanged;
|
||||
setTimeout(() => (werePropsChanged = false), 2000);
|
||||
function onSelect() {
|
||||
if (input)
|
||||
input.blur();
|
||||
navigator.vibrate(20)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper comfy-combo" class:updated={werePropsChanged}>
|
||||
<div class="wrapper comfy-combo" class:updated={$comboRefreshed}>
|
||||
{#key $propsChanged}
|
||||
{#if node !== null && nodeValue !== null}
|
||||
<label>
|
||||
{#if widget.attrs.title !== ""}
|
||||
<BlockTitle show_label={true}>{widget.attrs.title}</BlockTitle>
|
||||
{/if}
|
||||
<Select
|
||||
bind:value={option}
|
||||
items={node.properties.values}
|
||||
disabled={widget.attrs.disabled || node.properties.values.length === 0}
|
||||
clearable={false}
|
||||
showChevron={true}
|
||||
on:change
|
||||
on:select
|
||||
on:filter
|
||||
on:blur
|
||||
/>
|
||||
{#if debug}
|
||||
<div>Value: {option?.value}</div>
|
||||
<div>Items: {node.properties.values}</div>
|
||||
<div>NodeValue: {$nodeValue}</div>
|
||||
<div>LinkValue: {getLinkValue()}</div>
|
||||
{/if}
|
||||
</label>
|
||||
{/if}
|
||||
{#key $comboRefreshed}
|
||||
{#if node !== null && nodeValue !== null}
|
||||
<label>
|
||||
{#if widget.attrs.title !== ""}
|
||||
<BlockTitle show_label={true}>{widget.attrs.title}</BlockTitle>
|
||||
{/if}
|
||||
<Select
|
||||
bind:value={option}
|
||||
items={node.properties.values}
|
||||
disabled={isDisabled(widget) || node.properties.values.length === 0}
|
||||
clearable={false}
|
||||
showChevron={true}
|
||||
inputAttributes={{ autocomplete: 'off' }}
|
||||
bind:input
|
||||
on:change
|
||||
on:focus={onFocus}
|
||||
on:select={onSelect}
|
||||
on:filter
|
||||
on:blur
|
||||
/>
|
||||
{#if debug}
|
||||
<div>Value: {option?.value}</div>
|
||||
<div>Items: {node.properties.values}</div>
|
||||
<div>NodeValue: {$nodeValue}</div>
|
||||
<div>LinkValue: {getLinkValue()}</div>
|
||||
{/if}
|
||||
</label>
|
||||
{/if}
|
||||
{/key}
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
@@ -116,7 +136,9 @@
|
||||
|
||||
:global(.svelte-select) {
|
||||
width: auto;
|
||||
max-width: 30rem;
|
||||
max-width: 16rem;
|
||||
--font-size: 13px;
|
||||
--height: 32px;
|
||||
}
|
||||
|
||||
:global(.svelte-select-list) {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Block, BlockLabel, Empty } from "@gradio/atoms";
|
||||
import { Gallery } from "@gradio/gallery";
|
||||
import { Image } from "@gradio/icons";
|
||||
import type { Styles } from "@gradio/utils";
|
||||
import type { WidgetLayout } from "$lib/stores/layoutState";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { ComfyGalleryNode } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||
import type { SelectData as GradioSelectData } from "@gradio/utils";
|
||||
import { clamp } from "$lib/utils";
|
||||
import { clamp } from "$lib/utils";
|
||||
import { f7 } from "framework7-svelte";
|
||||
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfyGalleryNode | null = null;
|
||||
let nodeValue: Writable<GradioFileData[]> | null = null;
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
@@ -24,28 +27,94 @@
|
||||
nodeValue = node.value;
|
||||
propsChanged = node.propsChanged;
|
||||
|
||||
const len = $nodeValue.length
|
||||
if (node.properties.index < 0 || node.properties.index >= len) {
|
||||
node.setProperty("index", clamp(node.properties.index, 0, len))
|
||||
if ($nodeValue != null) {
|
||||
if (node.properties.index < 0 || node.properties.index >= $nodeValue.length) {
|
||||
node.setProperty("index", clamp(node.properties.index, 0, $nodeValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let style: Styles = {
|
||||
// grid_cols: [2],
|
||||
grid: [3],
|
||||
grid_cols: [4],
|
||||
grid_rows: [4],
|
||||
// object_fit: "cover",
|
||||
}
|
||||
let element: HTMLDivElement;
|
||||
|
||||
let mobileLightbox = null;
|
||||
|
||||
function showMobileLightbox(event: Event) {
|
||||
if (!f7)
|
||||
return
|
||||
|
||||
if (mobileLightbox) {
|
||||
mobileLightbox.destroy();
|
||||
mobileLightbox = null;
|
||||
}
|
||||
|
||||
const source = (event.target || event.srcElement) as HTMLImageElement;
|
||||
const galleryElem = source.closest<HTMLDivElement>("div.block")
|
||||
console.debug("[ImageViewer] showModal", event, source, galleryElem);
|
||||
if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) {
|
||||
console.error("No buttons found on gallery element!", galleryElem)
|
||||
return;
|
||||
}
|
||||
|
||||
const allGalleryButtons = ImageViewer.all_gallery_buttons(galleryElem);
|
||||
const selectedSource = source.src
|
||||
|
||||
const images = allGalleryButtons.map(button => {
|
||||
return {
|
||||
url: (button.children[0] as HTMLImageElement).src,
|
||||
caption: "Image"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
mobileLightbox = f7.photoBrowser.create({
|
||||
photos: images,
|
||||
thumbs: images.map(i => i.url),
|
||||
type: 'popup',
|
||||
});
|
||||
mobileLightbox.open()
|
||||
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
function setupImageForMobileLightbox(e: HTMLImageElement) {
|
||||
if (e.dataset.modded === "true")
|
||||
return;
|
||||
|
||||
e.dataset.modded = "true";
|
||||
e.style.cursor = "pointer";
|
||||
e.style.userSelect = "none";
|
||||
|
||||
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
|
||||
|
||||
// For Firefox, listening on click first switched to next image then shows the lightbox.
|
||||
// If you know how to fix this without switching to mousedown event, please.
|
||||
// For other browsers the event is click to make it possiblr to drag picture.
|
||||
var event = isFirefox ? 'mousedown' : 'click'
|
||||
|
||||
e.addEventListener(event, (evt) => {
|
||||
evt.preventDefault()
|
||||
showMobileLightbox(evt)
|
||||
}, true);
|
||||
}
|
||||
|
||||
function onSelect(e: CustomEvent<GradioSelectData>) {
|
||||
// Setup lightbox
|
||||
// Wait for gradio gallery to show the large preview image, if no timeout then
|
||||
// the event might fire too early
|
||||
|
||||
const callback = isMobile ? setupImageForMobileLightbox
|
||||
: ImageViewer.instance.setupImageForLightbox.bind(ImageViewer.instance)
|
||||
|
||||
setTimeout(() => {
|
||||
const images = element.querySelectorAll<HTMLImageElement>('div.block div > img')
|
||||
if (images != null) {
|
||||
images.forEach(ImageViewer.instance.setupImageForLightbox.bind(ImageViewer.instance));
|
||||
images.forEach(callback);
|
||||
}
|
||||
ImageViewer.instance.updateOnBackgroundChange();
|
||||
}, 200)
|
||||
@@ -53,25 +122,44 @@
|
||||
// Update index
|
||||
node.setProperty("index", e.detail.index as number)
|
||||
}
|
||||
|
||||
</script>
|
||||
<div class="wrapper comfy-gallery-widget gradio-gallery" bind:this={element}>
|
||||
{#if widget && node && nodeValue}
|
||||
<Block variant="solid" padding={false}>
|
||||
<div class="padding">
|
||||
<Gallery
|
||||
bind:value={$nodeValue}
|
||||
label={widget.attrs.title}
|
||||
show_label={widget.attrs.title !== ""}
|
||||
{style}
|
||||
root={""}
|
||||
root_url={""}
|
||||
on:select={onSelect}
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
|
||||
{#if widget && node && nodeValue && $nodeValue != null}
|
||||
{#if widget.attrs.variant === "image"}
|
||||
<div class="wrapper comfy-image-widget" style="height: {widget.attrs.height || 'auto'}" bind:this={element}>
|
||||
<Block variant="solid" padding={false}>
|
||||
{#if widget.attrs.title}
|
||||
<BlockLabel
|
||||
show_label={true}
|
||||
Icon={Image}
|
||||
label={widget.attrs.title || "Image"}
|
||||
/>
|
||||
{/if}
|
||||
{#if $nodeValue.length > 0}
|
||||
<img src={$nodeValue[$nodeValue.length-1].data}/>
|
||||
{:else}
|
||||
<Empty size="large" unpadded_box={true}><Image /></Empty>
|
||||
{/if}
|
||||
</Block>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="wrapper comfy-gallery-widget gradio-gallery" bind:this={element}>
|
||||
<Block variant="solid" padding={false}>
|
||||
<div class="padding">
|
||||
<Gallery
|
||||
bind:value={$nodeValue}
|
||||
label={widget.attrs.title}
|
||||
show_label={widget.attrs.title !== ""}
|
||||
{style}
|
||||
root={""}
|
||||
root_url={""}
|
||||
on:select={onSelect}
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.wrapper {
|
||||
@@ -80,13 +168,28 @@
|
||||
:global(> .block) {
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
|
||||
:global(button.thumbnail-lg) {
|
||||
width: var(--size-32);
|
||||
}
|
||||
|
||||
&.comfy-image-widget {
|
||||
aspect-ratio: 1/1;
|
||||
|
||||
:global(> .block) {
|
||||
height: 100%;
|
||||
|
||||
:global(img) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.padding {
|
||||
height: 30rem;
|
||||
}
|
||||
|
||||
.wrapper :global(button.thumbnail-lg) {
|
||||
width: var(--size-32);
|
||||
}
|
||||
</style>
|
||||
|
||||
64
src/lib/widgets/RadioWidget.svelte
Normal file
64
src/lib/widgets/RadioWidget.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import type { ComfyRadioNode } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Radio } from "@gradio/form";
|
||||
import { get, type Writable, writable } from "svelte/store";
|
||||
import { isDisabled } from "./utils"
|
||||
import type { SelectData } from "@gradio/utils";
|
||||
import { clamp } from "$lib/utils";
|
||||
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfyRadioNode | null = null;
|
||||
let nodeValue: Writable<string> | null = null;
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
let attrsChanged: Writable<number> | null = null;
|
||||
|
||||
$: widget && setNodeValue(widget);
|
||||
|
||||
function setNodeValue(widget: WidgetLayout) {
|
||||
if (widget) {
|
||||
node = widget.node as ComfyRadioNode
|
||||
nodeValue = node.value;
|
||||
attrsChanged = widget.attrsChanged;
|
||||
}
|
||||
};
|
||||
|
||||
$: node && $propsChanged && clampIndex();
|
||||
|
||||
function clampIndex() {
|
||||
node.index = clamp(node.index, 0, node.properties.choices?.length || 0)
|
||||
}
|
||||
|
||||
function onSelect(e: CustomEvent<SelectData>) {
|
||||
node.setValue(e.detail.value)
|
||||
node.index = e.detail.index as number
|
||||
navigator.vibrate(20)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper gradio-radio">
|
||||
<div class="inner">
|
||||
{#key $propsChanged}
|
||||
{#key $attrsChanged}
|
||||
{#if node !== null && node.properties.choices}
|
||||
<Block type="fieldset">
|
||||
<Radio
|
||||
elem_id="radio"
|
||||
choices={node.properties.choices}
|
||||
disabled={isDisabled(widget)}
|
||||
label={widget.attrs.title}
|
||||
show_label={widget.attrs.title && widget.attrs.title !== ""}
|
||||
value={$nodeValue}
|
||||
on:select={onSelect}
|
||||
/>
|
||||
</Block>
|
||||
{/if}
|
||||
{/key}
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
@@ -1,13 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type { ComfySliderNode } from "$lib/nodes/index";
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
import { Range } from "@gradio/form";
|
||||
import { Range } from "$lib/components/gradio/form";
|
||||
import { get, type Writable } from "svelte/store";
|
||||
import { debounce } from "$lib/utils";
|
||||
import interfaceState from "$lib/stores/interfaceState";
|
||||
import { isDisabled } from "./utils"
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfySliderNode | null = null;
|
||||
let nodeValue: Writable<number> | null = null;
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
let option: number | null = null;
|
||||
let isDragging: boolean = false;
|
||||
|
||||
$: widget && setNodeValue(widget);
|
||||
|
||||
@@ -18,6 +23,7 @@
|
||||
propsChanged = node.propsChanged;
|
||||
setOption($nodeValue); // don't react on option
|
||||
}
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
// I don't know why but this is necessary to watch for changes to node
|
||||
@@ -33,14 +39,34 @@
|
||||
}
|
||||
|
||||
function onRelease(e: Event) {
|
||||
if (nodeValue && option) {
|
||||
if (nodeValue && option != null) {
|
||||
$nodeValue = option
|
||||
}
|
||||
}
|
||||
|
||||
let gradient: string = ""
|
||||
function setBackgroundSize(input: HTMLInputElement) {
|
||||
input.style.setProperty("--background-size", `${getBackgroundSize(input)}%`);
|
||||
}
|
||||
|
||||
function getBackgroundSize(input: HTMLInputElement) {
|
||||
const min = +input.min || 0;
|
||||
const max = +input.max || 100;
|
||||
const value = +input.value;
|
||||
|
||||
return (value - min) / (max - min) * 100;
|
||||
}
|
||||
|
||||
function updateSliderForMobile() {
|
||||
const target = elem.querySelector<HTMLInputElement>("input[type=range]");
|
||||
setBackgroundSize(target);
|
||||
}
|
||||
|
||||
let elem: HTMLDivElement = null;
|
||||
|
||||
$: if (elem) {
|
||||
updateSliderForMobile()
|
||||
}
|
||||
|
||||
$: if (elem && node !== null && option !== null && (!$propsChanged || $propsChanged)) {
|
||||
const slider = elem.querySelector("input[type='range']") as any
|
||||
//const range_selectors = "[id$='_clone']:is(input[type='range'])";
|
||||
@@ -50,27 +76,95 @@
|
||||
const style = elem.style;
|
||||
style.setProperty('--ae-slider-bg-overlay', 'repeating-linear-gradient( 90deg, transparent, transparent '+tsp+', var(--ae-input-border-color) '+tsp+', var(--ae-input-border-color) '+fsp+' )');
|
||||
}
|
||||
|
||||
function onPointerDown(e: PointerEvent) {
|
||||
if (!isMobile)
|
||||
return;
|
||||
|
||||
interfaceState.showIndicator(e.clientX, e.clientY, option);
|
||||
}
|
||||
|
||||
let canVibrate = true;
|
||||
let lastDisplayValue = null;
|
||||
|
||||
function onPointerMove(e: PointerEvent) {
|
||||
if (!isMobile)
|
||||
return;
|
||||
interfaceState.showIndicator(e.clientX, e.clientY, option);
|
||||
|
||||
if (canVibrate && lastDisplayValue != option) {
|
||||
lastDisplayValue = option;
|
||||
canVibrate = false;
|
||||
setTimeout(() => { canVibrate = true }, 30)
|
||||
navigator.vibrate(10)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper gradio-slider" bind:this={elem}>
|
||||
<div class="wrapper gradio-slider" class:mobile={isMobile} bind:this={elem}>
|
||||
{#if node !== null && option !== null}
|
||||
<Range
|
||||
bind:value={option}
|
||||
disabled={widget.attrs.disabled}
|
||||
disabled={isDisabled(widget)}
|
||||
minimum={node.properties.min}
|
||||
maximum={node.properties.max}
|
||||
step={node.properties.step}
|
||||
label={widget.attrs.title}
|
||||
show_label={true}
|
||||
on:release={onRelease}
|
||||
on:change
|
||||
on:change={updateSliderForMobile}
|
||||
on:pointerdown={onPointerDown}
|
||||
on:pointermove={onPointerMove}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.wrapper {
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
|
||||
:global(input[type=number]) {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
// Prevent swiping on the slider track from accidentally changing the value
|
||||
:global(input[type="range"]) {
|
||||
pointer-events: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
cursor: default;
|
||||
height: 0.6rem;
|
||||
padding: initial;
|
||||
border: initial;
|
||||
margin: 0.8rem 0;
|
||||
width: 100%;
|
||||
|
||||
background: linear-gradient(to right, var(--color-blue-600), var(--color-blue-600)), #D7D7D7;
|
||||
background-size: var(--background-size, 0%) 100%;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid var(--neutral-400);
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
pointer-events: all;
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 50%;
|
||||
background: var(--color-blue-600);
|
||||
cursor: pointer;
|
||||
border: 2px solid var(--neutral-100);
|
||||
box-shadow: 0px 0px 0px 1px var(--neutral-400);
|
||||
}
|
||||
|
||||
:global(input[type=number]) {
|
||||
font-size: 16px;
|
||||
height: var(--size-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
import type { ComfyComboNode } from "$lib/nodes/index";
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
import { get, type Writable } from "svelte/store";
|
||||
import { isDisabled } from "./utils"
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfyComboNode | null = null;
|
||||
let nodeValue: Writable<string> | null = null;
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
@@ -32,7 +34,7 @@
|
||||
<TextBox
|
||||
bind:value={$nodeValue}
|
||||
label={widget.attrs.title}
|
||||
disabled={widget.attrs.disabled}
|
||||
disabled={isDisabled(widget)}
|
||||
lines={node.properties.multiline ? 5 : 1}
|
||||
max_lines={node.properties.multiline ? 5 : 1}
|
||||
show_label={widget.attrs.title !== ""}
|
||||
|
||||
26
src/lib/widgets/utils.ts
Normal file
26
src/lib/widgets/utils.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { IDragItem } from "$lib/stores/layoutState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import { NodeMode } from "@litegraph-ts/core";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
export function isDisabled(widget: IDragItem) {
|
||||
if (widget.attrs.disabled)
|
||||
return true;
|
||||
|
||||
if (widget.type === "widget") {
|
||||
return widget.attrs.nodeDisabledState === "disabled" && widget.node.mode === NodeMode.NEVER
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isHidden(widget: IDragItem) {
|
||||
if (widget.attrs.hidden)
|
||||
return true;
|
||||
|
||||
if (widget.type === "widget") {
|
||||
return widget.attrs.nodeDisabledState === "hidden" && widget.node.mode === NodeMode.NEVER
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -2,12 +2,23 @@ import AppMobile from './AppMobile.svelte';
|
||||
import Framework7 from 'framework7/lite-bundle';
|
||||
import Framework7Svelte from 'framework7-svelte';
|
||||
import { f7 } from 'framework7-svelte';
|
||||
|
||||
import ComfyApp from '$lib/components/ComfyApp';
|
||||
import uiState from '$lib/stores/uiState';
|
||||
import { LiteGraph } from '@litegraph-ts/core';
|
||||
|
||||
Framework7.use(Framework7Svelte);
|
||||
|
||||
LiteGraph.dialog_close_on_mouse_leave = false;
|
||||
LiteGraph.search_hide_on_mouse_leave = false;
|
||||
LiteGraph.pointerevents_method = "pointer";
|
||||
|
||||
const comfyApp = new ComfyApp();
|
||||
|
||||
uiState.update(s => { s.app = comfyApp; return s; })
|
||||
|
||||
const app = new AppMobile({
|
||||
target: document.getElementById('app'),
|
||||
target: document.getElementById('app'),
|
||||
props: { app: comfyApp }
|
||||
})
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -1,38 +1,104 @@
|
||||
<script lang="ts">
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import notify from "$lib/notify";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { getNodeInfo } from "$lib/utils"
|
||||
|
||||
import { Link, Toolbar } from "framework7-svelte"
|
||||
import { f7 } from "framework7-svelte"
|
||||
import ProgressBar from "$lib/components/ProgressBar.svelte";
|
||||
import Indicator from "./Indicator.svelte";
|
||||
import interfaceState from "$lib/stores/interfaceState";
|
||||
import LightboxModal from "$lib/components/LightboxModal.svelte";
|
||||
|
||||
export let subworkflowID: number = -1;
|
||||
let app: ComfyApp = undefined;
|
||||
|
||||
$: if (!app)
|
||||
app = $uiState.app
|
||||
export let app: ComfyApp = undefined;
|
||||
let fileInput: HTMLInputElement = undefined;
|
||||
|
||||
function queuePrompt() {
|
||||
app.queuePrompt(0, 1);
|
||||
showNotification();
|
||||
notify("Prompt was queued", "Queued");
|
||||
}
|
||||
|
||||
let notification;
|
||||
const showNotification = () => {
|
||||
if (!notification) {
|
||||
notification = f7.notification.create({
|
||||
title: 'Queued',
|
||||
titleRightText: 'now',
|
||||
// subtitle: 'Notification with close on click',
|
||||
text: 'Prompt was queued',
|
||||
closeOnClick: true,
|
||||
closeTimeout: 3000,
|
||||
});
|
||||
}
|
||||
// Open it
|
||||
notification.open();
|
||||
function doLoad(): void {
|
||||
if (!app?.lGraph || !fileInput)
|
||||
return;
|
||||
|
||||
fileInput.click();
|
||||
}
|
||||
|
||||
function loadWorkflow(): void {
|
||||
app.handleFile(fileInput.files[0]);
|
||||
fileInput.files = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="bottom">
|
||||
{#if $queueState.runningNodeId || $queueState.progress}
|
||||
<div class="node-name">
|
||||
<span>Node: {getNodeInfo($queueState.runningNodeId)}</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<ProgressBar value={$queueState.progress?.value} max={$queueState.progress?.max} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0}
|
||||
<div class="queue-remaining in-progress">
|
||||
<div>
|
||||
Queued prompts: {$queueState.queueRemaining}.
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<Toolbar bottom>
|
||||
<Link on:click={queuePrompt}>Queue Prompt</Link>
|
||||
<Link on:click={() => app.refreshComboInNodes()}>🔄</Link>
|
||||
<Link on:click={doLoad}>Load</Link>
|
||||
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
||||
</Toolbar>
|
||||
{#if $interfaceState.showIndicator}
|
||||
<Indicator value={$interfaceState.indicatorValue} />
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
#comfy-file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
bottom: calc(var(--f7-toolbar-height) + var(--f7-safe-area-bottom));
|
||||
z-index: var(--layer-top);
|
||||
background-color: grey;
|
||||
|
||||
.node-name {
|
||||
flex-grow: 1;
|
||||
background-color: var(--color-red-300);
|
||||
padding: 0.2em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex-grow: 10;
|
||||
background-color: var(--color-red-300);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.queue-remaining {
|
||||
flex-grow: 1;
|
||||
padding: 0.2em;
|
||||
&.in-progress {
|
||||
background-color: var(--secondary-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
45
src/mobile/Indicator.svelte
Normal file
45
src/mobile/Indicator.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import interfaceState from "$lib/stores/interfaceState";
|
||||
|
||||
export let value: any = null;
|
||||
</script>
|
||||
|
||||
<div style="position: relative; z-index: 10;">
|
||||
<div class="indicator"
|
||||
class:top={true}
|
||||
class:bottom={false}
|
||||
class:left={!$interfaceState.pointerNearLeft || !$interfaceState.pointerNearTop}
|
||||
class:right={$interfaceState.pointerNearLeft && $interfaceState.pointerNearTop}>
|
||||
<span>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.indicator {
|
||||
position: fixed;
|
||||
|
||||
align-content: left;
|
||||
padding: 1rem;
|
||||
font-size: xxx-large;
|
||||
background: var(--neutral-300);
|
||||
color: var(--neutral-800);
|
||||
border-radius: 1rem;
|
||||
border: 0.2rem solid var(--neutral-400);
|
||||
z-index: var(--layer-top) !important;
|
||||
|
||||
&.top {
|
||||
top: calc(1rem + var(--f7-navbar-height) + var(--f7-safe-area-top));
|
||||
}
|
||||
&.bottom {
|
||||
bottom: 5rem
|
||||
}
|
||||
&.left {
|
||||
left: 1rem;
|
||||
}
|
||||
&.right {
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -6,13 +6,10 @@
|
||||
import { onMount } from 'svelte';
|
||||
import uiState from "$lib/stores/uiState"
|
||||
|
||||
let app: ComfyApp | null = null;
|
||||
let lCanvas: LGraphCanvas | null = null;
|
||||
export let app: ComfyApp | null = null;
|
||||
let lCanvas: ComfyGraphCanvas | null = null;
|
||||
let canvasEl: HTMLCanvasElement | null = null;
|
||||
|
||||
$: if (!app)
|
||||
app = $uiState.app
|
||||
|
||||
function resizeCanvas() {
|
||||
canvasEl.width = canvasEl.parentElement.offsetWidth;
|
||||
canvasEl.height = canvasEl.parentElement.offsetHeight;
|
||||
@@ -21,13 +18,11 @@
|
||||
lCanvas.draw(true, true);
|
||||
}
|
||||
|
||||
$: if (app && canvasEl) {
|
||||
$: if (app != null && app.lGraph && canvasEl != null) {
|
||||
if (!lCanvas) {
|
||||
lCanvas = new ComfyGraphCanvas(app, canvasEl);
|
||||
lCanvas.allow_interaction = false;
|
||||
LiteGraph.dialog_close_on_mouse_leave = false;
|
||||
LiteGraph.search_hide_on_mouse_leave = false;
|
||||
LiteGraph.pointerevents_method = "pointer";
|
||||
app.lGraph.eventBus.on("afterExecute", () => lCanvas.draw(true))
|
||||
}
|
||||
resizeCanvas();
|
||||
}
|
||||
@@ -46,5 +41,10 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #333;
|
||||
|
||||
> canvas {
|
||||
// Don't try to scroll the page when scrolling on canvas
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -14,22 +14,7 @@
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
||||
|
||||
let app: ComfyApp | null = null;
|
||||
|
||||
onMount(async () => {
|
||||
if (app)
|
||||
return
|
||||
app = $uiState.app = new ComfyApp();
|
||||
|
||||
|
||||
app.api.addEventListener("status", (ev: CustomEvent) => {
|
||||
queueState.statusUpdated(ev.detail as ComfyAPIStatus);
|
||||
});
|
||||
|
||||
await app.setup();
|
||||
(window as any).app = app;
|
||||
|
||||
});
|
||||
export let app: ComfyApp | null = null;
|
||||
|
||||
</script>
|
||||
|
||||
@@ -49,8 +34,4 @@
|
||||
<i class="icon icon-f7" slot="media" />
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<div class="canvas-wrapper pane-wrapper" style="display: none">
|
||||
<canvas id="graph-canvas" />
|
||||
</div>
|
||||
</Page>
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { get } from "svelte/store";
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { download } from "$lib/utils"
|
||||
|
||||
import { LGraph, LGraphNode } from "@litegraph-ts/core";
|
||||
import type { ComfyAPIStatus } from "$lib/api";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import ComfyApp from "$lib/components/ComfyApp";
|
||||
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem } from "framework7-svelte"
|
||||
|
||||
export let app: ComfyApp;
|
||||
</script>
|
||||
|
||||
<Page name="subworkflows">
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
<script lang="ts">
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import layoutState, { type IDragItem } from "$lib/stores/layoutState";
|
||||
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { Page, Navbar, Link, BlockTitle, Block, List, ListItem, Toolbar } from "framework7-svelte"
|
||||
import WidgetContainer from "$lib/components/WidgetContainer.svelte";
|
||||
import type ComfyApp from "$lib/components/ComfyApp";
|
||||
|
||||
export let subworkflowID: number = -1;
|
||||
let app: ComfyApp = undefined;
|
||||
|
||||
$: if (!app)
|
||||
app = $uiState.app
|
||||
export let app: ComfyApp
|
||||
|
||||
</script>
|
||||
|
||||
<Page name="subworkflow">
|
||||
<Navbar title="Workflow {subworkflowID}" backLink="Back" />
|
||||
|
||||
<div>Workflow!</div>
|
||||
<div class="container">
|
||||
<WidgetContainer bind:dragItem={$layoutState.root} isMobile={true} classes={["root-container", "mobile"]} />
|
||||
</div>
|
||||
</Page>
|
||||
|
||||
<style>
|
||||
.is-executing {
|
||||
border: 5px dashed var(--color-green-600) !important;
|
||||
<style lang="scss">
|
||||
.container {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// TODO generalize this to all properties!
|
||||
:global(.root-container.mobile > .block > .v-pane) {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
|
||||
// Disable pull to refresh
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
|
||||
@@ -173,14 +173,17 @@ body {
|
||||
background: var(--ae-panel-bg-color) !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
.z-index0, .z-index1, .z-index2 {
|
||||
> .block > .v-pane > .animation-wrapper > .widget:not(.edit) {
|
||||
padding: var(--ae-inside-padding-size) !important;
|
||||
border: 1px solid var(--ae-panel-border-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add blocks for components not located in a physical container
|
||||
// TODO make it work with accordions/tabs
|
||||
// .container {
|
||||
// .z-index0, .z-index1, .z-index2 {
|
||||
// > .block > .v-pane > .animation-wrapper > .widget:not(.edit) {
|
||||
// padding: var(--ae-inside-padding-size) !important;
|
||||
// border: 1px solid var(--ae-panel-border-color) !important;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
.widget:has(> .gradio-button) {
|
||||
height: 100%;
|
||||
|
||||
@@ -390,8 +393,8 @@ div.block.padded {
|
||||
fieldset.block.padded
|
||||
{
|
||||
background-color: var(--ae-panel-bg-color) !important;
|
||||
/*border-width: var(--ae-border-width) !important;*/
|
||||
/*border-color: var(--ae-panel-border-color) !important;*/
|
||||
border-width: var(--ae-border-width) !important;
|
||||
border-color: var(--ae-panel-border-color) !important;
|
||||
border-radius: var(--ae-panel-border-radius) !important;
|
||||
}
|
||||
|
||||
@@ -440,7 +443,7 @@ div.gradio-row>.form{
|
||||
}
|
||||
|
||||
.wrap.svelte-1p9xokt.svelte-1p9xokt.svelte-1p9xokt label,
|
||||
.wrap.svelte-1qxcj04.svelte-1qxcj04.svelte-1qxcj04 label,
|
||||
.gradio-radio label,
|
||||
button.tool.secondary,
|
||||
button.secondary,
|
||||
.gradio-dropdown label .wrap,
|
||||
@@ -495,6 +498,7 @@ button.secondary{
|
||||
white-space: break-spaces !important;
|
||||
}
|
||||
|
||||
.gradio-radio fieldset > .wrap > label,
|
||||
[type=text],
|
||||
[type=email],
|
||||
[type=url],
|
||||
@@ -510,8 +514,8 @@ button.secondary{
|
||||
[multiple],
|
||||
textarea,
|
||||
select {
|
||||
line-height: 1.5rem;
|
||||
padding: 4px 8px;
|
||||
line-height: 1.5rem !important;
|
||||
padding: 4px 8px !important;
|
||||
}
|
||||
|
||||
button.tool.secondary,
|
||||
@@ -571,12 +575,6 @@ input[type=email] {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
span.svelte-1gfkn6j:not(.has-info) {
|
||||
margin-top: 1px;
|
||||
margin-left: 1px;
|
||||
margin-bottom: var(--ae-inside-padding-size);
|
||||
}
|
||||
|
||||
/* input column alignment */
|
||||
label.block{
|
||||
display: flex;
|
||||
@@ -873,6 +871,11 @@ input[type=range]::-ms-fill-upper {
|
||||
}
|
||||
}
|
||||
|
||||
.comfy-image-widget > .block {
|
||||
background: var(--ae-main-bg-color);
|
||||
border-color: var(--ae-panel-border-color);
|
||||
}
|
||||
|
||||
.comfy-toggle-button {
|
||||
> .lg {
|
||||
border-color: var(--ae-subpanel-border-color) !important;
|
||||
|
||||
Reference in New Issue
Block a user