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
+
+

+
+
+ {{ $t('apiNodesNews.introducing') }}
+ API NODES
+
+
{{ $t('apiNodesNews.subtitle') }}
+
+
+
+
+
+
+
{{ step.title }}
+
+ {{ step.subtitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index b9c5b4b36..8dc9f1445 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -1192,5 +1192,25 @@
"provider": "Sign-in Provider",
"notSet": "Not set",
"updatePassword": "Update Password"
+ },
+ "apiNodesNews": {
+ "introducing": "Introducing",
+ "subtitle": "All External Models now available in ComfyUI",
+ "steps": {
+ "step1": {
+ "title": "Login/Create an account:",
+ "subtitle": "Settings > User > Login"
+ },
+ "step2": {
+ "title": "Purchase credits:",
+ "subtitle": "Settings > Credits > Buy Credits"
+ },
+ "step3": {
+ "title": "Locate new API Nodes under 'API Node' section and add to the canvas"
+ },
+ "step4": {
+ "title": "Run!"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/locales/es/main.json b/src/locales/es/main.json
index 466d81fe0..ec9db1cb3 100644
--- a/src/locales/es/main.json
+++ b/src/locales/es/main.json
@@ -4,6 +4,26 @@
"title": "Nodo(s) de API",
"totalCost": "Costo total"
},
+ "apiNodesNews": {
+ "introducing": "Presentamos",
+ "steps": {
+ "step1": {
+ "subtitle": "Configuración > Usuario > Iniciar sesión",
+ "title": "Inicia sesión/Crea una cuenta:"
+ },
+ "step2": {
+ "subtitle": "Configuración > Créditos > Comprar créditos",
+ "title": "Compra créditos:"
+ },
+ "step3": {
+ "title": "Ubica los nuevos nodos API en la sección 'API Node' y agrégalos al lienzo"
+ },
+ "step4": {
+ "title": "¡Ejecuta!"
+ }
+ },
+ "subtitle": "Todos los modelos externos ahora disponibles en ComfyUI"
+ },
"apiNodesSignInDialog": {
"message": "Este flujo de trabajo contiene nodos de API, que requieren que inicies sesión en tu cuenta para poder ejecutar.",
"title": "Se requiere iniciar sesión para usar los nodos de API"
diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json
index 17c475057..3de32fde0 100644
--- a/src/locales/fr/main.json
+++ b/src/locales/fr/main.json
@@ -4,6 +4,26 @@
"title": "Nœud(s) API",
"totalCost": "Coût total"
},
+ "apiNodesNews": {
+ "introducing": "Présentation",
+ "steps": {
+ "step1": {
+ "subtitle": "Paramètres > Utilisateur > Connexion",
+ "title": "Connectez-vous / Créez un compte :"
+ },
+ "step2": {
+ "subtitle": "Paramètres > Crédits > Acheter des crédits",
+ "title": "Achetez des crédits :"
+ },
+ "step3": {
+ "title": "Trouvez les nouveaux nœuds API dans la section 'API Node' et ajoutez-les à la toile"
+ },
+ "step4": {
+ "title": "Lancez !"
+ }
+ },
+ "subtitle": "Tous les modèles externes sont désormais disponibles dans ComfyUI"
+ },
"apiNodesSignInDialog": {
"message": "Ce flux de travail contient des nœuds API, qui nécessitent que vous soyez connecté à votre compte pour pouvoir fonctionner.",
"title": "Connexion requise pour utiliser les nœuds API"
diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json
index 7ad30e5e3..72bd3fc78 100644
--- a/src/locales/ja/main.json
+++ b/src/locales/ja/main.json
@@ -4,6 +4,26 @@
"title": "APIノード",
"totalCost": "合計コスト"
},
+ "apiNodesNews": {
+ "introducing": "紹介",
+ "steps": {
+ "step1": {
+ "subtitle": "設定 > ユーザー > ログイン",
+ "title": "ログイン/アカウント作成:"
+ },
+ "step2": {
+ "subtitle": "設定 > クレジット > クレジットを購入",
+ "title": "クレジットを購入:"
+ },
+ "step3": {
+ "title": "「APIノード」セクションで新しいAPIノードを見つけてキャンバスに追加"
+ },
+ "step4": {
+ "title": "実行!"
+ }
+ },
+ "subtitle": "すべての外部モデルがComfyUIで利用可能になりました"
+ },
"apiNodesSignInDialog": {
"message": "このワークフローにはAPIノードが含まれており、実行するためにはアカウントにサインインする必要があります。",
"title": "APIノードを使用するためにはサインインが必要です"
diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json
index 25f127fb7..f04c26632 100644
--- a/src/locales/ko/main.json
+++ b/src/locales/ko/main.json
@@ -4,6 +4,26 @@
"title": "API 노드(들)",
"totalCost": "총 비용"
},
+ "apiNodesNews": {
+ "introducing": "소개합니다",
+ "steps": {
+ "step1": {
+ "subtitle": "설정 > 사용자 > 로그인",
+ "title": "로그인/계정 생성:"
+ },
+ "step2": {
+ "subtitle": "설정 > 크레딧 > 크레딧 구매",
+ "title": "크레딧 구매:"
+ },
+ "step3": {
+ "title": "'API Node' 섹션에서 새로운 API 노드를 찾아 캔버스에 추가하세요"
+ },
+ "step4": {
+ "title": "실행!"
+ }
+ },
+ "subtitle": "모든 외부 모델이 이제 ComfyUI에서 사용 가능합니다"
+ },
"apiNodesSignInDialog": {
"message": "이 워크플로우에는 API 노드가 포함되어 있으며, 실행하려면 계정에 로그인해야 합니다.",
"title": "API 노드 사용에 필요한 로그인"
diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json
index 20f8e89c7..4fc6ff548 100644
--- a/src/locales/ru/main.json
+++ b/src/locales/ru/main.json
@@ -4,6 +4,26 @@
"title": "API Node(s)",
"totalCost": "Общая стоимость"
},
+ "apiNodesNews": {
+ "introducing": "Представляем",
+ "steps": {
+ "step1": {
+ "subtitle": "Настройки > Пользователь > Войти",
+ "title": "Войти/Создать аккаунт:"
+ },
+ "step2": {
+ "subtitle": "Настройки > Кредиты > Купить кредиты",
+ "title": "Купить кредиты:"
+ },
+ "step3": {
+ "title": "Найдите новые API-узлы в разделе 'API Node' и добавьте их на холст"
+ },
+ "step4": {
+ "title": "Запустить!"
+ }
+ },
+ "subtitle": "Все внешние модели теперь доступны в ComfyUI"
+ },
"apiNodesSignInDialog": {
"message": "Этот рабочий процесс содержит API Nodes, которые требуют входа в вашу учетную запись для выполнения.",
"title": "Требуется вход для использования API Nodes"
diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json
index 9155f9d5d..d8c3493f4 100644
--- a/src/locales/zh/main.json
+++ b/src/locales/zh/main.json
@@ -4,6 +4,26 @@
"title": "API节点",
"totalCost": "总成本"
},
+ "apiNodesNews": {
+ "introducing": "介绍",
+ "steps": {
+ "step1": {
+ "subtitle": "设置 > 用户 > 登录",
+ "title": "登录/创建账户:"
+ },
+ "step2": {
+ "subtitle": "设置 > 积分 > 购买积分",
+ "title": "购买积分:"
+ },
+ "step3": {
+ "title": "在“API 节点”部分找到新的 API 节点并添加到画布"
+ },
+ "step4": {
+ "title": "运行!"
+ }
+ },
+ "subtitle": "所有外部模型现已在 ComfyUI 中可用"
+ },
"apiNodesSignInDialog": {
"message": "此工作流包含API节点,需要您登录账户才能运行。",
"title": "使用API节点需要登录"
diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts
index dff67e533..105f2333e 100644
--- a/src/services/dialogService.ts
+++ b/src/services/dialogService.ts
@@ -1,3 +1,4 @@
+import ApiNodesNewsContent from '@/components/dialog/content/ApiNodesNewsContent.vue'
import ApiNodesSignInContent from '@/components/dialog/content/ApiNodesSignInContent.vue'
import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue'
import ErrorDialogContent from '@/components/dialog/content/ErrorDialogContent.vue'
@@ -379,6 +380,31 @@ export const useDialogService = () => {
})
}
+ /**
+ * Shows a dialog for the API nodes news.
+ * TODO: Remove the news dialog on next major feature release.
+ */
+ function showApiNodesNewsDialog() {
+ if (localStorage.getItem('api-nodes-news-seen') === 'true') {
+ return
+ }
+
+ return dialogStore.showDialog({
+ key: 'api-nodes-news',
+ component: ApiNodesNewsContent,
+ props: {
+ onClose: () => {
+ dialogStore.closeDialog({ key: 'api-nodes-news' })
+ localStorage.setItem('api-nodes-news-seen', 'true')
+ }
+ },
+ dialogComponentProps: {
+ modal: false,
+ position: 'bottomright'
+ }
+ })
+ }
+
return {
showLoadWorkflowWarning,
showMissingModelsWarning,
@@ -394,6 +420,7 @@ export const useDialogService = () => {
showSignInDialog,
showTopUpCreditsDialog,
showUpdatePasswordDialog,
+ showApiNodesNewsDialog,
prompt,
confirm
}
diff --git a/src/views/GraphView.vue b/src/views/GraphView.vue
index 00265910e..322b523ea 100644
--- a/src/views/GraphView.vue
+++ b/src/views/GraphView.vue
@@ -42,6 +42,7 @@ import { StatusWsMessageStatus } from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { setupAutoQueueHandler } from '@/services/autoQueueService'
+import { useDialogService } from '@/services/dialogService'
import { useKeybindingService } from '@/services/keybindingService'
import { useCommandStore } from '@/stores/commandStore'
import { useExecutionStore } from '@/stores/executionStore'
@@ -241,6 +242,8 @@ const onGraphReady = () => {
// Explicitly initialize nodeSearchService to avoid indexing delay when
// node search is triggered
useNodeDefStore().nodeSearchService.searchNode('')
+
+ useDialogService().showApiNodesNewsDialog()
},
{ timeout: 1000 }
)
From 93d7d2c69cfef27aff3b2d120aab95bfea807c34 Mon Sep 17 00:00:00 2001
From: Chenlei Hu
Date: Sun, 4 May 2025 19:07:13 -0400
Subject: [PATCH 010/159] [Bug] Remove default dialog close button on news
dialog (#3758)
---
src/services/dialogService.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts
index 105f2333e..9ce732bda 100644
--- a/src/services/dialogService.ts
+++ b/src/services/dialogService.ts
@@ -399,6 +399,7 @@ export const useDialogService = () => {
}
},
dialogComponentProps: {
+ closable: false,
modal: false,
position: 'bottomright'
}
From 63d24301a366692aeb907cf05e1a068de50ed524 Mon Sep 17 00:00:00 2001
From: Comfy Org PR Bot
Date: Mon, 5 May 2025 07:31:52 +0800
Subject: [PATCH 011/159] [chore] Update litegraph to 0.15.0-3 (#3757)
Co-authored-by: webfiltered <176114999+webfiltered@users.noreply.github.com>
---
package-lock.json | 8 ++++----
package.json | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 62a7a6683..5cda6df76 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.43",
- "@comfyorg/litegraph": "^0.15.0-2",
+ "@comfyorg/litegraph": "^0.15.0-3",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"@sentry/vue": "^8.48.0",
@@ -482,9 +482,9 @@
"license": "GPL-3.0-only"
},
"node_modules/@comfyorg/litegraph": {
- "version": "0.15.0-2",
- "resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.15.0-2.tgz",
- "integrity": "sha512-bVeK1LaMKRrWHuAC+5/vqgkpAL/8Q8J633/BlAYL897sEorpWfW5qjMdc0T9iHXm0htlaET/tKdDwRcNERl/xA==",
+ "version": "0.15.0-3",
+ "resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.15.0-3.tgz",
+ "integrity": "sha512-gpbu9oFEPmi8x9jpIgU816M16nh3kLl+EoTqadvNmImNHAoc6HO2TgGSjiNbvOqrvwWpz7WeYugnXKawwRl01A==",
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {
diff --git a/package.json b/package.json
index 7610125b5..58d659a26 100644
--- a/package.json
+++ b/package.json
@@ -72,7 +72,7 @@
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.43",
- "@comfyorg/litegraph": "^0.15.0-2",
+ "@comfyorg/litegraph": "^0.15.0-3",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"@sentry/vue": "^8.48.0",
From 7a1a626b369d1fc84854bd90b2c107112f7b3f33 Mon Sep 17 00:00:00 2001
From: Comfy Org PR Bot
Date: Mon, 5 May 2025 08:45:43 +0800
Subject: [PATCH 012/159] 1.19.1 (#3756)
Co-authored-by: huchenlei <20929282+huchenlei@users.noreply.github.com>
---
package-lock.json | 4 ++--
package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 5cda6df76..0c2851e2a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@comfyorg/comfyui-frontend",
- "version": "1.19.0",
+ "version": "1.19.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@comfyorg/comfyui-frontend",
- "version": "1.19.0",
+ "version": "1.19.1",
"license": "GPL-3.0-only",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
diff --git a/package.json b/package.json
index 58d659a26..1da5b1df3 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
- "version": "1.19.0",
+ "version": "1.19.1",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
From b5ae354bec8394173797ce26d3bfc80e1a641717 Mon Sep 17 00:00:00 2001
From: filtered <176114999+webfiltered@users.noreply.github.com>
Date: Mon, 5 May 2025 22:48:25 +1000
Subject: [PATCH 013/159] Prevent node tooltip from breaking pan & zoom (#3760)
---
src/components/graph/NodeTooltip.vue | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/graph/NodeTooltip.vue b/src/components/graph/NodeTooltip.vue
index 206afd801..48155d0eb 100644
--- a/src/components/graph/NodeTooltip.vue
+++ b/src/components/graph/NodeTooltip.vue
@@ -131,6 +131,7 @@ useEventListener(window, 'click', hideTooltip)
From 926278e9efe83c3c48bb582967e33001bb97fd5d Mon Sep 17 00:00:00 2001
From: Chenlei Hu
Date: Tue, 6 May 2025 16:07:19 -0400
Subject: [PATCH 033/159] [nit] Simplify selection overlay watcher (#3789)
---
src/components/graph/SelectionOverlay.vue | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/src/components/graph/SelectionOverlay.vue b/src/components/graph/SelectionOverlay.vue
index 302084587..88c5e6547 100644
--- a/src/components/graph/SelectionOverlay.vue
+++ b/src/components/graph/SelectionOverlay.vue
@@ -63,19 +63,13 @@ watch(
)
watch(
- () => {
- const canvas = canvasStore.canvas
- if (!canvas) return null
- return {
- scale: canvas.ds.state.scale,
- offset: [canvas.ds.state.offset[0], canvas.ds.state.offset[1]]
- }
- },
+ () => canvasStore.getCanvas().ds.state,
(state) => {
if (!state) return
- positionSelectionOverlay(canvasStore.canvas as LGraphCanvas)
- }
+ positionSelectionOverlay(canvasStore.getCanvas())
+ },
+ { deep: true }
)
watch(
From fad6c6c502098197fe7f95907c18313ce01d7782 Mon Sep 17 00:00:00 2001
From: filtered <176114999+webfiltered@users.noreply.github.com>
Date: Wed, 7 May 2025 10:23:46 +1000
Subject: [PATCH 034/159] [nit] Further simplify watcher via whenever (#3790)
---
src/components/graph/SelectionOverlay.vue | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/src/components/graph/SelectionOverlay.vue b/src/components/graph/SelectionOverlay.vue
index 88c5e6547..380d2afc1 100644
--- a/src/components/graph/SelectionOverlay.vue
+++ b/src/components/graph/SelectionOverlay.vue
@@ -15,6 +15,7 @@
+
+
diff --git a/src/extensions/core/load3d/Load3d.ts b/src/extensions/core/load3d/Load3d.ts
index 0989c7344..cadf25247 100644
--- a/src/extensions/core/load3d/Load3d.ts
+++ b/src/extensions/core/load3d/Load3d.ts
@@ -456,6 +456,8 @@ class Load3d {
this.viewHelperManager.visibleViewHelper(true)
this.recordingManager.stopRecording()
+
+ this.eventManager.emitEvent('recordingStatusChange', false)
}
public isRecording(): boolean {
From bbbf140b1f8c2d0c2d8061ee26b67171ea036680 Mon Sep 17 00:00:00 2001
From: Christian Byrne
Date: Tue, 6 May 2025 19:04:25 -0700
Subject: [PATCH 036/159] Handle user avatar error (#3735)
Co-authored-by: github-actions
---
src/components/common/UserAvatar.test.ts | 106 ++++++++++++++++++
src/components/common/UserAvatar.vue | 25 +++++
.../dialog/content/setting/UserPanel.vue | 7 +-
src/components/topbar/CurrentUserButton.vue | 9 +-
src/components/topbar/CurrentUserPopover.vue | 12 +-
src/locales/en/main.json | 1 +
src/locales/es/main.json | 3 +-
src/locales/fr/main.json | 3 +-
src/locales/ja/main.json | 3 +-
src/locales/ko/main.json | 3 +-
src/locales/ru/main.json | 3 +-
src/locales/zh/main.json | 3 +-
12 files changed, 155 insertions(+), 23 deletions(-)
create mode 100644 src/components/common/UserAvatar.test.ts
create mode 100644 src/components/common/UserAvatar.vue
diff --git a/src/components/common/UserAvatar.test.ts b/src/components/common/UserAvatar.test.ts
new file mode 100644
index 000000000..d844277b9
--- /dev/null
+++ b/src/components/common/UserAvatar.test.ts
@@ -0,0 +1,106 @@
+import { mount } from '@vue/test-utils'
+import Avatar from 'primevue/avatar'
+import PrimeVue from 'primevue/config'
+import { beforeEach, describe, expect, it } from 'vitest'
+import { createApp, nextTick } from 'vue'
+import { createI18n } from 'vue-i18n'
+
+import UserAvatar from './UserAvatar.vue'
+
+const i18n = createI18n({
+ legacy: false,
+ locale: 'en',
+ messages: {
+ en: {
+ auth: {
+ login: {
+ userAvatar: 'User Avatar'
+ }
+ }
+ }
+ }
+})
+
+describe('UserAvatar', () => {
+ beforeEach(() => {
+ const app = createApp({})
+ app.use(PrimeVue)
+ })
+
+ const mountComponent = (props: any = {}) => {
+ return mount(UserAvatar, {
+ global: {
+ plugins: [PrimeVue, i18n],
+ components: { Avatar }
+ },
+ props
+ })
+ }
+
+ it('renders correctly with photo Url', async () => {
+ const wrapper = mountComponent({
+ photoUrl: 'https://example.com/avatar.jpg'
+ })
+
+ const avatar = wrapper.findComponent(Avatar)
+ expect(avatar.exists()).toBe(true)
+ expect(avatar.props('image')).toBe('https://example.com/avatar.jpg')
+ expect(avatar.props('icon')).toBeNull()
+ })
+
+ it('renders with default icon when no photo Url is provided', () => {
+ const wrapper = mountComponent({
+ photoUrl: undefined
+ })
+
+ const avatar = wrapper.findComponent(Avatar)
+ expect(avatar.exists()).toBe(true)
+ expect(avatar.props('image')).toBeNull()
+ expect(avatar.props('icon')).toBe('pi pi-user')
+ })
+
+ it('renders with default icon when provided photo Url is null', () => {
+ const wrapper = mountComponent({
+ photoUrl: null
+ })
+
+ const avatar = wrapper.findComponent(Avatar)
+ expect(avatar.exists()).toBe(true)
+ expect(avatar.props('image')).toBeNull()
+ expect(avatar.props('icon')).toBe('pi pi-user')
+ })
+
+ it('falls back to icon when image fails to load', async () => {
+ const wrapper = mountComponent({
+ photoUrl: 'https://example.com/broken-image.jpg'
+ })
+
+ const avatar = wrapper.findComponent(Avatar)
+ expect(avatar.props('icon')).toBeNull()
+
+ // Simulate image load error
+ avatar.vm.$emit('error')
+ await nextTick()
+
+ expect(avatar.props('icon')).toBe('pi pi-user')
+ })
+
+ it('uses provided ariaLabel', () => {
+ const wrapper = mountComponent({
+ photoUrl: 'https://example.com/avatar.jpg',
+ ariaLabel: 'Custom Label'
+ })
+
+ const avatar = wrapper.findComponent(Avatar)
+ expect(avatar.attributes('aria-label')).toBe('Custom Label')
+ })
+
+ it('falls back to i18n translation when no ariaLabel is provided', () => {
+ const wrapper = mountComponent({
+ photoUrl: 'https://example.com/avatar.jpg'
+ })
+
+ const avatar = wrapper.findComponent(Avatar)
+ expect(avatar.attributes('aria-label')).toBe('User Avatar')
+ })
+})
diff --git a/src/components/common/UserAvatar.vue b/src/components/common/UserAvatar.vue
new file mode 100644
index 000000000..8fce43d10
--- /dev/null
+++ b/src/components/common/UserAvatar.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/src/components/dialog/content/setting/UserPanel.vue b/src/components/dialog/content/setting/UserPanel.vue
index 2529261d7..9047d8c5a 100644
--- a/src/components/dialog/content/setting/UserPanel.vue
+++ b/src/components/dialog/content/setting/UserPanel.vue
@@ -5,12 +5,11 @@
-
@@ -87,13 +86,13 @@
diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts
index 080f62cf5..9e1a31d52 100644
--- a/src/composables/useCoreCommands.ts
+++ b/src/composables/useCoreCommands.ts
@@ -312,6 +312,28 @@ export function useCoreCommands(): ComfyCommand[] {
await app.queuePrompt(-1, batchCount)
}
},
+ {
+ id: 'Comfy.QueueSelectedOutputNodes',
+ icon: 'pi pi-play',
+ label: 'Queue Selected Output Nodes',
+ versionAdded: '1.19.6',
+ function: async () => {
+ const batchCount = useQueueSettingsStore().batchCount
+ const queueNodeIds = getSelectedNodes()
+ .filter((node) => node.constructor.nodeData.output_node)
+ .map((node) => node.id)
+ if (queueNodeIds.length === 0) {
+ toastStore.add({
+ severity: 'error',
+ summary: t('toastMessages.nothingToQueue'),
+ detail: t('toastMessages.pleaseSelectOutputNodes'),
+ life: 3000
+ })
+ return
+ }
+ await app.queuePrompt(0, batchCount, queueNodeIds)
+ }
+ },
{
id: 'Comfy.ShowSettingsDialog',
icon: 'pi pi-cog',
diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json
index 3afc8fedb..e707fb93f 100644
--- a/src/locales/en/commands.json
+++ b/src/locales/en/commands.json
@@ -155,6 +155,9 @@
"Comfy_QueuePromptFront": {
"label": "Queue Prompt (Front)"
},
+ "Comfy_QueueSelectedOutputNodes": {
+ "label": "Queue Selected Output Nodes"
+ },
"Comfy_Redo": {
"label": "Redo"
},
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index 6ee05ac3e..bcd671c23 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -803,6 +803,7 @@
"Open": "Open",
"Queue Prompt": "Queue Prompt",
"Queue Prompt (Front)": "Queue Prompt (Front)",
+ "Queue Selected Output Nodes": "Queue Selected Output Nodes",
"Redo": "Redo",
"Refresh Node Definitions": "Refresh Node Definitions",
"Save": "Save",
@@ -1197,6 +1198,8 @@
"resizeNodeMatchOutput": "Resize Node to match output"
},
"toastMessages": {
+ "nothingToQueue": "Nothing to queue",
+ "pleaseSelectOutputNodes": "Please select output nodes",
"no3dScene": "No 3D scene to apply texture",
"failedToApplyTexture": "Failed to apply texture",
"no3dSceneToExport": "No 3D scene to export",
@@ -1343,5 +1346,11 @@
"title": "Run!"
}
}
+ },
+ "selectionToolbox": {
+ "executeButton": {
+ "tooltip": "Execute to selected output nodes (Highlighted with orange border)",
+ "disabledTooltip": "No output nodes selected"
+ }
}
}
\ No newline at end of file
diff --git a/src/locales/es/commands.json b/src/locales/es/commands.json
index 271755c59..1294be59f 100644
--- a/src/locales/es/commands.json
+++ b/src/locales/es/commands.json
@@ -155,6 +155,9 @@
"Comfy_QueuePromptFront": {
"label": "Prompt de Cola (Frente)"
},
+ "Comfy_QueueSelectedOutputNodes": {
+ "label": "Encolar nodos de salida seleccionados"
+ },
"Comfy_Redo": {
"label": "Rehacer"
},
diff --git a/src/locales/es/main.json b/src/locales/es/main.json
index 85280b170..eb85678f7 100644
--- a/src/locales/es/main.json
+++ b/src/locales/es/main.json
@@ -695,6 +695,7 @@
"Previous Opened Workflow": "Flujo de trabajo abierto anterior",
"Queue Prompt": "Indicador de cola",
"Queue Prompt (Front)": "Indicador de cola (Frente)",
+ "Queue Selected Output Nodes": "Encolar nodos de salida seleccionados",
"Quit": "Salir",
"Redo": "Rehacer",
"Refresh Node Definitions": "Actualizar definiciones de nodo",
@@ -801,6 +802,12 @@
},
"title": "Tu dispositivo no es compatible"
},
+ "selectionToolbox": {
+ "executeButton": {
+ "disabledTooltip": "No hay nodos de salida seleccionados",
+ "tooltip": "Ejecutar en los nodos de salida seleccionados (resaltados con borde naranja)"
+ }
+ },
"serverConfig": {
"modifiedConfigs": "Has modificado las siguientes configuraciones del servidor. Reinicia para aplicar los cambios.",
"restart": "Reiniciar",
@@ -1297,8 +1304,10 @@
"noTemplatesToExport": "No hay plantillas para exportar",
"nodeDefinitionsUpdated": "Definiciones de nodos actualizadas",
"nothingToGroup": "Nada para agrupar",
+ "nothingToQueue": "Nada para poner en cola",
"pendingTasksDeleted": "Tareas pendientes eliminadas",
"pleaseSelectNodesToGroup": "Por favor, seleccione los nodos (u otros grupos) para crear un grupo para",
+ "pleaseSelectOutputNodes": "Por favor, selecciona los nodos de salida",
"unableToGetModelFilePath": "No se puede obtener la ruta del archivo del modelo",
"unauthorizedDomain": "Tu dominio {domain} no está autorizado para usar este servicio. Por favor, contacta a {email} para agregar tu dominio a la lista blanca.",
"updateRequested": "Actualización solicitada",
diff --git a/src/locales/fr/commands.json b/src/locales/fr/commands.json
index bd18cd8ac..a38391210 100644
--- a/src/locales/fr/commands.json
+++ b/src/locales/fr/commands.json
@@ -155,6 +155,9 @@
"Comfy_QueuePromptFront": {
"label": "Invite de file d'attente (avant)"
},
+ "Comfy_QueueSelectedOutputNodes": {
+ "label": "Mettre en file d’attente les nœuds de sortie sélectionnés"
+ },
"Comfy_Redo": {
"label": "Refaire"
},
diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json
index bb76e7546..187b15672 100644
--- a/src/locales/fr/main.json
+++ b/src/locales/fr/main.json
@@ -695,6 +695,7 @@
"Previous Opened Workflow": "Flux de travail ouvert précédent",
"Queue Prompt": "Invite de file d'attente",
"Queue Prompt (Front)": "Invite de file d'attente (Front)",
+ "Queue Selected Output Nodes": "Mettre en file d’attente les nœuds de sortie sélectionnés",
"Quit": "Quitter",
"Redo": "Refaire",
"Refresh Node Definitions": "Actualiser les définitions de nœud",
@@ -801,6 +802,12 @@
},
"title": "Votre appareil n'est pas pris en charge"
},
+ "selectionToolbox": {
+ "executeButton": {
+ "disabledTooltip": "Aucun nœud de sortie sélectionné",
+ "tooltip": "Exécuter vers les nœuds de sortie sélectionnés (surlignés avec une bordure orange)"
+ }
+ },
"serverConfig": {
"modifiedConfigs": "Vous avez modifié les configurations suivantes du serveur. Redémarrez pour appliquer les modifications.",
"restart": "Redémarrer",
@@ -1297,8 +1304,10 @@
"noTemplatesToExport": "Aucun modèle à exporter",
"nodeDefinitionsUpdated": "Définitions de nœuds mises à jour",
"nothingToGroup": "Rien à regrouper",
+ "nothingToQueue": "Rien à ajouter à la file d’attente",
"pendingTasksDeleted": "Tâches en attente supprimées",
"pleaseSelectNodesToGroup": "Veuillez sélectionner les nœuds (ou autres groupes) pour créer un groupe pour",
+ "pleaseSelectOutputNodes": "Veuillez sélectionner les nœuds de sortie",
"unableToGetModelFilePath": "Impossible d'obtenir le chemin du fichier modèle",
"unauthorizedDomain": "Votre domaine {domain} n'est pas autorisé à utiliser ce service. Veuillez contacter {email} pour ajouter votre domaine à la liste blanche.",
"updateRequested": "Mise à jour demandée",
diff --git a/src/locales/ja/commands.json b/src/locales/ja/commands.json
index 22a14488a..75dfd93e3 100644
--- a/src/locales/ja/commands.json
+++ b/src/locales/ja/commands.json
@@ -155,6 +155,9 @@
"Comfy_QueuePromptFront": {
"label": "キュープロンプト(フロント)"
},
+ "Comfy_QueueSelectedOutputNodes": {
+ "label": "選択した出力ノードをキューに追加"
+ },
"Comfy_Redo": {
"label": "やり直す"
},
diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json
index e1405a659..a403909f8 100644
--- a/src/locales/ja/main.json
+++ b/src/locales/ja/main.json
@@ -695,6 +695,7 @@
"Previous Opened Workflow": "前に開いたワークフロー",
"Queue Prompt": "キューのプロンプト",
"Queue Prompt (Front)": "キューのプロンプト (前面)",
+ "Queue Selected Output Nodes": "選択した出力ノードをキューに追加",
"Quit": "終了",
"Redo": "やり直す",
"Refresh Node Definitions": "ノード定義を更新",
@@ -801,6 +802,12 @@
},
"title": "お使いのデバイスはサポートされていません"
},
+ "selectionToolbox": {
+ "executeButton": {
+ "disabledTooltip": "出力ノードが選択されていません",
+ "tooltip": "選択した出力ノードに実行(オレンジ色の枠でハイライト表示)"
+ }
+ },
"serverConfig": {
"modifiedConfigs": "以下のサーバー設定を変更しました。変更を適用するには再起動してください。",
"restart": "再起動",
@@ -1297,8 +1304,10 @@
"noTemplatesToExport": "エクスポートするテンプレートがありません",
"nodeDefinitionsUpdated": "ノード定義が更新されました",
"nothingToGroup": "グループ化するものがありません",
+ "nothingToQueue": "キューに追加する項目がありません",
"pendingTasksDeleted": "保留中のタスクが削除されました",
"pleaseSelectNodesToGroup": "グループを作成するためのノード(または他のグループ)を選択してください",
+ "pleaseSelectOutputNodes": "出力ノードを選択してください",
"unableToGetModelFilePath": "モデルファイルのパスを取得できません",
"unauthorizedDomain": "あなたのドメイン {domain} はこのサービスを利用する権限がありません。ご利用のドメインをホワイトリストに追加するには、{email} までご連絡ください。",
"updateRequested": "更新が要求されました",
diff --git a/src/locales/ko/commands.json b/src/locales/ko/commands.json
index 001f8a3ac..624f0db1b 100644
--- a/src/locales/ko/commands.json
+++ b/src/locales/ko/commands.json
@@ -155,6 +155,9 @@
"Comfy_QueuePromptFront": {
"label": "실행 큐 맨 앞에 프롬프트 추가"
},
+ "Comfy_QueueSelectedOutputNodes": {
+ "label": "선택한 출력 노드 대기열에 추가"
+ },
"Comfy_Redo": {
"label": "다시 실행"
},
diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json
index fe81ef7af..f68c00c6d 100644
--- a/src/locales/ko/main.json
+++ b/src/locales/ko/main.json
@@ -695,6 +695,7 @@
"Previous Opened Workflow": "이전 열린 워크플로",
"Queue Prompt": "실행 큐에 프롬프트 추가",
"Queue Prompt (Front)": "실행 큐 맨 앞에 프롬프트 추가",
+ "Queue Selected Output Nodes": "선택한 출력 노드 대기열에 추가",
"Quit": "종료",
"Redo": "다시 실행",
"Refresh Node Definitions": "노드 정의 새로 고침",
@@ -801,6 +802,12 @@
},
"title": "이 장치는 지원되지 않습니다."
},
+ "selectionToolbox": {
+ "executeButton": {
+ "disabledTooltip": "선택된 출력 노드가 없습니다",
+ "tooltip": "선택한 출력 노드에 실행 (주황색 테두리로 강조 표시됨)"
+ }
+ },
"serverConfig": {
"modifiedConfigs": "다음 서버 구성을 수정했습니다. 변경 사항을 적용하려면 다시 시작하세오.",
"restart": "다시 시작",
@@ -1297,8 +1304,10 @@
"noTemplatesToExport": "내보낼 템플릿이 없습니다",
"nodeDefinitionsUpdated": "노드 정의가 업데이트되었습니다",
"nothingToGroup": "그룹화할 항목이 없습니다",
+ "nothingToQueue": "대기열에 추가할 항목이 없습니다",
"pendingTasksDeleted": "보류 중인 작업이 삭제되었습니다",
"pleaseSelectNodesToGroup": "그룹을 만들기 위해 노드(또는 다른 그룹)를 선택해 주세요",
+ "pleaseSelectOutputNodes": "출력 노드를 선택해 주세요",
"unableToGetModelFilePath": "모델 파일 경로를 가져올 수 없습니다",
"unauthorizedDomain": "귀하의 도메인 {domain}은(는) 이 서비스를 사용할 수 있는 권한이 없습니다. 도메인을 허용 목록에 추가하려면 {email}로 문의해 주세요.",
"updateRequested": "업데이트 요청됨",
diff --git a/src/locales/ru/commands.json b/src/locales/ru/commands.json
index 90d95985f..e692760fc 100644
--- a/src/locales/ru/commands.json
+++ b/src/locales/ru/commands.json
@@ -155,6 +155,9 @@
"Comfy_QueuePromptFront": {
"label": "Очередь запросов (передняя)"
},
+ "Comfy_QueueSelectedOutputNodes": {
+ "label": "Добавить выбранные выходные узлы в очередь"
+ },
"Comfy_Redo": {
"label": "Повторить"
},
diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json
index 1d1eac625..065a5ce9a 100644
--- a/src/locales/ru/main.json
+++ b/src/locales/ru/main.json
@@ -695,6 +695,7 @@
"Previous Opened Workflow": "Предыдущий открытый рабочий процесс",
"Queue Prompt": "Запрос в очереди",
"Queue Prompt (Front)": "Запрос в очереди (спереди)",
+ "Queue Selected Output Nodes": "Добавить выбранные выходные узлы в очередь",
"Quit": "Выйти",
"Redo": "Повторить",
"Refresh Node Definitions": "Обновить определения нод",
@@ -801,6 +802,12 @@
},
"title": "Ваше устройство не поддерживается"
},
+ "selectionToolbox": {
+ "executeButton": {
+ "disabledTooltip": "Выходные узлы не выбраны",
+ "tooltip": "Выполнить для выбранных выходных узлов (выделены оранжевой рамкой)"
+ }
+ },
"serverConfig": {
"modifiedConfigs": "Вы изменили следующие конфигурации сервера. Перезапустите, чтобы применить изменения.",
"restart": "Перезапустить",
@@ -1297,8 +1304,10 @@
"noTemplatesToExport": "Нет шаблонов для экспорта",
"nodeDefinitionsUpdated": "Определения узлов обновлены",
"nothingToGroup": "Нечего группировать",
+ "nothingToQueue": "Нет заданий в очереди",
"pendingTasksDeleted": "Ожидающие задачи удалены",
"pleaseSelectNodesToGroup": "Пожалуйста, выберите узлы (или другие группы) для создания группы",
+ "pleaseSelectOutputNodes": "Пожалуйста, выберите выходные узлы",
"unableToGetModelFilePath": "Не удалось получить путь к файлу модели",
"unauthorizedDomain": "Ваш домен {domain} не авторизован для использования этого сервиса. Пожалуйста, свяжитесь с {email}, чтобы добавить ваш домен в белый список.",
"updateRequested": "Запрошено обновление",
diff --git a/src/locales/zh/commands.json b/src/locales/zh/commands.json
index afd044bb6..29fef86d1 100644
--- a/src/locales/zh/commands.json
+++ b/src/locales/zh/commands.json
@@ -155,6 +155,9 @@
"Comfy_QueuePromptFront": {
"label": "执行提示词(前端)"
},
+ "Comfy_QueueSelectedOutputNodes": {
+ "label": "队列所选输出节点"
+ },
"Comfy_Redo": {
"label": "重做"
},
diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json
index e79455afc..9999ee729 100644
--- a/src/locales/zh/main.json
+++ b/src/locales/zh/main.json
@@ -695,6 +695,7 @@
"Previous Opened Workflow": "上一个打开的工作流",
"Queue Prompt": "执行提示词",
"Queue Prompt (Front)": "执行提示词 (优先执行)",
+ "Queue Selected Output Nodes": "将所选输出节点加入队列",
"Quit": "退出",
"Redo": "重做",
"Refresh Node Definitions": "刷新节点定义",
@@ -801,6 +802,12 @@
},
"title": "您的设备不受支持"
},
+ "selectionToolbox": {
+ "executeButton": {
+ "disabledTooltip": "未选择输出节点",
+ "tooltip": "执行到选定的输出节点(用橙色边框高亮显示)"
+ }
+ },
"serverConfig": {
"modifiedConfigs": "您已修改以下服务器配置。重启以应用更改。",
"restart": "重启",
@@ -1297,8 +1304,10 @@
"noTemplatesToExport": "没有模板可以导出",
"nodeDefinitionsUpdated": "节点定义已更新",
"nothingToGroup": "没有可分组的内容",
+ "nothingToQueue": "没有可加入队列的内容",
"pendingTasksDeleted": "待处理任务已删除",
"pleaseSelectNodesToGroup": "请选取节点(或其他组)以创建分组",
+ "pleaseSelectOutputNodes": "请选择输出节点",
"unableToGetModelFilePath": "无法获取模型文件路径",
"unauthorizedDomain": "您的域名 {domain} 未被授权使用此服务。请联系 {email} 将您的域名添加到白名单。",
"updateRequested": "已请求更新",
diff --git a/src/scripts/app.ts b/src/scripts/app.ts
index 38dfd8c48..f588c7892 100644
--- a/src/scripts/app.ts
+++ b/src/scripts/app.ts
@@ -107,7 +107,11 @@ export class ComfyApp {
/**
* List of entries to queue
*/
- #queueItems: { number: number; batchCount: number }[] = []
+ #queueItems: {
+ number: number
+ batchCount: number
+ queueNodeIds?: NodeId[]
+ }[] = []
/**
* If the queue is currently being processed
*/
@@ -1144,14 +1148,22 @@ export class ComfyApp {
})
}
- async graphToPrompt(graph = this.graph) {
+ async graphToPrompt(
+ graph = this.graph,
+ options: { queueNodeIds?: NodeId[] } = {}
+ ) {
return graphToPrompt(graph, {
- sortNodes: useSettingStore().get('Comfy.Workflow.SortNodeIdOnSave')
+ sortNodes: useSettingStore().get('Comfy.Workflow.SortNodeIdOnSave'),
+ queueNodeIds: options.queueNodeIds
})
}
- async queuePrompt(number: number, batchCount: number = 1): Promise
{
- this.#queueItems.push({ number, batchCount })
+ async queuePrompt(
+ number: number,
+ batchCount: number = 1,
+ queueNodeIds?: NodeId[]
+ ): Promise {
+ this.#queueItems.push({ number, batchCount, queueNodeIds })
// Only have one action process the items so each one gets a unique seed correctly
if (this.#processingQueue) {
@@ -1167,14 +1179,14 @@ export class ComfyApp {
try {
while (this.#queueItems.length) {
- const { number, batchCount } = this.#queueItems.pop()!
+ const { number, batchCount, queueNodeIds } = this.#queueItems.pop()!
for (let i = 0; i < batchCount; i++) {
// Allow widgets to run callbacks before a prompt has been queued
// e.g. random seed before every gen
executeWidgetsCallback(this.graph.nodes, 'beforeQueued')
- const p = await this.graphToPrompt()
+ const p = await this.graphToPrompt(this.graph, { queueNodeIds })
try {
api.authToken = comfyOrgAuthToken
const res = await api.queuePrompt(number, p)
diff --git a/src/utils/executionUtil.ts b/src/utils/executionUtil.ts
index b630be539..eeb56c2dc 100644
--- a/src/utils/executionUtil.ts
+++ b/src/utils/executionUtil.ts
@@ -1,4 +1,4 @@
-import type { LGraph } from '@comfyorg/litegraph'
+import type { LGraph, NodeId } from '@comfyorg/litegraph'
import { LGraphEventMode } from '@comfyorg/litegraph'
import type {
@@ -8,16 +8,46 @@ import type {
import { compressWidgetInputSlots } from './litegraphUtil'
+/**
+ * Recursively target node's parent nodes to the new output.
+ * @param nodeId The node id to add.
+ * @param oldOutput The old output.
+ * @param newOutput The new output.
+ * @returns The new output.
+ */
+function recursiveAddNodes(
+ nodeId: NodeId,
+ oldOutput: ComfyApiWorkflow,
+ newOutput: ComfyApiWorkflow
+) {
+ const currentId = String(nodeId)
+ const currentNode = oldOutput[currentId]!
+ if (newOutput[currentId] == null) {
+ newOutput[currentId] = currentNode
+ for (const inputValue of Object.values(currentNode.inputs || [])) {
+ if (Array.isArray(inputValue)) {
+ recursiveAddNodes(inputValue[0], oldOutput, newOutput)
+ }
+ }
+ }
+ return newOutput
+}
+
/**
* Converts the current graph workflow for sending to the API.
- * Note: Node widgets are updated before serialization to prepare queueing.
+ * @note Node widgets are updated before serialization to prepare queueing.
+ *
+ * @param graph The graph to convert.
+ * @param options The options for the conversion.
+ * - `sortNodes`: Whether to sort the nodes by execution order.
+ * - `queueNodeIds`: The output nodes to execute. Execute all output nodes if not provided.
* @returns The workflow and node links
*/
export const graphToPrompt = async (
graph: LGraph,
- options: { sortNodes?: boolean } = {}
+ options: { sortNodes?: boolean; queueNodeIds?: NodeId[] } = {}
): Promise<{ workflow: ComfyWorkflowJSON; output: ComfyApiWorkflow }> => {
- const { sortNodes = false } = options
+ const { sortNodes = false, queueNodeIds } = options
for (const node of graph.computeExecutionOrder(false)) {
const innerNodes = node.getInnerNodes ? node.getInnerNodes() : [node]
@@ -44,7 +74,7 @@ export const graphToPrompt = async (
workflow.extra ??= {}
workflow.extra.frontendVersion = __COMFYUI_FRONTEND_VERSION__
- const output: ComfyApiWorkflow = {}
+ let output: ComfyApiWorkflow = {}
// Process nodes in order of execution
for (const outerNode of graph.computeExecutionOrder(false)) {
const skipNode =
@@ -163,6 +193,15 @@ export const graphToPrompt = async (
}
}
+ // Partial execution
+ if (queueNodeIds?.length) {
+ const newOutput = {}
+ for (const queueNodeId of queueNodeIds) {
+ recursiveAddNodes(queueNodeId, output, newOutput)
+ }
+ output = newOutput
+ }
+
// @ts-expect-error Convert ISerializedGraph to ComfyWorkflowJSON
return { workflow: workflow as ComfyWorkflowJSON, output }
}
From 2019c1d87730ffc2884d9656bdd1baa96f22779e Mon Sep 17 00:00:00 2001
From: Chenlei Hu
Date: Thu, 8 May 2025 16:00:13 -0400
Subject: [PATCH 054/159] Align reset_view param on json file load (#3823)
---
browser_tests/assets/default_input.json | 8 ++++----
browser_tests/assets/execution/partial_execution.json | 2 +-
browser_tests/assets/string_node_id.json | 6 +++---
src/scripts/app.ts | 2 +-
4 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/browser_tests/assets/default_input.json b/browser_tests/assets/default_input.json
index eff7b3236..a9291ab82 100644
--- a/browser_tests/assets/default_input.json
+++ b/browser_tests/assets/default_input.json
@@ -42,12 +42,12 @@
"config": {},
"extra": {
"ds": {
- "scale": 2.1600300525920346,
+ "scale": 1,
"offset": [
- 63.071794466403446,
- 75.18055335968394
+ 0,
+ 0
]
}
},
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/execution/partial_execution.json b/browser_tests/assets/execution/partial_execution.json
index 5685ab814..4669b44fd 100644
--- a/browser_tests/assets/execution/partial_execution.json
+++ b/browser_tests/assets/execution/partial_execution.json
@@ -77,7 +77,7 @@
"extra": {
"frontendVersion": "1.19.1",
"ds": {
- "offset": [400, 400],
+ "offset": [0, 0],
"scale": 1
}
},
diff --git a/browser_tests/assets/string_node_id.json b/browser_tests/assets/string_node_id.json
index 275f81177..a4ed79ba7 100644
--- a/browser_tests/assets/string_node_id.json
+++ b/browser_tests/assets/string_node_id.json
@@ -368,10 +368,10 @@
"ds": {
"scale": 1,
"offset": [
- 149.9747408641311,
- 383.8593224280729
+ 0,
+ 0
]
}
},
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/src/scripts/app.ts b/src/scripts/app.ts
index f588c7892..1a8716e11 100644
--- a/src/scripts/app.ts
+++ b/src/scripts/app.ts
@@ -1364,7 +1364,7 @@ export class ComfyApp {
await this.loadGraphData(
JSON.parse(readerResult),
true,
- false,
+ true,
fileName
)
}
From 2a297e512d57f241a4b96c4e839fed008ca9f3cf Mon Sep 17 00:00:00 2001
From: Chenlei Hu
Date: Thu, 8 May 2025 17:39:44 -0400
Subject: [PATCH 055/159] Fit view on workflow load without extra.ds (#3822)
Co-authored-by: github-actions
---
browser_tests/assets/collapsed_multiline.json | 6 ++++++
browser_tests/assets/default.json | 7 ++++++-
browser_tests/assets/every_node_color.json | 9 +++++++--
...up_node_identical_nodes_hidden_inputs.json | 2 +-
browser_tests/assets/group_node_v1.3.3.json | 6 +++++-
browser_tests/assets/legacy_group_node.json | 6 +++++-
.../missing_models_from_node_properties.json | 7 ++++++-
.../missing_nodes_converted_widget.json | 9 +++++++--
browser_tests/assets/note_nodes.json | 9 +++++++--
.../assets/only_optional_inputs.json | 8 ++++++--
...primitive_node_unconnected_dom_widget.json | 7 ++++++-
.../assets/renamed_converted_widget.json | 6 +++++-
.../assets/reroute/native_reroute.json | 4 ++++
.../assets/single_connected_reroute_node.json | 5 +----
.../assets/widgets/boolean_widget.json | 8 +++++++-
.../assets/widgets/load_animated_webp.json | 2 +-
.../assets/widgets/load_audio_widget.json | 7 ++++++-
.../assets/widgets/load_image_widget.json | 9 +++++++--
.../assets/widgets/save_animated_webp.json | 6 +++++-
browser_tests/tests/interaction.spec.ts | 6 ++++++
.../single-ksampler-fit-chromium-linux.png | Bin 0 -> 82167 bytes
.../workflow-m4v-chromium-linux.png | Bin 44745 -> 59345 bytes
.../workflow-mov-chromium-linux.png | Bin 44745 -> 59345 bytes
.../workflow-mp4-chromium-linux.png | Bin 44745 -> 59345 bytes
.../workflow-webm-chromium-linux.png | Bin 49126 -> 58068 bytes
package-lock.json | 8 ++++----
package.json | 2 +-
src/scripts/app.ts | 7 +++++++
src/scripts/defaultGraph.ts | 7 ++++++-
src/services/litegraphService.ts | 17 +++++++++++++++--
tests-ui/workflows/default_workflow.json | 2 +-
31 files changed, 138 insertions(+), 34 deletions(-)
create mode 100644 browser_tests/tests/interaction.spec.ts-snapshots/single-ksampler-fit-chromium-linux.png
diff --git a/browser_tests/assets/collapsed_multiline.json b/browser_tests/assets/collapsed_multiline.json
index d2977074d..2999e994a 100644
--- a/browser_tests/assets/collapsed_multiline.json
+++ b/browser_tests/assets/collapsed_multiline.json
@@ -33,5 +33,11 @@
"links": [],
"groups": [],
"config": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
}
diff --git a/browser_tests/assets/default.json b/browser_tests/assets/default.json
index dfb7078a6..dd083e568 100644
--- a/browser_tests/assets/default.json
+++ b/browser_tests/assets/default.json
@@ -130,6 +130,11 @@
],
"groups": [],
"config": {},
- "extra": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
}
diff --git a/browser_tests/assets/every_node_color.json b/browser_tests/assets/every_node_color.json
index 79c067382..b64da88bd 100644
--- a/browser_tests/assets/every_node_color.json
+++ b/browser_tests/assets/every_node_color.json
@@ -499,6 +499,11 @@
],
"groups": [],
"config": {},
- "extra": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/group_node_identical_nodes_hidden_inputs.json b/browser_tests/assets/group_node_identical_nodes_hidden_inputs.json
index db6b85c69..e423546f3 100644
--- a/browser_tests/assets/group_node_identical_nodes_hidden_inputs.json
+++ b/browser_tests/assets/group_node_identical_nodes_hidden_inputs.json
@@ -160,4 +160,4 @@
}
},
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/group_node_v1.3.3.json b/browser_tests/assets/group_node_v1.3.3.json
index 7afd9d7c8..1c04ec8bc 100644
--- a/browser_tests/assets/group_node_v1.3.3.json
+++ b/browser_tests/assets/group_node_v1.3.3.json
@@ -43,6 +43,10 @@
"groups": [],
"config": {},
"extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ },
"groupNodes": {
"group_node": {
"nodes": [
@@ -401,4 +405,4 @@
}
},
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/legacy_group_node.json b/browser_tests/assets/legacy_group_node.json
index e64f2f785..0e883872e 100644
--- a/browser_tests/assets/legacy_group_node.json
+++ b/browser_tests/assets/legacy_group_node.json
@@ -110,6 +110,10 @@
"groups": [],
"config": {},
"extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ },
"groupNodes": {
"hello": {
"nodes": [
@@ -249,4 +253,4 @@
}
},
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/missing_models_from_node_properties.json b/browser_tests/assets/missing_models_from_node_properties.json
index cf7b1e198..b1a8e5a67 100644
--- a/browser_tests/assets/missing_models_from_node_properties.json
+++ b/browser_tests/assets/missing_models_from_node_properties.json
@@ -44,6 +44,11 @@
"links": [],
"groups": [],
"config": {},
- "extra": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
}
diff --git a/browser_tests/assets/missing_nodes_converted_widget.json b/browser_tests/assets/missing_nodes_converted_widget.json
index 5237f0f9f..b7c3a73aa 100644
--- a/browser_tests/assets/missing_nodes_converted_widget.json
+++ b/browser_tests/assets/missing_nodes_converted_widget.json
@@ -61,6 +61,11 @@
"links": [],
"groups": [],
"config": {},
- "extra": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/note_nodes.json b/browser_tests/assets/note_nodes.json
index 280c44135..95d8b149d 100644
--- a/browser_tests/assets/note_nodes.json
+++ b/browser_tests/assets/note_nodes.json
@@ -50,6 +50,11 @@
"links": [],
"groups": [],
"config": {},
- "extra": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/only_optional_inputs.json b/browser_tests/assets/only_optional_inputs.json
index 3f4198756..fa926118a 100644
--- a/browser_tests/assets/only_optional_inputs.json
+++ b/browser_tests/assets/only_optional_inputs.json
@@ -38,7 +38,11 @@
"groups": [],
"config": {},
"extra": {
- "groupNodes": {}
+ "groupNodes": {},
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
},
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/primitive/primitive_node_unconnected_dom_widget.json b/browser_tests/assets/primitive/primitive_node_unconnected_dom_widget.json
index 48d95d3ff..5ed8d6d2c 100644
--- a/browser_tests/assets/primitive/primitive_node_unconnected_dom_widget.json
+++ b/browser_tests/assets/primitive/primitive_node_unconnected_dom_widget.json
@@ -54,6 +54,11 @@
"links": [],
"groups": [],
"config": {},
- "extra": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
}
diff --git a/browser_tests/assets/renamed_converted_widget.json b/browser_tests/assets/renamed_converted_widget.json
index 37975583e..439018ec0 100644
--- a/browser_tests/assets/renamed_converted_widget.json
+++ b/browser_tests/assets/renamed_converted_widget.json
@@ -92,10 +92,14 @@
"groups": [],
"config": {},
"extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ },
"VHS_latentpreview": true,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": false,
"VHS_KeepIntermediate": false
},
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/reroute/native_reroute.json b/browser_tests/assets/reroute/native_reroute.json
index 993d478ac..af7510069 100644
--- a/browser_tests/assets/reroute/native_reroute.json
+++ b/browser_tests/assets/reroute/native_reroute.json
@@ -84,6 +84,10 @@
"groups": [],
"config": {},
"extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ },
"reroutes": [
{
"id": 1,
diff --git a/browser_tests/assets/single_connected_reroute_node.json b/browser_tests/assets/single_connected_reroute_node.json
index c81c1204c..9c7347e1f 100644
--- a/browser_tests/assets/single_connected_reroute_node.json
+++ b/browser_tests/assets/single_connected_reroute_node.json
@@ -106,10 +106,7 @@
"extra": {
"ds": {
"scale": 1,
- "offset": {
- "0": 0,
- "1": 0
- }
+ "offset": [0, 0]
}
},
"version": 0.4
diff --git a/browser_tests/assets/widgets/boolean_widget.json b/browser_tests/assets/widgets/boolean_widget.json
index c60aee7d0..2c7d415f2 100644
--- a/browser_tests/assets/widgets/boolean_widget.json
+++ b/browser_tests/assets/widgets/boolean_widget.json
@@ -31,5 +31,11 @@
"links": [],
"groups": [],
"config": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/widgets/load_animated_webp.json b/browser_tests/assets/widgets/load_animated_webp.json
index 561da3147..a9d7f3cc9 100644
--- a/browser_tests/assets/widgets/load_animated_webp.json
+++ b/browser_tests/assets/widgets/load_animated_webp.json
@@ -8,4 +8,4 @@
"title": "Load Animated Image"
}
}
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/widgets/load_audio_widget.json b/browser_tests/assets/widgets/load_audio_widget.json
index c40440a1c..128720f61 100644
--- a/browser_tests/assets/widgets/load_audio_widget.json
+++ b/browser_tests/assets/widgets/load_audio_widget.json
@@ -27,6 +27,11 @@
"links": [],
"groups": [],
"config": {},
- "extra": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
}
diff --git a/browser_tests/assets/widgets/load_image_widget.json b/browser_tests/assets/widgets/load_image_widget.json
index 844b77d53..1ba9e4158 100644
--- a/browser_tests/assets/widgets/load_image_widget.json
+++ b/browser_tests/assets/widgets/load_image_widget.json
@@ -41,6 +41,11 @@
"links": [],
"groups": [],
"config": {},
- "extra": {},
+ "extra": {
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
+ },
"version": 0.4
-}
\ No newline at end of file
+}
diff --git a/browser_tests/assets/widgets/save_animated_webp.json b/browser_tests/assets/widgets/save_animated_webp.json
index 362a56cec..664362f66 100644
--- a/browser_tests/assets/widgets/save_animated_webp.json
+++ b/browser_tests/assets/widgets/save_animated_webp.json
@@ -54,7 +54,11 @@
"groups": [],
"config": {},
"extra": {
- "frontendVersion": "1.17.0"
+ "frontendVersion": "1.17.0",
+ "ds": {
+ "offset": [0, 0],
+ "scale": 1
+ }
},
"version": 0.4
}
diff --git a/browser_tests/tests/interaction.spec.ts b/browser_tests/tests/interaction.spec.ts
index b803578e4..fa907d738 100644
--- a/browser_tests/tests/interaction.spec.ts
+++ b/browser_tests/tests/interaction.spec.ts
@@ -666,6 +666,12 @@ test.describe('Load workflow', () => {
expect(activeWorkflowName).toEqual(workflowPathB)
})
})
+
+ test('Auto fit view after loading workflow', async ({ comfyPage }) => {
+ await comfyPage.setSetting('Comfy.EnableWorkflowViewRestore', false)
+ await comfyPage.loadWorkflow('single_ksampler')
+ await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler_fit.png')
+ })
})
test.describe('Load duplicate workflow', () => {
diff --git a/browser_tests/tests/interaction.spec.ts-snapshots/single-ksampler-fit-chromium-linux.png b/browser_tests/tests/interaction.spec.ts-snapshots/single-ksampler-fit-chromium-linux.png
new file mode 100644
index 0000000000000000000000000000000000000000..d2d2200184353265bcf93e2a3d84ab7e60d27135
GIT binary patch
literal 82167
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