Copy action and button
This commit is contained in:
Submodule litegraph updated: a1e9ae1261...93b0ed15b7
@@ -43,6 +43,7 @@
|
||||
"@gradio/utils": "workspace:*",
|
||||
"@litegraph-ts/core": "workspace:*",
|
||||
"@litegraph-ts/nodes-basic": "workspace:*",
|
||||
"@litegraph-ts/nodes-events": "workspace:*",
|
||||
"@litegraph-ts/tsconfig": "workspace:*",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
||||
"@tsconfig/svelte": "^4.0.1",
|
||||
|
||||
197
pnpm-lock.yaml
generated
197
pnpm-lock.yaml
generated
@@ -37,6 +37,9 @@ importers:
|
||||
'@litegraph-ts/nodes-basic':
|
||||
specifier: workspace:*
|
||||
version: link:litegraph/packages/nodes-basic
|
||||
'@litegraph-ts/nodes-events':
|
||||
specifier: workspace:*
|
||||
version: link:litegraph/packages/nodes-events
|
||||
'@litegraph-ts/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:litegraph/packages/tsconfig
|
||||
@@ -129,7 +132,7 @@ importers:
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^2.9.9
|
||||
version: 2.9.9(sass@1.61.0)
|
||||
version: 2.9.9
|
||||
|
||||
gradio/js/accordion: {}
|
||||
|
||||
@@ -694,7 +697,7 @@ importers:
|
||||
version: 1.0.0-next.91(@sveltejs/kit@1.15.2)
|
||||
'@sveltejs/kit':
|
||||
specifier: ^1.0.0-next.318
|
||||
version: 1.15.2(svelte@3.58.0)(vite@4.3.1)
|
||||
version: 1.15.2(svelte@3.58.0)
|
||||
autoprefixer:
|
||||
specifier: ^10.4.2
|
||||
version: 10.4.2(postcss@8.4.21)
|
||||
@@ -706,13 +709,13 @@ importers:
|
||||
version: 3.1.1
|
||||
svelte-check:
|
||||
specifier: ^2.2.6
|
||||
version: 2.2.6(postcss-load-config@3.1.1)(postcss@8.4.21)(sass@1.61.0)(svelte@3.58.0)
|
||||
version: 2.2.6(postcss-load-config@3.1.1)(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)(sass@1.61.0)(svelte@3.58.0)(typescript@4.5.4)
|
||||
version: 4.10.1(postcss-load-config@3.1.1)(postcss@8.4.21)(svelte@3.58.0)(typescript@4.5.4)
|
||||
tailwindcss:
|
||||
specifier: ^3.0.12
|
||||
version: 3.3.1(postcss@8.4.21)
|
||||
version: 3.3.1
|
||||
tslib:
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1
|
||||
@@ -746,7 +749,7 @@ importers:
|
||||
version: 5.0.3
|
||||
vite:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(sass@1.61.0)
|
||||
version: 4.2.1
|
||||
vite-plugin-checker:
|
||||
specifier: ^0.5.6
|
||||
version: 0.5.6(eslint@8.37.0)(typescript@5.0.3)(vite@4.2.1)
|
||||
@@ -765,7 +768,23 @@ importers:
|
||||
version: 5.0.3
|
||||
vite:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(sass@1.61.0)
|
||||
version: 4.2.1
|
||||
|
||||
litegraph/packages/nodes-events:
|
||||
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/tsconfig: {}
|
||||
|
||||
@@ -1993,11 +2012,11 @@ packages:
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^1.0.0-next.587
|
||||
dependencies:
|
||||
'@sveltejs/kit': 1.15.2(svelte@3.58.0)(vite@4.3.1)
|
||||
'@sveltejs/kit': 1.15.2(svelte@3.58.0)
|
||||
import-meta-resolve: 2.2.2
|
||||
dev: true
|
||||
|
||||
/@sveltejs/kit@1.15.2(svelte@3.58.0)(vite@4.3.1):
|
||||
/@sveltejs/kit@1.15.2(svelte@3.58.0):
|
||||
resolution: {integrity: sha512-rLNxZrjbrlPf8AWW8GAU4L/Vvu17e9v8EYl7pUip7x72lTft7RcxeP3z7tsrHpMSBBxC9o4XdKzFvz1vMZyXZw==}
|
||||
engines: {node: ^16.14 || >=18}
|
||||
hasBin: true
|
||||
@@ -2006,7 +2025,7 @@ packages:
|
||||
svelte: ^3.54.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 2.1.1(svelte@3.58.0)(vite@4.3.1)
|
||||
'@sveltejs/vite-plugin-svelte': 2.1.1(svelte@3.58.0)
|
||||
'@types/cookie': 0.5.1
|
||||
cookie: 0.5.0
|
||||
devalue: 4.3.0
|
||||
@@ -2020,7 +2039,24 @@ packages:
|
||||
svelte: 3.58.0
|
||||
tiny-glob: 0.2.9
|
||||
undici: 5.20.0
|
||||
vite: 4.3.1(@types/node@18.16.0)(sass@1.61.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@sveltejs/vite-plugin-svelte@2.1.1(svelte@3.58.0):
|
||||
resolution: {integrity: sha512-7YeBDt4us0FiIMNsVXxyaP4Hwyn2/v9x3oqStkHU3ZdIc5O22pGwUwH33wUqYo+7Itdmo8zxJ45Qvfm3H7UUjQ==}
|
||||
engines: {node: ^14.18.0 || >= 16}
|
||||
peerDependencies:
|
||||
svelte: ^3.54.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
deepmerge: 4.3.1
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.0
|
||||
svelte: 3.58.0
|
||||
svelte-hmr: 0.15.1(svelte@3.58.0)
|
||||
vitefu: 0.2.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -2042,6 +2078,7 @@ packages:
|
||||
vitefu: 0.2.4(vite@4.3.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@ts-morph/common@0.18.1:
|
||||
resolution: {integrity: sha512-RVE+zSRICWRsfrkAw5qCAK+4ZH9kwEFv5h0+/YeHTLieWP7F4wWq4JsKFuNWG+fYh/KF+8rAtgdj5zb2mm+DVA==}
|
||||
@@ -4304,7 +4341,7 @@ packages:
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
import-local: 3.1.0
|
||||
jest-config: 29.5.0(@types/node@18.16.0)
|
||||
jest-config: 29.5.0
|
||||
jest-util: 29.5.0
|
||||
jest-validate: 29.5.0
|
||||
prompts: 2.4.2
|
||||
@@ -4315,6 +4352,44 @@ packages:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/jest-config@29.5.0:
|
||||
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
'@types/node': '*'
|
||||
ts-node: '>=9.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
ts-node:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/core': 7.21.4
|
||||
'@jest/test-sequencer': 29.5.0
|
||||
'@jest/types': 29.5.0
|
||||
babel-jest: 29.5.0(@babel/core@7.21.4)
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.8.0
|
||||
deepmerge: 4.3.1
|
||||
glob: 7.2.3
|
||||
graceful-fs: 4.2.11
|
||||
jest-circus: 29.5.0
|
||||
jest-environment-node: 29.5.0
|
||||
jest-get-type: 29.4.3
|
||||
jest-regex-util: 29.4.3
|
||||
jest-resolve: 29.5.0
|
||||
jest-runner: 29.5.0
|
||||
jest-util: 29.5.0
|
||||
jest-validate: 29.5.0
|
||||
micromatch: 4.0.5
|
||||
parse-json: 5.2.0
|
||||
pretty-format: 29.5.0
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/jest-config@29.5.0(@types/node@18.16.0):
|
||||
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -5894,7 +5969,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)(sass@1.61.0)(svelte@3.58.0):
|
||||
/svelte-check@2.2.6(postcss-load-config@3.1.1)(postcss@8.4.21)(svelte@3.58.0):
|
||||
resolution: {integrity: sha512-oJux/afbmcZO+N+ADXB88h6XANLie8Y2rh2qBlhgfkpr2c3t/q/T0w2JWrHqagaDL8zeNwO8a8RVFBkrRox8gg==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -5908,7 +5983,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)(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3)
|
||||
svelte-preprocess: 4.10.1(postcss-load-config@3.1.1)(postcss@8.4.21)(svelte@3.58.0)(typescript@5.0.3)
|
||||
typescript: 5.0.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -5988,7 +6063,7 @@ packages:
|
||||
tiny-glob: 0.2.9
|
||||
dev: false
|
||||
|
||||
/svelte-preprocess@4.10.1(postcss-load-config@3.1.1)(postcss@8.4.21)(sass@1.61.0)(svelte@3.58.0)(typescript@4.5.4):
|
||||
/svelte-preprocess@4.10.1(postcss-load-config@3.1.1)(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
|
||||
@@ -6035,14 +6110,13 @@ packages:
|
||||
magic-string: 0.25.9
|
||||
postcss: 8.4.21
|
||||
postcss-load-config: 3.1.1
|
||||
sass: 1.61.0
|
||||
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)(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3):
|
||||
/svelte-preprocess@4.10.1(postcss-load-config@3.1.1)(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
|
||||
@@ -6089,7 +6163,6 @@ packages:
|
||||
magic-string: 0.25.9
|
||||
postcss: 8.4.21
|
||||
postcss-load-config: 3.1.1
|
||||
sass: 1.61.0
|
||||
sorcery: 0.10.0
|
||||
strip-indent: 3.0.0
|
||||
svelte: 3.58.0
|
||||
@@ -6232,42 +6305,6 @@ packages:
|
||||
sucrase: 3.32.0
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
/tailwindcss@3.3.1(postcss@8.4.21):
|
||||
resolution: {integrity: sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
postcss: ^8.0.9
|
||||
dependencies:
|
||||
arg: 5.0.2
|
||||
chokidar: 3.5.3
|
||||
color-name: 1.1.4
|
||||
didyoumean: 1.2.2
|
||||
dlv: 1.1.3
|
||||
fast-glob: 3.2.12
|
||||
glob-parent: 6.0.2
|
||||
is-glob: 4.0.3
|
||||
jiti: 1.18.2
|
||||
lilconfig: 2.1.0
|
||||
micromatch: 4.0.5
|
||||
normalize-path: 3.0.0
|
||||
object-hash: 3.0.0
|
||||
picocolors: 1.0.0
|
||||
postcss: 8.4.21
|
||||
postcss-import: 14.1.0(postcss@8.4.21)
|
||||
postcss-js: 4.0.1(postcss@8.4.21)
|
||||
postcss-load-config: 3.1.4(postcss@8.4.21)
|
||||
postcss-nested: 6.0.0(postcss@8.4.21)
|
||||
postcss-selector-parser: 6.0.11
|
||||
postcss-value-parser: 4.2.0
|
||||
quick-lru: 5.1.1
|
||||
resolve: 1.22.2
|
||||
sucrase: 3.32.0
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
dev: true
|
||||
|
||||
/test-exclude@6.0.0:
|
||||
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
|
||||
@@ -6969,7 +7006,7 @@ packages:
|
||||
strip-ansi: 6.0.1
|
||||
tiny-invariant: 1.3.1
|
||||
typescript: 5.0.3
|
||||
vite: 4.2.1(sass@1.61.0)
|
||||
vite: 4.2.1
|
||||
vscode-languageclient: 7.0.0
|
||||
vscode-languageserver: 7.0.0
|
||||
vscode-languageserver-textdocument: 1.0.8
|
||||
@@ -6992,7 +7029,7 @@ packages:
|
||||
kolorist: 1.8.0
|
||||
magic-string: 0.29.0
|
||||
ts-morph: 17.0.1
|
||||
vite: 4.2.1(sass@1.61.0)
|
||||
vite: 4.2.1
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- rollup
|
||||
@@ -7026,7 +7063,7 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/vite@2.9.9(sass@1.61.0):
|
||||
/vite@2.9.9:
|
||||
resolution: {integrity: sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==}
|
||||
engines: {node: '>=12.2.0'}
|
||||
hasBin: true
|
||||
@@ -7046,12 +7083,11 @@ packages:
|
||||
postcss: 8.4.21
|
||||
resolve: 1.22.2
|
||||
rollup: 2.79.1
|
||||
sass: 1.61.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/vite@4.2.1(sass@1.61.0):
|
||||
/vite@4.2.1:
|
||||
resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
@@ -7080,10 +7116,41 @@ packages:
|
||||
postcss: 8.4.21
|
||||
resolve: 1.22.2
|
||||
rollup: 3.21.0
|
||||
sass: 1.61.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
/vite@4.3.1:
|
||||
resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': '>= 14'
|
||||
less: '*'
|
||||
sass: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
esbuild: 0.17.18
|
||||
postcss: 8.4.21
|
||||
rollup: 3.21.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/vite@4.3.1(@types/node@18.16.0)(sass@1.61.0):
|
||||
resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
@@ -7150,6 +7217,15 @@ packages:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
/vitefu@0.2.4:
|
||||
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
|
||||
peerDependencies:
|
||||
vite: ^3.0.0 || ^4.0.0
|
||||
peerDependenciesMeta:
|
||||
vite:
|
||||
optional: true
|
||||
dev: true
|
||||
|
||||
/vitefu@0.2.4(vite@4.3.1):
|
||||
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
|
||||
peerDependencies:
|
||||
@@ -7159,6 +7235,7 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 4.3.1(sass@1.61.0)
|
||||
dev: false
|
||||
|
||||
/vitest@0.25.8(sass@1.61.0):
|
||||
resolution: {integrity: sha512-X75TApG2wZTJn299E/TIYevr4E9/nBo1sUtZzn0Ci5oK8qnpZAZyhwg0qCeMSakGIWtc6oRwcQFyFfW14aOFWg==}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
packages:
|
||||
- 'gradio/js/*'
|
||||
- 'gradio/client/js'
|
||||
- 'litegraph/packages/core'
|
||||
- 'litegraph/packages/nodes-basic'
|
||||
- 'litegraph/packages/tsconfig'
|
||||
- 'litegraph/packages/*'
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<script lang="ts">
|
||||
import ComfyApp from "$lib/components/ComfyApp.svelte"
|
||||
import { default as ComfyAppState } from "$lib/components/ComfyApp"
|
||||
import "@litegraph-ts/core/css/litegraph.css";
|
||||
import "./scss/global.scss";
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const app = new ComfyAppState();
|
||||
</script>
|
||||
|
||||
<ComfyApp/>
|
||||
<ComfyApp {app}/>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,7 @@ import uiState from "./stores/uiState";
|
||||
import { get } from "svelte/store";
|
||||
import type ComfyGraphNode from "./nodes/ComfyGraphNode";
|
||||
import type IComfyInputSlot from "./IComfyInputSlot";
|
||||
import type { ComfyBackendNode } from "./nodes/ComfyBackendNode";
|
||||
|
||||
type ComfyGraphEvents = {
|
||||
configured: (graph: LGraph) => void
|
||||
@@ -16,6 +17,7 @@ type ComfyGraphEvents = {
|
||||
cleared: () => void
|
||||
beforeChange: (graph: LGraph, param: any) => void
|
||||
afterChange: (graph: LGraph, param: any) => void
|
||||
afterExecute: () => void
|
||||
}
|
||||
|
||||
export default class ComfyGraph extends LGraph {
|
||||
@@ -42,6 +44,10 @@ export default class ComfyGraph extends LGraph {
|
||||
this.eventBus.emit("afterChange", graph, info);
|
||||
}
|
||||
|
||||
override onAfterExecute() {
|
||||
this.eventBus.emit("afterExecute");
|
||||
}
|
||||
|
||||
override onNodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
|
||||
layoutState.nodeAdded(node)
|
||||
this.graphSync.onNodeAdded(node);
|
||||
@@ -51,7 +57,7 @@ export default class ComfyGraph extends LGraph {
|
||||
&& !options.addedByDeserialize // ...and we're not trying to deserialize an existing workflow
|
||||
&& get(uiState).autoAddUI) {
|
||||
console.debug("[ComfyGraph] AutoAdd UI")
|
||||
const comfyNode = node as ComfyGraphNode;
|
||||
const comfyNode = node as ComfyBackendNode;
|
||||
const widgetNodesAdded = []
|
||||
for (let index = 0; index < comfyNode.inputs.length; index++) {
|
||||
const input = comfyNode.inputs[index];
|
||||
@@ -70,7 +76,7 @@ export default class ComfyGraph extends LGraph {
|
||||
}
|
||||
const dragItems = widgetNodesAdded.map(wn => get(layoutState).allItemsByNode[wn.id]?.dragItem).filter(di => di)
|
||||
console.debug("[ComfyGraph] Group new widgets", dragItems)
|
||||
layoutState.groupItems(dragItems, comfyNode.comfyClass)
|
||||
layoutState.groupItems(dragItems, { title: comfyNode.comfyClass })
|
||||
}
|
||||
|
||||
console.debug("Added", node);
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
gap: var(--layout-gap);
|
||||
width: var(--size-full);
|
||||
|
||||
.v-pane {
|
||||
> :global(.block > .v-pane) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
&.vertical {
|
||||
position: relative;
|
||||
|
||||
.v-pane {
|
||||
> :global(.block > .v-pane) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
import ComfyQueue from "./ComfyQueue.svelte";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
|
||||
let app: ComfyApp = undefined;
|
||||
export let app: ComfyApp = undefined;
|
||||
let imageViewer: ImageViewer;
|
||||
let queue: ComfyQueue = undefined;
|
||||
let mainElem: HTMLDivElement;
|
||||
let uiPane: ComfyUIPane = undefined;
|
||||
let containerElem: HTMLDivElement;
|
||||
let resizeTimeout: NodeJS.Timeout | null;
|
||||
let hasShownUIHelpToast: boolean = false;
|
||||
|
||||
let debugLayout: boolean = true;
|
||||
|
||||
@@ -44,8 +45,8 @@
|
||||
app.queuePrompt(0, 1);
|
||||
}
|
||||
|
||||
$: if (app) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
|
||||
$: if (app) app.lCanvas.allow_interaction = !$uiState.graphLocked;
|
||||
$: if (app?.lCanvas) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
|
||||
$: if (app?.lCanvas) app.lCanvas.allow_interaction = !$uiState.graphLocked;
|
||||
|
||||
$: if ($uiState.uiEditMode)
|
||||
$layoutState.currentSelection = []
|
||||
@@ -98,27 +99,26 @@
|
||||
app.lCanvas.recenter();
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
app = new ComfyApp();
|
||||
$: if ($uiState.uiEditMode !== "disabled" && !hasShownUIHelpToast) {
|
||||
hasShownUIHelpToast = true;
|
||||
toast.push("Right-click to open context menu.")
|
||||
}
|
||||
|
||||
if (debugLayout) {
|
||||
layoutState.subscribe(s => {
|
||||
console.warn("UPDATESTATE", s)
|
||||
})
|
||||
}
|
||||
if (debugLayout) {
|
||||
layoutState.subscribe(s => {
|
||||
console.warn("UPDATESTATE", s)
|
||||
})
|
||||
}
|
||||
|
||||
app.api.addEventListener("status", (ev: CustomEvent) => {
|
||||
queueState.statusUpdated(ev.detail as ComfyAPIStatus);
|
||||
});
|
||||
|
||||
await app.setup();
|
||||
(window as any).app = app;
|
||||
(window as any).appPane = uiPane;
|
||||
|
||||
refreshView();
|
||||
app.api.addEventListener("status", (ev: CustomEvent) => {
|
||||
queueState.statusUpdated(ev.detail as ComfyAPIStatus);
|
||||
});
|
||||
|
||||
$: if (app.rootEl && !imageViewer) {
|
||||
imageViewer = new ImageViewer(app.rootEl);
|
||||
}
|
||||
|
||||
$: if (containerElem) {
|
||||
let wrappers = containerElem.querySelectorAll<HTMLDivElement>(".pane-wrapper")
|
||||
for (const wrapper of wrappers) {
|
||||
const paneNode = wrapper.parentNode as HTMLElement; // get the node inside the <Pane/>
|
||||
@@ -126,6 +126,14 @@
|
||||
app.resizeCanvas()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await app.setup();
|
||||
(window as any).app = app;
|
||||
(window as any).appPane = uiPane;
|
||||
|
||||
refreshView();
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LiteGraph, LGraph, LGraphCanvas, LGraphNode, type LGraphNodeConstructor, type LGraphNodeExecutable, type SerializedLGraph, type SerializedLGraphGroup, type SerializedLGraphNode, type SerializedLLink, NodeMode, type Vector2 } from "@litegraph-ts/core";
|
||||
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 { ComfyWidgets } from "$lib/widgets"
|
||||
@@ -9,7 +9,9 @@ import type TypedEmitter from "typed-emitter";
|
||||
|
||||
// Import nodes
|
||||
import "@litegraph-ts/nodes-basic"
|
||||
import "@litegraph-ts/nodes-events"
|
||||
import * as nodes from "$lib/nodes/index"
|
||||
|
||||
import ComfyGraphCanvas, { type SerializedGraphCanvasState } from "$lib/ComfyGraphCanvas";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import * as widgets from "$lib/widgets/index"
|
||||
@@ -20,6 +22,7 @@ import type { SerializedLayoutState } from "$lib/stores/layoutState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import ComfyGraph from "$lib/ComfyGraph";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
|
||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||
|
||||
@@ -61,12 +64,18 @@ export default class ComfyApp {
|
||||
|
||||
private queueItems: QueueItem[] = [];
|
||||
private processingQueue: boolean = false;
|
||||
private alreadySetup = false;
|
||||
|
||||
constructor() {
|
||||
this.api = new ComfyAPI();
|
||||
}
|
||||
|
||||
async setup(): Promise<void> {
|
||||
if (this.alreadySetup) {
|
||||
console.error("Already setup")
|
||||
return;
|
||||
}
|
||||
|
||||
this.rootEl = document.getElementById("main") as HTMLDivElement;
|
||||
this.canvasEl = document.getElementById("graph-canvas") as HTMLCanvasElement;
|
||||
this.lGraph = new ComfyGraph();
|
||||
@@ -76,11 +85,7 @@ export default class ComfyApp {
|
||||
LiteGraph.release_link_on_empty_shows_menu = true;
|
||||
LiteGraph.alt_drag_do_clone_nodes = true;
|
||||
|
||||
this.lGraph.start();
|
||||
|
||||
// await this.#invokeExtensionsAsync("init");
|
||||
this.registerNodeTypeOverrides();
|
||||
this.registerWidgetTypeOverrides();
|
||||
await this.registerNodes();
|
||||
|
||||
// Load previous workflow
|
||||
@@ -109,10 +114,7 @@ export default class ComfyApp {
|
||||
this.addPasteHandler();
|
||||
this.addKeyboardHandler();
|
||||
|
||||
// Distinguish frontend/backend connections
|
||||
const BACKEND_TYPES = ["CLIP", "CLIP_VISION", "CLIP_VISION_OUTPUT", "CONDITIONING", "CONTROL_NET", "IMAGE", "LATENT", "MASK", "MODEL", "STYLE_MODEL", "VAE"]
|
||||
for (const type of BACKEND_TYPES)
|
||||
LGraphCanvas.link_type_colors[type] = "orange" // yellow
|
||||
this.setupColorScheme()
|
||||
|
||||
// await this.#invokeExtensionsAsync("setup");
|
||||
|
||||
@@ -120,6 +122,11 @@ export default class ComfyApp {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -137,32 +144,14 @@ export default class ComfyApp {
|
||||
localStorage.setItem("workflow", json)
|
||||
}
|
||||
|
||||
static node_type_overrides: Record<string, typeof ComfyGraphNode> = {}
|
||||
static node_type_overrides: Record<string, typeof ComfyBackendNode> = {}
|
||||
static widget_type_overrides: Record<string, typeof SvelteComponentDev> = {}
|
||||
|
||||
private registerNodeTypeOverrides() {
|
||||
ComfyApp.node_type_overrides["SaveImage"] = nodes.ComfySaveImageNode;
|
||||
ComfyApp.node_type_overrides["PreviewImage"] = nodes.ComfyPreviewImageNode;
|
||||
}
|
||||
|
||||
private registerWidgetTypeOverrides() {
|
||||
ComfyApp.widget_type_overrides["comfy/gallery"] = widgets.ComfyGalleryWidget_Svelte;
|
||||
}
|
||||
|
||||
private async registerNodes() {
|
||||
const app = this;
|
||||
|
||||
// Load node definitions from the backend
|
||||
const defs = await this.api.getNodeDefs();
|
||||
// await this.#invokeExtensionsAsync("addCustomNodeDefs", defs);
|
||||
|
||||
// Generate list of known widgets
|
||||
const widgets = ComfyWidgets;
|
||||
// const widgets = Object.assign(
|
||||
// {},
|
||||
// ComfyWidgets,
|
||||
// ...(await this.#invokeExtensionsAsync("getCustomWidgets")).filter(Boolean)
|
||||
// );
|
||||
|
||||
// Register a node for each definition
|
||||
for (const nodeId in defs) {
|
||||
@@ -171,60 +160,11 @@ export default class ComfyApp {
|
||||
const typeOverride = ComfyApp.node_type_overrides[nodeId]
|
||||
if (typeOverride)
|
||||
console.debug("Attaching custom type to received node:", nodeId, typeOverride)
|
||||
const baseClass: typeof LGraphNode = typeOverride || LGraphNode;
|
||||
const baseClass: typeof ComfyBackendNode = typeOverride || ComfyBackendNode;
|
||||
|
||||
const ctor = class extends baseClass {
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
this.type = nodeId; // XXX: workaround dependency in LGraphNode.addInput()
|
||||
(this as any).comfyClass = nodeId;
|
||||
(this as any).isBackendNode = true;
|
||||
const color = LGraphCanvas.node_colors["yellow"];
|
||||
this.color = color.color
|
||||
this.bgColor = color.bgColor
|
||||
var inputs = nodeData["input"]["required"];
|
||||
if (nodeData["input"]["optional"] != undefined) {
|
||||
inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"])
|
||||
}
|
||||
const config = { minWidth: 1, minHeight: 1 };
|
||||
for (const inputName in inputs) {
|
||||
const inputData = inputs[inputName];
|
||||
const type = inputData[0];
|
||||
|
||||
if (inputData[1]?.forceInput) {
|
||||
this.addInput(inputName, type);
|
||||
} else {
|
||||
if (Array.isArray(type)) {
|
||||
// Enums
|
||||
Object.assign(config, widgets.COMBO(this, inputName, inputData, app) || {});
|
||||
} else if (`${type}:${inputName}` in widgets) {
|
||||
// Support custom widgets by Type:Name
|
||||
Object.assign(config, widgets[`${type}:${inputName}`](this, inputName, inputData, app) || {});
|
||||
} else if (type in widgets) {
|
||||
// Standard type widgets
|
||||
Object.assign(config, widgets[type](this, inputName, inputData, app) || {});
|
||||
} else {
|
||||
// Node connection inputs (backend)
|
||||
this.addInput(inputName, type, { color_off: "orange", color_on: "orange" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const o in nodeData["output"]) {
|
||||
const output = nodeData["output"][o];
|
||||
const outputName = nodeData["output_name"][o] || output;
|
||||
this.addOutput(outputName, output, { color_off: "orange", color_on: "orange" });
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return this;
|
||||
super(title, nodeId, nodeData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,15 +175,9 @@ export default class ComfyApp {
|
||||
desc: `ComfyNode: ${nodeId}`
|
||||
}
|
||||
|
||||
// this.#addNodeContextMenuHandler(node);
|
||||
// this.#addDrawBackgroundHandler(node, app);
|
||||
|
||||
// await this.#invokeExtensionsAsync("beforeRegisterNodeDef", node, nodeData);
|
||||
LiteGraph.registerNodeType(node);
|
||||
node.category = nodeData.category;
|
||||
}
|
||||
|
||||
// await this.#invokeExtensionsAsync("registerCustomNodes");
|
||||
}
|
||||
|
||||
private showDropZone() {
|
||||
@@ -363,6 +297,23 @@ 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
|
||||
}
|
||||
|
||||
// Distinguish frontend/backend connections
|
||||
const BACKEND_TYPES = ["CLIP", "CLIP_VISION", "CLIP_VISION_OUTPUT", "CONDITIONING", "CONTROL_NET", "IMAGE", "LATENT", "MASK", "MODEL", "STYLE_MODEL", "VAE"]
|
||||
for (const type of BACKEND_TYPES) {
|
||||
setColor(type, "orange")
|
||||
}
|
||||
|
||||
setColor("OUTPUT", "rebeccapurple")
|
||||
setColor(BuiltInSlotType.EVENT, "lightseagreen")
|
||||
setColor(BuiltInSlotType.ACTION, "lightseagreen")
|
||||
}
|
||||
|
||||
serialize(): SerializedAppState {
|
||||
const graph = this.lGraph;
|
||||
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
// TODO
|
||||
}
|
||||
|
||||
function groupWidgets() {
|
||||
function groupWidgets(horizontal: boolean) {
|
||||
const items = layoutState.getCurrentSelection()
|
||||
$layoutState.currentSelection = []
|
||||
layoutState.groupItems(items)
|
||||
layoutState.groupItems(items, { direction: horizontal ? "horizontal" : "vertical" })
|
||||
}
|
||||
|
||||
let canUngroup = false;
|
||||
@@ -95,8 +95,12 @@
|
||||
<Menu {...menuPos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
||||
<MenuOption
|
||||
isDisabled={$layoutState.currentSelection.length === 0}
|
||||
on:click={groupWidgets}
|
||||
on:click={() => groupWidgets(false)}
|
||||
text="Group" />
|
||||
<MenuOption
|
||||
isDisabled={$layoutState.currentSelection.length === 0}
|
||||
on:click={() => groupWidgets(true)}
|
||||
text="Group Horizontally" />
|
||||
<MenuOption
|
||||
isDisabled={!canUngroup}
|
||||
on:click={ungroup}
|
||||
|
||||
@@ -56,6 +56,9 @@
|
||||
.widget.selected {
|
||||
background: var(--color-yellow-200);
|
||||
}
|
||||
.container.selected {
|
||||
background: var(--color-yellow-400);
|
||||
}
|
||||
|
||||
.is-executing {
|
||||
border: 3px dashed var(--color-green-600) !important;
|
||||
|
||||
53
src/lib/nodes/ComfyActionNodes.ts
Normal file
53
src/lib/nodes/ComfyActionNodes.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { LiteGraph, type ContextMenuItem, type LGraphNode, type Vector2, LConnectionKind, LLink, LGraphCanvas, type SlotType, TitleMode, type SlotLayout, BuiltInSlotType, type ITextWidget } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
import { Watch } from "@litegraph-ts/nodes-basic";
|
||||
|
||||
export interface ComfyCopyActionProperties extends Record<any, any> {
|
||||
value: any
|
||||
}
|
||||
|
||||
export class ComfyCopyAction extends ComfyGraphNode {
|
||||
override properties: ComfyCopyActionProperties = {
|
||||
value: null
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
{ name: "in", type: "*" },
|
||||
{ name: "copy", type: BuiltInSlotType.ACTION }
|
||||
],
|
||||
outputs: [
|
||||
{ name: "out", type: "*" }
|
||||
],
|
||||
}
|
||||
|
||||
displayWidget: ITextWidget;
|
||||
|
||||
constructor(title?: string) {
|
||||
super(title);
|
||||
this.displayWidget = this.addWidget<ITextWidget>(
|
||||
"text",
|
||||
"Value",
|
||||
"",
|
||||
"value"
|
||||
);
|
||||
this.displayWidget.disabled = true;
|
||||
}
|
||||
|
||||
override onExecute() {
|
||||
this.setProperty("value", this.getInputData(0))
|
||||
}
|
||||
|
||||
override onAction(action: any, param: any) {
|
||||
this.setProperty("value", this.getInputData(0))
|
||||
this.setOutputData(0, this.properties.value)
|
||||
console.log("setData", this.properties.value)
|
||||
};
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfyCopyAction,
|
||||
title: "Comfy.CopyAction",
|
||||
desc: "Copies its input to its output when an event is received",
|
||||
type: "actions/copy"
|
||||
})
|
||||
92
src/lib/nodes/ComfyBackendNode.ts
Normal file
92
src/lib/nodes/ComfyBackendNode.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
import ComfyWidgets from "$lib/widgets"
|
||||
import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
|
||||
|
||||
/*
|
||||
* Base class for any node with configuration sent by the backend.
|
||||
*/
|
||||
export class ComfyBackendNode extends ComfyGraphNode {
|
||||
comfyClass: string;
|
||||
|
||||
constructor(title: string, comfyClass: string, nodeData: any) {
|
||||
super(title)
|
||||
this.type = comfyClass; // XXX: workaround dependency in LGraphNode.addInput()
|
||||
this.comfyClass = comfyClass;
|
||||
this.isBackendNode = true;
|
||||
|
||||
const color = LGraphCanvas.node_colors["yellow"];
|
||||
this.color = color.color
|
||||
this.bgColor = color.bgColor
|
||||
|
||||
this.setup(nodeData)
|
||||
|
||||
// ComfyUI has no obvious way to identify if a node will return outputs back to the frontend based on its properties.
|
||||
// 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", "OUTPUT");
|
||||
}
|
||||
}
|
||||
|
||||
private setup(nodeData: any) {
|
||||
var inputs = nodeData["input"]["required"];
|
||||
if (nodeData["input"]["optional"] != undefined) {
|
||||
inputs = Object.assign({}, nodeData["input"]["required"], nodeData["input"]["optional"])
|
||||
}
|
||||
|
||||
const config = { minWidth: 1, minHeight: 1 };
|
||||
for (const inputName in inputs) {
|
||||
const inputData = inputs[inputName];
|
||||
const type = inputData[0];
|
||||
|
||||
if (inputData[1]?.forceInput) {
|
||||
this.addInput(inputName, type);
|
||||
} else {
|
||||
if (Array.isArray(type)) {
|
||||
// Enums
|
||||
Object.assign(config, ComfyWidgets.COMBO(this, inputName, inputData) || {});
|
||||
} else if (`${type}:${inputName}` in ComfyWidgets) {
|
||||
// Support custom ComfyWidgets by Type:Name
|
||||
Object.assign(config, ComfyWidgets[`${type}:${inputName}`](this, inputName, inputData) || {});
|
||||
} else if (type in ComfyWidgets) {
|
||||
// Standard type ComfyWidgets
|
||||
Object.assign(config, ComfyWidgets[type](this, inputName, inputData) || {});
|
||||
} else {
|
||||
// Node connection inputs (backend)
|
||||
this.addInput(inputName, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const o in nodeData["output"]) {
|
||||
const output = nodeData["output"][o];
|
||||
const outputName = nodeData["output_name"][o] || output;
|
||||
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 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 === "OUTPUT") {
|
||||
this.setOutputData(index, outputData)
|
||||
for (const node of this.getOutputNodes(index)) {
|
||||
if ("receiveOutput" in node) {
|
||||
const widgetNode = node as ComfyWidgetNode;
|
||||
widgetNode.receiveOutput();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import type ComfyWidget from "$lib/components/widgets/ComfyWidget";
|
||||
import { LGraph, LGraphNode } from "@litegraph-ts/core";
|
||||
|
||||
export default class ComfyGraphNode extends LGraphNode {
|
||||
comfyClass: string | null
|
||||
isBackendNode?: boolean;
|
||||
|
||||
afterQueued?(): void;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import ComfyGalleryWidget, { type ComfyGalleryEntry } from "$lib/widgets/ComfyGalleryWidget";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
|
||||
export type ComfyImageResult = {
|
||||
filename: string,
|
||||
subfolder: string,
|
||||
type: "output" | "temp"
|
||||
}
|
||||
export type ComfyImageExecOutput = {
|
||||
images: ComfyImageResult[]
|
||||
}
|
||||
|
||||
/*
|
||||
* Node with a single extra image output widget
|
||||
*/
|
||||
class ComfyImageNode extends ComfyGraphNode {
|
||||
private _imageResults: Array<ComfyImageResult> = [];
|
||||
private _galleryWidget: ComfyGalleryWidget;
|
||||
|
||||
constructor(title?: any) {
|
||||
super(title)
|
||||
this._galleryWidget = new ComfyGalleryWidget("Images", [], this);
|
||||
this.addCustomWidget(this._galleryWidget);
|
||||
}
|
||||
|
||||
override onExecuted(output: ComfyImageExecOutput) {
|
||||
this._imageResults = Array.from(output.images); // TODO append?
|
||||
const galleryItems = this._imageResults.map(r => {
|
||||
// TODO
|
||||
const url = "http://localhost:8188/view?"
|
||||
const params = new URLSearchParams(r)
|
||||
let entry: ComfyGalleryEntry = [url + params, null]
|
||||
return entry
|
||||
});
|
||||
this._galleryWidget.addImages(galleryItems);
|
||||
}
|
||||
}
|
||||
|
||||
export class ComfySaveImageNode extends ComfyImageNode {
|
||||
}
|
||||
|
||||
export class ComfyPreviewImageNode extends ComfyImageNode {
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
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 } from "@litegraph-ts/core";
|
||||
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 } from "@litegraph-ts/core";
|
||||
import ComfyGraphNode from "./ComfyGraphNode";
|
||||
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 type { SvelteComponentDev } from "svelte/internal";
|
||||
import { ComfyWidgets } from "$lib/widgets";
|
||||
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 } from "$lib/utils"
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
|
||||
export interface ComfyWidgetProperties extends Record<string, any> {
|
||||
defaultValue: any
|
||||
@@ -36,6 +39,10 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
override isBackendNode = false;
|
||||
override serialize_widgets = true;
|
||||
|
||||
outputIndex: number = 0;
|
||||
inputIndex: number = 0;
|
||||
changedIndex: number = 1;
|
||||
|
||||
displayWidget: ITextWidget;
|
||||
|
||||
override size: Vector2 = [60, 40];
|
||||
@@ -55,24 +62,47 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
this.unsubscribe = this.value.subscribe(this.onValueUpdated.bind(this))
|
||||
}
|
||||
|
||||
formatValue(value: any): string {
|
||||
return Watch.toString(value)
|
||||
}
|
||||
|
||||
private onValueUpdated(value: any) {
|
||||
console.debug("[Widget] valueUpdated", this, value)
|
||||
this.displayWidget.value = Watch.toString(value)
|
||||
this.displayWidget.value = this.formatValue(value)
|
||||
|
||||
if (this.outputs.length >= this.outputIndex) {
|
||||
this.setOutputData(this.outputIndex, get(this.value))
|
||||
}
|
||||
if (this.outputs.length >= this.changedIndex) {
|
||||
const changedOutput = this.outputs[this.changedIndex]
|
||||
if (changedOutput.type === BuiltInSlotType.EVENT)
|
||||
this.triggerSlot(this.changedIndex, "changed")
|
||||
}
|
||||
}
|
||||
|
||||
setValue(value: any) {
|
||||
this.value.set(value)
|
||||
}
|
||||
|
||||
abstract validateValue(value: any): boolean;
|
||||
|
||||
/*
|
||||
* Logic to run if this widget can be treated as output (slider, combo, text)
|
||||
*/
|
||||
override onExecute() {
|
||||
// Assumption: we will have one output in the inherited class with the
|
||||
// correct type
|
||||
this.setOutputData(0, get(this.value))
|
||||
if (this.inputs.length >= this.inputIndex) {
|
||||
const data = this.getInputData(this.inputIndex)
|
||||
if (data && this.validateValue(data)) { // TODO can "null" be a legitimate value here?
|
||||
this.setValue(data)
|
||||
}
|
||||
}
|
||||
if (this.outputs.length >= this.outputIndex) {
|
||||
this.setOutputData(this.outputIndex, get(this.value))
|
||||
}
|
||||
}
|
||||
|
||||
const outputLinks = this.getOutputLinks(0)
|
||||
console.debug("[Widget] onExecute", this, outputLinks)
|
||||
|
||||
// TODO send event to linked nodes
|
||||
/** Called when a backend node sends a ComfyUI output over a link */
|
||||
receiveOutput() {
|
||||
}
|
||||
|
||||
onConnectOutput(
|
||||
@@ -93,7 +123,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
|
||||
this.setValue(this.properties.defaultValue)
|
||||
|
||||
const widget = layoutState.findLayoutForNode(this.id)
|
||||
if (widget) {
|
||||
if (widget && input.name !== "") {
|
||||
widget.attrs.title = input.name;
|
||||
}
|
||||
|
||||
@@ -164,8 +194,12 @@ export class ComfySliderNode extends ComfyWidgetNode<number> {
|
||||
override svelteComponentType = RangeWidget
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
outputs: [
|
||||
inputs: [
|
||||
{ name: "value", type: "number" }
|
||||
],
|
||||
outputs: [
|
||||
{ name: "value", type: "number" },
|
||||
{ name: "changed", type: BuiltInSlotType.EVENT }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -173,6 +207,12 @@ export class ComfySliderNode extends ComfyWidgetNode<number> {
|
||||
super(name, 0)
|
||||
}
|
||||
|
||||
override validateValue(value: any): boolean {
|
||||
return typeof value === "number"
|
||||
&& value >= this.properties.min
|
||||
&& value <= this.properties.max
|
||||
}
|
||||
|
||||
override clampOneConfig(input: IComfyInputSlot) {
|
||||
// this.setProperty("min", clamp(this.properties.min, input.config.min, input.config.max))
|
||||
// this.setProperty("max", clamp(this.properties.max, input.config.max, input.config.min))
|
||||
@@ -199,8 +239,12 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
outputs: [
|
||||
inputs: [
|
||||
{ name: "value", type: "string" }
|
||||
],
|
||||
outputs: [
|
||||
{ name: "value", type: "string" },
|
||||
{ name: "changed", type: BuiltInSlotType.EVENT }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -236,6 +280,12 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
return true;
|
||||
}
|
||||
|
||||
override validateValue(value: any): boolean {
|
||||
if (typeof value !== "string")
|
||||
return false;
|
||||
return this.properties.values.indexOf(value) !== -1;
|
||||
}
|
||||
|
||||
override clampOneConfig(input: IComfyInputSlot) {
|
||||
if (input.config.values.indexOf(this.properties.value) === -1) {
|
||||
if (input.config.values.length === 0)
|
||||
@@ -264,8 +314,12 @@ export class ComfyTextNode extends ComfyWidgetNode<string> {
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
outputs: [
|
||||
inputs: [
|
||||
{ name: "value", type: "string" }
|
||||
],
|
||||
outputs: [
|
||||
{ name: "value", type: "string" },
|
||||
{ name: "changed", type: BuiltInSlotType.EVENT }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -274,6 +328,10 @@ export class ComfyTextNode extends ComfyWidgetNode<string> {
|
||||
constructor(name?: string) {
|
||||
super(name, "")
|
||||
}
|
||||
|
||||
override validateValue(value: any): boolean {
|
||||
return typeof value === "string"
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
@@ -282,3 +340,121 @@ LiteGraph.registerNodeType({
|
||||
desc: "Textbox outputting a string value",
|
||||
type: "ui/text"
|
||||
})
|
||||
|
||||
/** Raw output as received from ComfyUI's backend */
|
||||
export type GalleryOutput = {
|
||||
images: GalleryOutputEntry[]
|
||||
}
|
||||
|
||||
/** Raw output entry as received from ComfyUI's backend */
|
||||
export type GalleryOutputEntry = {
|
||||
filename: string,
|
||||
subfolder: string,
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface ComfyGalleryProperties extends ComfyWidgetProperties {
|
||||
}
|
||||
|
||||
export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
|
||||
override properties: ComfyGalleryProperties = {
|
||||
defaultValue: []
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
inputs: [
|
||||
{ name: "images", type: "OUTPUT" }
|
||||
]
|
||||
}
|
||||
|
||||
override svelteComponentType = GalleryWidget
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name, [])
|
||||
}
|
||||
|
||||
override afterQueued() {
|
||||
let queue = get(queueState)
|
||||
if (!(typeof queue.queueRemaining === "number" && queue.queueRemaining > 1)) {
|
||||
this.setValue([])
|
||||
}
|
||||
}
|
||||
|
||||
override formatValue(value: GradioFileData[]): string {
|
||||
return `Images: ${value.length}`
|
||||
}
|
||||
|
||||
override validateValue(value: any): boolean {
|
||||
return Array.isArray(value) && value.every(e => "images" in e)
|
||||
}
|
||||
|
||||
receiveOutput() {
|
||||
const link = this.getInputLink(0)
|
||||
if (link.data && "images" in link.data) {
|
||||
const data = link.data as GalleryOutput
|
||||
console.debug("[ComfyGalleryNode] Received output!", data)
|
||||
|
||||
const galleryItems: GradioFileData[] = data.images.map(r => {
|
||||
// TODO configure backend URL
|
||||
const url = "http://localhost:8188/view?"
|
||||
const params = new URLSearchParams(r)
|
||||
return {
|
||||
name: null,
|
||||
data: url + params
|
||||
}
|
||||
});
|
||||
|
||||
const currentValue = get(this.value)
|
||||
this.setValue(currentValue.concat(galleryItems))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfyGalleryNode,
|
||||
title: "UI.Gallery",
|
||||
desc: "Gallery that shows most recent outputs",
|
||||
type: "ui/gallery"
|
||||
})
|
||||
|
||||
export interface ComfyButtonProperties extends ComfyWidgetProperties {
|
||||
message: string
|
||||
}
|
||||
|
||||
export class ComfyButtonNode extends ComfyWidgetNode<boolean> {
|
||||
override properties: ComfyButtonProperties = {
|
||||
defaultValue: false,
|
||||
message: "bang"
|
||||
}
|
||||
|
||||
static slotLayout: SlotLayout = {
|
||||
outputs: [
|
||||
{ name: "event", type: BuiltInSlotType.EVENT },
|
||||
{ name: "isClicked", type: "boolean" },
|
||||
]
|
||||
}
|
||||
|
||||
override outputIndex = 1;
|
||||
override svelteComponentType = ButtonWidget;
|
||||
|
||||
override validateValue(value: any): boolean {
|
||||
return typeof value === "boolean"
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.setValue(true)
|
||||
this.triggerSlot(0, this.properties.message);
|
||||
this.setValue(false)
|
||||
}
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name, false)
|
||||
}
|
||||
}
|
||||
|
||||
LiteGraph.registerNodeType({
|
||||
class: ComfyButtonNode,
|
||||
title: "UI.Button",
|
||||
desc: "Button that triggers an event when clicked",
|
||||
type: "ui/button"
|
||||
})
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { default as ComfyReroute } from "./ComfyReroute"
|
||||
export { ComfySaveImageNode, ComfyPreviewImageNode } from "./ComfyImageNodes"
|
||||
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
|
||||
export { ComfyCopyAction } from "./ComfyActionNodes"
|
||||
|
||||
@@ -64,7 +64,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
||||
export { ALL_ATTRIBUTES };
|
||||
|
||||
export type Attributes = {
|
||||
direction: string,
|
||||
direction: "horizontal" | "vertical",
|
||||
title: string,
|
||||
showTitle: boolean,
|
||||
classes: string
|
||||
@@ -95,7 +95,7 @@ type LayoutStateOps = {
|
||||
updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[],
|
||||
nodeAdded: (node: LGraphNode) => void,
|
||||
nodeRemoved: (node: LGraphNode) => void,
|
||||
groupItems: (dragItems: IDragItem[], title: string) => ContainerLayout,
|
||||
groupItems: (dragItems: IDragItem[], attrs?: Partial<Attributes>) => ContainerLayout,
|
||||
ungroup: (container: ContainerLayout) => void,
|
||||
getCurrentSelection: () => IDragItem[],
|
||||
findLayoutForNode: (nodeId: number) => IDragItem | null;
|
||||
@@ -265,7 +265,7 @@ function moveItem(target: IDragItem, to: ContainerLayout, index: number = -1) {
|
||||
|
||||
if (entry.parent) {
|
||||
const parentEntry = state.allItems[entry.parent.id];
|
||||
const index = parentEntry.children.indexOf(target)
|
||||
const index = parentEntry.children.findIndex(c => c.id === target.id)
|
||||
if (index !== -1) {
|
||||
parentEntry.children.splice(index, 1)
|
||||
}
|
||||
@@ -293,7 +293,7 @@ function getCurrentSelection(): IDragItem[] {
|
||||
return state.currentSelection.map(id => state.allItems[id].dragItem)
|
||||
}
|
||||
|
||||
function groupItems(dragItems: IDragItem[], title: string = "Group"): ContainerLayout {
|
||||
function groupItems(dragItems: IDragItem[], attrs: Partial<Attributes> = {}): ContainerLayout {
|
||||
if (dragItems.length === 0)
|
||||
return;
|
||||
|
||||
@@ -305,17 +305,19 @@ function groupItems(dragItems: IDragItem[], title: string = "Group"): ContainerL
|
||||
|
||||
let index = undefined;
|
||||
if (parent) {
|
||||
const indexFound = state.allItems[parent.id].children.indexOf(dragItems[0])
|
||||
const indexFound = state.allItems[parent.id].children.findIndex(c => c.id === dragItems[0].id)
|
||||
if (indexFound !== -1)
|
||||
index = indexFound
|
||||
}
|
||||
|
||||
const container = addContainer(parent as ContainerLayout, { title }, index)
|
||||
const container = addContainer(parent as ContainerLayout, attrs, index)
|
||||
|
||||
for (const item of dragItems) {
|
||||
moveItem(item, container)
|
||||
}
|
||||
|
||||
console.debug("[layoutState] Grouped", container, parent, state.allItems[container.id].children, index)
|
||||
|
||||
store.set(state)
|
||||
return container
|
||||
}
|
||||
@@ -331,7 +333,7 @@ function ungroup(container: ContainerLayout) {
|
||||
|
||||
let index = undefined;
|
||||
const parentChildren = state.allItems[parent.id].children;
|
||||
const indexFound = parentChildren.indexOf(container)
|
||||
const indexFound = parentChildren.findIndex(c => c.id === container.id)
|
||||
if (indexFound !== -1)
|
||||
index = indexFound
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { IWidget, LGraphNode } from "@litegraph-js/core";
|
||||
import type ComfyApp from "$lib/components/ComfyApp";
|
||||
import ComfyValueControlWidget from "./widgets/ComfyValueControlWidget";
|
||||
import type { ComfyInputConfig } from "./IComfyInputSlot";
|
||||
import type IComfyInputSlot from "./IComfyInputSlot";
|
||||
import { BuiltInSlotShape } from "@litegraph-ts/core";
|
||||
import { ComfyComboNode, ComfySliderNode, ComfyTextNode } from "./nodes";
|
||||
|
||||
type WidgetFactory = (node: LGraphNode, inputName: string, inputData: any, app: ComfyApp) => IComfyInputSlot;
|
||||
type WidgetFactory = (node: LGraphNode, inputName: string, inputData: any) => IComfyInputSlot;
|
||||
|
||||
function getNumberDefaults(inputData: any, defaultStep: number): ComfyInputConfig {
|
||||
let defaultValue = inputData[1]["default"];
|
||||
@@ -38,7 +37,7 @@ const INT: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any)
|
||||
return addComfyInput(node, inputName, { type: "number", config, defaultWidgetNode: ComfySliderNode })
|
||||
};
|
||||
|
||||
const STRING: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any, app: ComfyApp): IComfyInputSlot => {
|
||||
const STRING: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any): IComfyInputSlot => {
|
||||
const defaultValue = inputData[1].default || "";
|
||||
const multiline = !!inputData[1].multiline;
|
||||
|
||||
@@ -54,13 +53,13 @@ const COMBO: WidgetFactory = (node: LGraphNode, inputName: string, inputData: an
|
||||
return addComfyInput(node, inputName, { type: "string", config: { values: type, defaultValue }, defaultWidgetNode: ComfyComboNode })
|
||||
}
|
||||
|
||||
const IMAGEUPLOAD: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any, app): IComfyInputSlot => {
|
||||
const IMAGEUPLOAD: WidgetFactory = (node: LGraphNode, inputName: string, inputData: any): IComfyInputSlot => {
|
||||
return addComfyInput(node, inputName, { type: "number", config: {} })
|
||||
}
|
||||
|
||||
export type WidgetRepository = Record<string, WidgetFactory>
|
||||
|
||||
export const ComfyWidgets: WidgetRepository = {
|
||||
const ComfyWidgets: WidgetRepository = {
|
||||
"INT:seed": INT,
|
||||
"INT:noise_seed": INT,
|
||||
FLOAT,
|
||||
@@ -69,3 +68,5 @@ export const ComfyWidgets: WidgetRepository = {
|
||||
COMBO,
|
||||
IMAGEUPLOAD,
|
||||
}
|
||||
|
||||
export default ComfyWidgets
|
||||
|
||||
44
src/lib/widgets/ButtonWidget.svelte
Normal file
44
src/lib/widgets/ButtonWidget.svelte
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import type { ComfyButtonNode } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import type { ComfySliderNode } from "$lib/nodes/index";
|
||||
import { type WidgetLayout } from "$lib/stores/layoutState";
|
||||
import { Button } from "@gradio/button";
|
||||
import { get, type Writable } from "svelte/store";
|
||||
export let widget: WidgetLayout | null = null;
|
||||
let node: ComfyButtonNode | null = null;
|
||||
let nodeValue: Writable<boolean> | null = null;
|
||||
let propsChanged: Writable<boolean> | null = null;
|
||||
|
||||
$: widget && setNodeValue(widget);
|
||||
|
||||
function setNodeValue(widget: WidgetLayout) {
|
||||
if (widget) {
|
||||
node = widget.node as ComfyButtonNode
|
||||
nodeValue = node.value;
|
||||
propsChanged = node.propsChanged;
|
||||
}
|
||||
};
|
||||
|
||||
function onClick(e: MouseEvent) {
|
||||
node.onClick();
|
||||
}
|
||||
|
||||
const style = {
|
||||
full_width: "100%"
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper gr-button">
|
||||
{#if node !== null}
|
||||
<Button on:click={onClick} variant="primary" {style}>
|
||||
{widget.attrs.title}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,56 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Gallery } from "@gradio/gallery";
|
||||
import type { Styles } from "@gradio/utils";
|
||||
|
||||
export let item: WidgetUIState | null = null;
|
||||
let itemValue: WidgetUIStateStore | null = null; // stores must be declared at top level
|
||||
|
||||
$: if(item) {
|
||||
itemValue = item.value;
|
||||
}
|
||||
|
||||
let style: Styles = {
|
||||
// grid_cols: [2],
|
||||
grid: [3],
|
||||
// object_fit: "cover",
|
||||
}
|
||||
let element: HTMLDivElement;
|
||||
|
||||
function updateForLightbox() {
|
||||
// Wait for gradio gallery to show the large preview image, if no timeout then
|
||||
// the event might fire too early
|
||||
setTimeout(() => {
|
||||
const images = element.querySelectorAll<HTMLImageElement>('div.block div > img')
|
||||
if (images != null) {
|
||||
images.forEach(ImageViewer.instance.setupImageForLightbox.bind(ImageViewer.instance));
|
||||
}
|
||||
ImageViewer.instance.updateOnBackgroundChange();
|
||||
}, 200)
|
||||
}
|
||||
|
||||
</script>
|
||||
<div class="wrapper comfy-gallery-widget gr-gallery" bind:this={element}>
|
||||
{#if item && itemValue}
|
||||
<Block variant="solid" padding={false}>
|
||||
<Gallery
|
||||
bind:value={$itemValue}
|
||||
label={item.widget.name}
|
||||
show_label={true}
|
||||
{style}
|
||||
root={""}
|
||||
root_url={""}
|
||||
on:select={updateForLightbox}
|
||||
/>
|
||||
</Block>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { get } from "svelte/store";
|
||||
import type { WidgetPanelOptions } from "@litegraph-ts/core";
|
||||
import ComfyWidget from "./ComfyWidget";
|
||||
import type { ComfyImageResult } from "$lib/nodes/ComfySaveImageNode";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
|
||||
export type ComfyGalleryEntry = [string, string | null]; // <img> src and alt/title, gradio format
|
||||
|
||||
export interface ComfyGalleryWidgetOptions extends WidgetPanelOptions {
|
||||
}
|
||||
|
||||
export default class ComfyGalleryWidget extends ComfyWidget<ComfyGalleryWidgetOptions, ComfyGalleryEntry[]> {
|
||||
override type = "comfy/gallery";
|
||||
override isVirtual = true;
|
||||
|
||||
addImages(images: ComfyImageResult[]) {
|
||||
this.setValue(this.value.concat(images));
|
||||
}
|
||||
|
||||
override afterQueued() {
|
||||
let queue = get(queueState)
|
||||
if (!(typeof queue.queueRemaining === "number" && queue.queueRemaining > 1)) {
|
||||
this.setValue([])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import type { IEnumWidget, IEnumWidgetOptions, INumberWidget, LGraphNode, WidgetPanelOptions } from "@litegraph-ts/core";
|
||||
import ComfyWidget from "./ComfyWidget";
|
||||
import type { ComfyImageResult } from "$lib/nodes/ComfySaveImageNode";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
|
||||
export interface ComfyValueControlWidgetOptions extends IEnumWidgetOptions {
|
||||
}
|
||||
|
||||
export default class ComfyValueControlWidget extends ComfyWidget<ComfyValueControlWidgetOptions, string> {
|
||||
override type = "combo";
|
||||
targetWidget: INumberWidget;
|
||||
|
||||
constructor(name: string, value: string, node: ComfyGraphNode, targetWidget: INumberWidget) {
|
||||
super(name, value, node)
|
||||
this.targetWidget = targetWidget;
|
||||
this.options = { values: ["fixed", "increment", "decrement", "randomize"], serialize: false };
|
||||
}
|
||||
|
||||
override afterQueued() {
|
||||
var v = this.value;
|
||||
|
||||
let min = this.targetWidget.options.min;
|
||||
let max = this.targetWidget.options.max;
|
||||
// limit to something that javascript can handle
|
||||
max = Math.min(1125899906842624, max);
|
||||
min = Math.max(-1125899906842624, min);
|
||||
let range = (max - min) / (this.targetWidget.options.step);
|
||||
|
||||
//adjust values based on valueControl Behaviour
|
||||
switch (v) {
|
||||
case "fixed":
|
||||
break;
|
||||
case "increment":
|
||||
this.targetWidget.value += this.targetWidget.options.step;
|
||||
break;
|
||||
case "decrement":
|
||||
this.targetWidget.value -= this.targetWidget.options.step;
|
||||
break;
|
||||
case "randomize":
|
||||
this.targetWidget.value = Math.floor(Math.random() * range) * (this.targetWidget.options.step) + min;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/*check if values are over or under their respective
|
||||
* ranges and set them to min or max.*/
|
||||
if (this.targetWidget.value < min)
|
||||
this.targetWidget.value = min;
|
||||
|
||||
if (this.targetWidget.value > max)
|
||||
this.targetWidget.value = max;
|
||||
|
||||
// nodeState.widgetStateChanged(this.node.id, this.targetWidget);
|
||||
}
|
||||
}
|
||||
78
src/lib/widgets/GalleryWidget.svelte
Normal file
78
src/lib/widgets/GalleryWidget.svelte
Normal file
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Gallery } from "@gradio/gallery";
|
||||
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";
|
||||
|
||||
export let widget: WidgetLayout | null = null;
|
||||
let node: ComfyGalleryNode | null = null;
|
||||
let nodeValue: Writable<GradioFileData[]> | null = null;
|
||||
let propsChanged: Writable<boolean> | null = null;
|
||||
let option: number | null = null;
|
||||
|
||||
$: widget && setNodeValue(widget);
|
||||
|
||||
function setNodeValue(widget: WidgetLayout) {
|
||||
if (widget) {
|
||||
node = widget.node as ComfyGalleryNode
|
||||
nodeValue = node.value;
|
||||
propsChanged = node.propsChanged;
|
||||
}
|
||||
};
|
||||
|
||||
let style: Styles = {
|
||||
// grid_cols: [2],
|
||||
grid: [3],
|
||||
// object_fit: "cover",
|
||||
}
|
||||
let element: HTMLDivElement;
|
||||
|
||||
function updateForLightbox() {
|
||||
// Wait for gradio gallery to show the large preview image, if no timeout then
|
||||
// the event might fire too early
|
||||
setTimeout(() => {
|
||||
const images = element.querySelectorAll<HTMLImageElement>('div.block div > img')
|
||||
if (images != null) {
|
||||
images.forEach(ImageViewer.instance.setupImageForLightbox.bind(ImageViewer.instance));
|
||||
}
|
||||
ImageViewer.instance.updateOnBackgroundChange();
|
||||
}, 200)
|
||||
}
|
||||
|
||||
</script>
|
||||
<div class="wrapper comfy-gallery-widget gr-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={true}
|
||||
{style}
|
||||
root={""}
|
||||
root_url={""}
|
||||
on:select={updateForLightbox}
|
||||
/>
|
||||
</div>
|
||||
</Block>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.padding {
|
||||
height: 30rem;
|
||||
}
|
||||
|
||||
.wrapper :global(button.thumbnail-lg) {
|
||||
width: var(--size-32);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user