diff --git a/litegraph b/litegraph index a1e9ae1..93b0ed1 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit a1e9ae1261f55f41964215505233f5ab9c9489e1 +Subproject commit 93b0ed15b7f5ca70ab074ebe7d8b39d3e5963818 diff --git a/package.json b/package.json index eba5ec1..3b33289 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e77552..4eb0430 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cd2c7a0..0cf1b02 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,4 @@ packages: - 'gradio/js/*' - 'gradio/client/js' - - 'litegraph/packages/core' - - 'litegraph/packages/nodes-basic' - - 'litegraph/packages/tsconfig' + - 'litegraph/packages/*' diff --git a/src/App.svelte b/src/App.svelte index a075214..821d589 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,12 +1,15 @@ - + diff --git a/src/lib/ComfyGraph.ts b/src/lib/ComfyGraph.ts index 1c7d6eb..6e5d82a 100644 --- a/src/lib/ComfyGraph.ts +++ b/src/lib/ComfyGraph.ts @@ -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); diff --git a/src/lib/components/BlockContainer.svelte b/src/lib/components/BlockContainer.svelte index a6e999c..263ebfb 100644 --- a/src/lib/components/BlockContainer.svelte +++ b/src/lib/components/BlockContainer.svelte @@ -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; } diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte index 3eb8191..ed7b3ce 100644 --- a/src/lib/components/ComfyApp.svelte +++ b/src/lib/components/ComfyApp.svelte @@ -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(".pane-wrapper") for (const wrapper of wrappers) { const paneNode = wrapper.parentNode as HTMLElement; // get the node inside the @@ -126,6 +126,14 @@ app.resizeCanvas() } } + } + + onMount(async () => { + await app.setup(); + (window as any).app = app; + (window as any).appPane = uiPane; + + refreshView(); }) diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 1a57908..69ed594 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -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 { + 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 = {} + static node_type_overrides: Record = {} static widget_type_overrides: Record = {} - 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; diff --git a/src/lib/components/ComfyUIPane.svelte b/src/lib/components/ComfyUIPane.svelte index 9cee8a5..b9a95b1 100644 --- a/src/lib/components/ComfyUIPane.svelte +++ b/src/lib/components/ComfyUIPane.svelte @@ -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 @@ groupWidgets(false)} text="Group" /> + groupWidgets(true)} + text="Group Horizontally" /> { + 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( + "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" +}) diff --git a/src/lib/nodes/ComfyBackendNode.ts b/src/lib/nodes/ComfyBackendNode.ts new file mode 100644 index 0000000..3cde566 --- /dev/null +++ b/src/lib/nodes/ComfyBackendNode.ts @@ -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(); + } + } + } + } + } +} diff --git a/src/lib/nodes/ComfyGraphNode.ts b/src/lib/nodes/ComfyGraphNode.ts index 6f334f5..9bfb588 100644 --- a/src/lib/nodes/ComfyGraphNode.ts +++ b/src/lib/nodes/ComfyGraphNode.ts @@ -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; diff --git a/src/lib/nodes/ComfyImageNodes.ts b/src/lib/nodes/ComfyImageNodes.ts deleted file mode 100644 index 996e1e8..0000000 --- a/src/lib/nodes/ComfyImageNodes.ts +++ /dev/null @@ -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 = []; - 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 { -} diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index e37dcba..2012414 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -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 { defaultValue: any @@ -36,6 +39,10 @@ export abstract class ComfyWidgetNode 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 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 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 { 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 { 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 { } 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 { 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 { } 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 { 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 { + 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 { + 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" +}) diff --git a/src/lib/nodes/index.ts b/src/lib/nodes/index.ts index c35b6e8..4907332 100644 --- a/src/lib/nodes/index.ts +++ b/src/lib/nodes/index.ts @@ -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" diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index 4713252..a299319 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -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) => 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 = {}): 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 diff --git a/src/lib/widgets.ts b/src/lib/widgets.ts index c020e9e..93e8a98 100644 --- a/src/lib/widgets.ts +++ b/src/lib/widgets.ts @@ -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 -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 diff --git a/src/lib/widgets/ButtonWidget.svelte b/src/lib/widgets/ButtonWidget.svelte new file mode 100644 index 0000000..de3c972 --- /dev/null +++ b/src/lib/widgets/ButtonWidget.svelte @@ -0,0 +1,44 @@ + + +
+ {#if node !== null} + + {/if} +
+ + diff --git a/src/lib/widgets/ComfyGalleryWidget.svelte b/src/lib/widgets/ComfyGalleryWidget.svelte index fdaa96d..e69de29 100644 --- a/src/lib/widgets/ComfyGalleryWidget.svelte +++ b/src/lib/widgets/ComfyGalleryWidget.svelte @@ -1,56 +0,0 @@ - - - - diff --git a/src/lib/widgets/ComfyGalleryWidget.ts b/src/lib/widgets/ComfyGalleryWidget.ts deleted file mode 100644 index fa0b747..0000000 --- a/src/lib/widgets/ComfyGalleryWidget.ts +++ /dev/null @@ -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]; // src and alt/title, gradio format - -export interface ComfyGalleryWidgetOptions extends WidgetPanelOptions { -} - -export default class ComfyGalleryWidget extends ComfyWidget { - 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([]) - } - } -} diff --git a/src/lib/widgets/ComfyValueControlWidget.ts b/src/lib/widgets/ComfyValueControlWidget.ts deleted file mode 100644 index a6b8ba8..0000000 --- a/src/lib/widgets/ComfyValueControlWidget.ts +++ /dev/null @@ -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 { - 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); - } -} diff --git a/src/lib/widgets/GalleryWidget.svelte b/src/lib/widgets/GalleryWidget.svelte new file mode 100644 index 0000000..dcd4d01 --- /dev/null +++ b/src/lib/widgets/GalleryWidget.svelte @@ -0,0 +1,78 @@ + + + +