From 99c948f578049c1e4cf69239f3847c5aa0d87b80 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Mon, 7 Oct 2024 14:46:43 -0400 Subject: [PATCH 01/66] Update README (#1149) --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 749af62074..b7f0b63127 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,11 @@ ### Nightly Release -Nightly releases are published daily at [https://github.com/Comfy-Org/ComfyUI_frontend/releases](https://github.com/Comfy-Org/ComfyUI_frontend/releases). +Nightly releases are published daily at [https://github.com/Comfy-Org/ComfyUI_frontend/releases](https://github.com/Comfy-Org/ComfyUI_frontend/releases). To use the latest nightly release, add the following command line argument to your ComfyUI launch script: -``` +```bat --front-end-version Comfy-Org/ComfyUI_frontend@latest ``` @@ -62,6 +62,23 @@ There will be a 2-day feature freeze before each stable release. During this per ### Major features +
+ v1.3.7: Keybinding customization + +## Basic UI +![image](https://github.com/user-attachments/assets/c84a1609-3880-48e0-a746-011f36beda68) + +## Reset button +![image](https://github.com/user-attachments/assets/4d2922da-bb4f-4f90-8017-a8e4a0db07c7) + +## Edit Keybinding +![image](https://github.com/user-attachments/assets/77626b7a-cb46-48f8-9465-e03120aac66a) +![image](https://github.com/user-attachments/assets/79131a4e-75c6-4715-bd11-c6aaed887779) + +[rec.webm](https://github.com/user-attachments/assets/a3984ed9-eb28-4d47-86c0-7fc3efc2b5d0) + +
+
v1.2.4: Node library sidebar tab @@ -90,6 +107,32 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf ### QoL changes +
+ v1.3.6: **Litegraph** Toggle link visibility + +[rec.webm](https://github.com/user-attachments/assets/34e460ac-fbbc-44ef-bfbb-99a84c2ae2be) + +
+ +
+ v1.3.4: **Litegraph** Auto widget to input conversion + +Dropping a link of correct type on node widget will automatically convert the widget to input. + +[rec.webm](https://github.com/user-attachments/assets/15cea0b0-b225-4bec-af50-2cdb16dc46bf) + +
+ +
+ v1.3.4: **Litegraph** Canvas pan mode + +The canvas becomes readonly in pan mode. Pan mode is activated by clicking the pan mode button on the canvas menu +or by holding the space key. + +[rec.webm](https://github.com/user-attachments/assets/c7872532-a2ac-44c1-9e7d-9e03b5d1a80b) + +
+
v1.3.1: **Litegraph** Shift drag link to create a new link @@ -97,6 +140,13 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
+
+ v1.2.62: **Litegraph** Show optional input slots as donuts + +![GYEIRidb0AYGO-v](https://github.com/user-attachments/assets/e6cde0b6-654b-4afd-a117-133657a410b1) + +
+
v1.2.44: **Litegraph** Double click group title to edit From ff1ca268a4fea07f65e6772b60173022de703ee0 Mon Sep 17 00:00:00 2001 From: "Alex \"mcmonkey\" Goodwin" <4000772+mcmonkey4eva@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:50:45 -0700 Subject: [PATCH 02/66] Model Library sidebar: allow searching metadata (#1148) * Model Library sidebar: allow searching metadata title, description, etc * don't use vue stuff inside of vue because vue doesn't support vue very cool * remove old import * and that one --- .../sidebar/tabs/ModelLibrarySidebarTab.vue | 2 +- src/stores/modelStore.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/sidebar/tabs/ModelLibrarySidebarTab.vue b/src/components/sidebar/tabs/ModelLibrarySidebarTab.vue index f4f50a9537..077601afa8 100644 --- a/src/components/sidebar/tabs/ModelLibrarySidebarTab.vue +++ b/src/components/sidebar/tabs/ModelLibrarySidebarTab.vue @@ -83,7 +83,7 @@ const root: ComputedRef = computed(() => { if (searchQuery.value) { const search = searchQuery.value.toLocaleLowerCase() modelList = modelList.filter((model: ComfyModelDef) => { - return model.file_name.toLocaleLowerCase().includes(search) + return model.searchable.includes(search) }) } const tree: TreeNode = buildTree(modelList, (model: ComfyModelDef) => { diff --git a/src/stores/modelStore.ts b/src/stores/modelStore.ts index e4c1a03736..5fa1da7a97 100644 --- a/src/stores/modelStore.ts +++ b/src/stores/modelStore.ts @@ -48,6 +48,8 @@ export class ComfyModelDef { is_load_requested: boolean = false /** If true, this is a fake model object used as a placeholder for something (eg a loading icon) */ is_fake_object: boolean = false + /** A string full of auto-computed lowercase-only searchable text for this model */ + searchable: string = '' constructor(name: string, directory: string) { this.file_name = name @@ -60,6 +62,20 @@ export class ComfyModelDef { } this.title = this.simplified_file_name this.directory = directory + this.updateSearchable() + } + + updateSearchable() { + this.searchable = [ + this.file_name, + this.title, + this.author, + this.description, + this.trigger_phrase, + this.tags.join(', ') + ] + .join('\n') + .toLowerCase() } /** Loads the model metadata from the server, filling in this object if data is available */ @@ -110,6 +126,7 @@ export class ComfyModelDef { _findInMetadata(metadata, 'modelspec.tags', 'tags') || '' this.tags = tagsCommaSeparated.split(',').map((tag) => tag.trim()) this.has_loaded_metadata = true + this.updateSearchable() } catch (error) { console.error('Error loading model metadata', this.file_name, this, error) } From cc17bee945ab61630b47bfb46a9c5d1a47529aa7 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Mon, 7 Oct 2024 16:50:58 -0400 Subject: [PATCH 03/66] Manage app.ts litegraph keybindings (#1151) * Manage app.ts litegraph keybindings * nit --- src/extensions/core/keybinds.ts | 2 +- src/scripts/app.ts | 80 ++++----------------------------- src/stores/commandStore.ts | 75 ++++++++++++++++++++++++++++++- src/stores/coreKeybindings.ts | 66 +++++++++++++++++++++++++++ src/stores/keybindingStore.ts | 6 ++- src/types/keyBindingTypes.ts | 7 ++- 6 files changed, 159 insertions(+), 77 deletions(-) diff --git a/src/extensions/core/keybinds.ts b/src/extensions/core/keybinds.ts index 7cfdc5a45a..736cf64bdd 100644 --- a/src/extensions/core/keybinds.ts +++ b/src/extensions/core/keybinds.ts @@ -30,7 +30,7 @@ app.registerExtension({ const keybindingStore = useKeybindingStore() const commandStore = useCommandStore() const keybinding = keybindingStore.getKeybinding(keyCombo) - if (keybinding) { + if (keybinding && keybinding.targetSelector !== '#graph-canvas') { await commandStore.execute(keybinding.commandId) event.preventDefault() return diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 691d962d57..17ee4c6856 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -53,6 +53,8 @@ import { useWorkspaceStore } from '@/stores/workspaceStateStore' import { useExecutionStore } from '@/stores/executionStore' import { IWidget } from '@comfyorg/litegraph' import { useExtensionStore } from '@/stores/extensionStore' +import { KeyComboImpl, useKeybindingStore } from '@/stores/keybindingStore' +import { useCommandStore } from '@/stores/commandStore' export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview' @@ -1278,13 +1280,10 @@ export class ComfyApp { /** * Handle keypress - * - * Ctrl + M mute/unmute selected nodes */ #addProcessKeyHandler() { - const self = this const origProcessKey = LGraphCanvas.prototype.processKey - LGraphCanvas.prototype.processKey = function (e) { + LGraphCanvas.prototype.processKey = function (e: KeyboardEvent) { if (!this.graph) { return } @@ -1296,54 +1295,11 @@ export class ComfyApp { } if (e.type == 'keydown' && !e.repeat) { - // Ctrl + M mute/unmute - if (e.key === 'm' && (e.metaKey || e.ctrlKey)) { - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].mode === 2) { - // never - this.selected_nodes[i].mode = 0 // always - } else { - this.selected_nodes[i].mode = 2 // never - } - } - } - block_default = true - } - - // Ctrl + B bypass - if (e.key === 'b' && (e.metaKey || e.ctrlKey)) { - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].mode === 4) { - // never - this.selected_nodes[i].mode = 0 // always - } else { - this.selected_nodes[i].mode = 4 // never - } - } - } - block_default = true - } - - // p pin/unpin - if (e.key === 'p') { - if (this.selected_nodes) { - for (const i in this.selected_nodes) { - const node = this.selected_nodes[i] - node.pin() - } - } - block_default = true - } - - // Alt + C collapse/uncollapse - if (e.key === 'c' && e.altKey) { - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - this.selected_nodes[i].collapse() - } - } + const keyCombo = KeyComboImpl.fromEvent(e) + const keybindingStore = useKeybindingStore() + const keybinding = keybindingStore.getKeybinding(keyCombo) + if (keybinding && keybinding.targetSelector === '#graph-canvas') { + useCommandStore().execute(keybinding.commandId) block_default = true } @@ -1362,26 +1318,6 @@ export class ComfyApp { // Trigger onPaste return true } - - if (e.key === '+' && e.altKey) { - block_default = true - let scale = this.ds.scale * 1.1 - this.ds.changeScale(scale, [ - this.ds.element.width / 2, - this.ds.element.height / 2 - ]) - this.graph.change() - } - - if (e.key === '-' && e.altKey) { - block_default = true - let scale = (this.ds.scale * 1) / 1.1 - this.ds.changeScale(scale, [ - this.ds.element.width / 2, - this.ds.element.height / 2 - ]) - this.graph.change() - } } this.graph.change() diff --git a/src/stores/commandStore.ts b/src/stores/commandStore.ts index 7ed61e80ee..9bd7ef708c 100644 --- a/src/stores/commandStore.ts +++ b/src/stores/commandStore.ts @@ -17,6 +17,7 @@ import { useTitleEditorStore } from './graphStore' import { useErrorHandling } from '@/hooks/errorHooks' import { useWorkflowStore } from './workflowStore' import { type KeybindingImpl, useKeybindingStore } from './keybindingStore' +import { LGraphNode } from '@comfyorg/litegraph' export interface ComfyCommand { id: string @@ -75,6 +76,28 @@ export class ComfyCommandImpl implements ComfyCommand { const getTracker = () => app.workflowManager.activeWorkflow?.changeTracker ?? globalTracker +const getSelectedNodes = (): LGraphNode[] => { + const selectedNodes = app.canvas.selected_nodes + const result: LGraphNode[] = [] + if (selectedNodes) { + for (const i in selectedNodes) { + const node = selectedNodes[i] + result.push(node) + } + } + return result +} + +const toggleSelectedNodesMode = (mode: number) => { + getSelectedNodes().forEach((node) => { + if (node.mode === mode) { + node.mode = 0 // always + } else { + node.mode = mode + } + }) +} + export const useCommandStore = defineStore('command', () => { const settingStore = useSettingStore() @@ -248,7 +271,11 @@ export const useCommandStore = defineStore('command', () => { icon: 'pi pi-plus', label: 'Zoom In', function: () => { - app.canvas.ds.changeScale(app.canvas.ds.scale + 0.1) + const ds = app.canvas.ds + ds.changeScale(ds.scale * 1.1, [ + ds.element.width / 2, + ds.element.height / 2 + ]) app.canvas.setDirty(true, true) } }, @@ -257,7 +284,11 @@ export const useCommandStore = defineStore('command', () => { icon: 'pi pi-minus', label: 'Zoom Out', function: () => { - app.canvas.ds.changeScale(app.canvas.ds.scale - 0.1) + const ds = app.canvas.ds + ds.changeScale(ds.scale / 1.1, [ + ds.element.width / 2, + ds.element.height / 2 + ]) app.canvas.setDirty(true, true) } }, @@ -365,6 +396,46 @@ export const useCommandStore = defineStore('command', () => { function: () => { useWorkflowStore().loadPreviousOpenedWorkflow() } + }, + { + id: 'Comfy.Canvas.ToggleSelectedNodes.Mute', + icon: 'pi pi-volume-off', + label: 'Mute/Unmute Selected Nodes', + versionAdded: '1.3.11', + function: () => { + toggleSelectedNodesMode(2) // muted + } + }, + { + id: 'Comfy.Canvas.ToggleSelectedNodes.Bypass', + icon: 'pi pi-shield', + label: 'Bypass/Unbypass Selected Nodes', + versionAdded: '1.3.11', + function: () => { + toggleSelectedNodesMode(4) // bypassed + } + }, + { + id: 'Comfy.Canvas.ToggleSelectedNodes.Pin', + icon: 'pi pi-pin', + label: 'Pin/Unpin Selected Nodes', + versionAdded: '1.3.11', + function: () => { + getSelectedNodes().forEach((node) => { + node.pin(!node.pinned) + }) + } + }, + { + id: 'Comfy.Canvas.ToggleSelectedNodes.Collapse', + icon: 'pi pi-minus', + label: 'Collapse/Expand Selected Nodes', + versionAdded: '1.3.11', + function: () => { + getSelectedNodes().forEach((node) => { + node.collapse() + }) + } } ] diff --git a/src/stores/coreKeybindings.ts b/src/stores/coreKeybindings.ts index 0afea36a34..aec19029f7 100644 --- a/src/stores/coreKeybindings.ts +++ b/src/stores/coreKeybindings.ts @@ -94,5 +94,71 @@ export const CORE_KEYBINDINGS: Keybinding[] = [ ctrl: true }, commandId: 'Comfy.ShowSettingsDialog' + }, + // For '=' both holding shift and not holding shift + { + combo: { + key: '=', + alt: true + }, + commandId: 'Comfy.Canvas.ZoomIn', + targetSelector: '#graph-canvas' + }, + { + combo: { + key: '+', + alt: true, + shift: true + }, + commandId: 'Comfy.Canvas.ZoomIn', + targetSelector: '#graph-canvas' + }, + // For number pad '+' + { + combo: { + key: '+', + alt: true + }, + commandId: 'Comfy.Canvas.ZoomIn', + targetSelector: '#graph-canvas' + }, + { + combo: { + key: '-', + alt: true + }, + commandId: 'Comfy.Canvas.ZoomOut', + targetSelector: '#graph-canvas' + }, + { + combo: { + key: 'p' + }, + commandId: 'Comfy.Canvas.ToggleSelectedNodes.Pin', + targetSelector: '#graph-canvas' + }, + { + combo: { + key: 'c', + alt: true + }, + commandId: 'Comfy.Canvas.ToggleSelectedNodes.Collapse', + targetSelector: '#graph-canvas' + }, + { + combo: { + key: 'b', + ctrl: true + }, + commandId: 'Comfy.Canvas.ToggleSelectedNodes.Bypass', + targetSelector: '#graph-canvas' + }, + { + combo: { + key: 'm', + ctrl: true + }, + commandId: 'Comfy.Canvas.ToggleSelectedNodes.Mute', + targetSelector: '#graph-canvas' } ] diff --git a/src/stores/keybindingStore.ts b/src/stores/keybindingStore.ts index 924b23ea62..c82cf3369d 100644 --- a/src/stores/keybindingStore.ts +++ b/src/stores/keybindingStore.ts @@ -8,16 +8,20 @@ import type { ComfyExtension } from '@/types/comfy' export class KeybindingImpl implements Keybinding { commandId: string combo: KeyComboImpl + targetSelector?: string constructor(obj: Keybinding) { this.commandId = obj.commandId this.combo = new KeyComboImpl(obj.combo) + this.targetSelector = obj.targetSelector } equals(other: any): boolean { if (toRaw(other) instanceof KeybindingImpl) { return ( - this.commandId === other.commandId && this.combo.equals(other.combo) + this.commandId === other.commandId && + this.combo.equals(other.combo) && + this.targetSelector === other.targetSelector ) } return false diff --git a/src/types/keyBindingTypes.ts b/src/types/keyBindingTypes.ts index c637e2084d..863cae0dd8 100644 --- a/src/types/keyBindingTypes.ts +++ b/src/types/keyBindingTypes.ts @@ -12,7 +12,12 @@ export const zKeyCombo = z.object({ // Keybinding schema export const zKeybinding = z.object({ commandId: z.string(), - combo: zKeyCombo + combo: zKeyCombo, + // Optional target element CSS selector to limit keybinding to. + // Note: Currently only used to distinguish between global keybindings + // and litegraph canvas keybindings. + // Do NOT use this field in extensions as it has no effect. + targetSelector: z.string().optional() }) // Infer types from schemas From f8343d0f93d7a3935e9518f737124b63efe92b1d Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Mon, 7 Oct 2024 17:11:20 -0400 Subject: [PATCH 04/66] Fix flaky playwright test (#1152) --- browser_tests/menu.spec.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/browser_tests/menu.spec.ts b/browser_tests/menu.spec.ts index 9633002748..2f1cf0eb31 100644 --- a/browser_tests/menu.spec.ts +++ b/browser_tests/menu.spec.ts @@ -416,10 +416,9 @@ test.describe('Menu', () => { const tab = comfyPage.menu.workflowsTab await tab.open() - expect(await tab.getTopLevelSavedWorkflowNames()).toEqual([ - 'workflow1.json', - 'workflow2.json' - ]) + expect(await tab.getTopLevelSavedWorkflowNames()).toEqual( + expect.arrayContaining(['workflow1.json', 'workflow2.json']) + ) }) test('Does not report warning when switching between opened workflows', async ({ From 2b265141902fa7f0e7804a9e725be78448553152 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Mon, 7 Oct 2024 17:27:25 -0400 Subject: [PATCH 05/66] 1.3.11 (#1154) --- 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 6c74f41d65..cb88ba1752 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "comfyui-frontend", - "version": "1.3.10", + "version": "1.3.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "comfyui-frontend", - "version": "1.3.10", + "version": "1.3.11", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.2.1", "@comfyorg/litegraph": "^0.8.0", diff --git a/package.json b/package.json index 6a95ada151..ec9e565ef4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "comfyui-frontend", "private": true, - "version": "1.3.10", + "version": "1.3.11", "type": "module", "scripts": { "dev": "vite", From 23952d97516a7e9e00dcfd720163639f07989d73 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Mon, 7 Oct 2024 19:54:00 -0400 Subject: [PATCH 06/66] Show queue front icon when shift is pressed (#1157) * Move shiftDown state to workspaceStateStore * Queue front state --- src/App.vue | 9 +++++- src/components/actionbar/ComfyQueueButton.vue | 13 +++++++-- src/i18n.ts | 2 ++ src/scripts/app.ts | 29 +++++++------------ src/stores/workspaceStateStore.ts | 5 +++- tests-ui/globalSetup.ts | 9 ++++++ 6 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/App.vue b/src/App.vue index 675f4fc870..a52f770266 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,8 +15,15 @@ import { useWorkspaceStore } from '@/stores/workspaceStateStore' import BlockUI from 'primevue/blockui' import ProgressSpinner from 'primevue/progressspinner' import GlobalDialog from '@/components/dialog/GlobalDialog.vue' +import { useEventListener } from '@vueuse/core' -const isLoading = computed(() => useWorkspaceStore().spinner) +const workspaceStore = useWorkspaceStore() +const isLoading = computed(() => workspaceStore.spinner) +const handleKey = (e: KeyboardEvent) => { + workspaceStore.shiftDown = e.shiftKey +} +useEventListener(window, 'keydown', handleKey) +useEventListener(window, 'keyup', handleKey) onMounted(() => { window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version diff --git a/src/components/actionbar/ComfyQueueButton.vue b/src/components/actionbar/ComfyQueueButton.vue index b816678491..854a61bb50 100644 --- a/src/components/actionbar/ComfyQueueButton.vue +++ b/src/components/actionbar/ComfyQueueButton.vue @@ -3,13 +3,20 @@ + diff --git a/src/components/sidebar/tabs/queue/__tests__/ResultGallery.spec.ts b/src/components/sidebar/tabs/queue/__tests__/ResultGallery.ts similarity index 96% rename from src/components/sidebar/tabs/queue/__tests__/ResultGallery.spec.ts rename to src/components/sidebar/tabs/queue/__tests__/ResultGallery.ts index 2bfefed455..2bd1c859b9 100644 --- a/src/components/sidebar/tabs/queue/__tests__/ResultGallery.spec.ts +++ b/src/components/sidebar/tabs/queue/__tests__/ResultGallery.ts @@ -1,3 +1,5 @@ +// Disabled because of https://github.com/Comfy-Org/ComfyUI_frontend/issues/1184 + import { mount } from '@vue/test-utils' import { expect, describe, it } from 'vitest' import ResultGallery from '../ResultGallery.vue' diff --git a/src/stores/queueStore.ts b/src/stores/queueStore.ts index 73625338bb..84b99ff232 100644 --- a/src/stores/queueStore.ts +++ b/src/stores/queueStore.ts @@ -50,17 +50,58 @@ export class ResultItemImpl { this.frame_rate = obj.frame_rate } + private get urlParams(): URLSearchParams { + const params = new URLSearchParams() + params.set('filename', this.filename) + params.set('type', this.type) + params.set('subfolder', this.subfolder || '') + + if (this.format) { + params.set('format', this.format) + } + if (this.frame_rate) { + params.set('frame_rate', this.frame_rate.toString()) + } + return params + } + + /** + * VHS advanced preview URL. `/viewvideo` endpoint is provided by VHS node. + */ + get vhsAdvancedPreviewUrl(): string { + return api.apiURL('/viewvideo?' + this.urlParams) + } + get url(): string { - return api.apiURL(`/view?filename=${encodeURIComponent(this.filename)}&type=${this.type}& - subfolder=${encodeURIComponent(this.subfolder || '')}`) + return api.apiURL('/view?' + this.urlParams) } get urlWithTimestamp(): string { return `${this.url}&t=${+new Date()}` } + get isVhsFormat(): boolean { + return !!this.format && !!this.frame_rate + } + + get htmlVideoType(): string | undefined { + const defaultType = undefined + + if (!this.isVhsFormat) { + return defaultType + } + + if (this.format.endsWith('webm')) { + return 'video/webm' + } + if (this.format.endsWith('mp4')) { + return 'video/mp4' + } + return defaultType + } + get isVideo(): boolean { - return this.format && this.format.startsWith('video/') + return !this.isImage && this.format && this.format.startsWith('video/') } get isGif(): boolean { From c8f50509ede69e26c72ff5f95f46becfba1b730e Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 9 Oct 2024 15:02:02 -0400 Subject: [PATCH 24/66] Fix VHS advanced preview html video type (#1186) * Fix VHS advanced preview html video type * nit --- .../sidebar/tabs/queue/ResultVideo.vue | 20 ++++++++++++------- src/stores/queueStore.ts | 8 +++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/components/sidebar/tabs/queue/ResultVideo.vue b/src/components/sidebar/tabs/queue/ResultVideo.vue index 91521fcea5..c6d5e84307 100644 --- a/src/components/sidebar/tabs/queue/ResultVideo.vue +++ b/src/components/sidebar/tabs/queue/ResultVideo.vue @@ -1,6 +1,6 @@ @@ -15,10 +15,16 @@ const props = defineProps<{ }>() const settingStore = useSettingStore() -const url = computed(() => { - if (settingStore.get('VHS.AdvancedPreviews')) { - return props.result.vhsAdvancedPreviewUrl - } - return props.result.url -}) +const vhsAdvancedPreviews = computed(() => + settingStore.get('VHS.AdvancedPreviews') +) + +const url = computed(() => + vhsAdvancedPreviews.value + ? props.result.vhsAdvancedPreviewUrl + : props.result.url +) +const htmlVideoType = computed(() => + vhsAdvancedPreviews.value ? 'video/webm' : props.result.htmlVideoType +) diff --git a/src/stores/queueStore.ts b/src/stores/queueStore.ts index 84b99ff232..b4bd797628 100644 --- a/src/stores/queueStore.ts +++ b/src/stores/queueStore.ts @@ -67,6 +67,8 @@ export class ResultItemImpl { /** * VHS advanced preview URL. `/viewvideo` endpoint is provided by VHS node. + * + * `/viewvideo` always returns a webm file. */ get vhsAdvancedPreviewUrl(): string { return api.apiURL('/viewvideo?' + this.urlParams) @@ -108,8 +110,12 @@ export class ResultItemImpl { return this.filename.endsWith('.gif') } + get isWebp(): boolean { + return this.filename.endsWith('.webp') + } + get isImage(): boolean { - return this.mediaType === 'images' || this.isGif + return this.mediaType === 'images' || this.isGif || this.isWebp } get supportsPreview(): boolean { From fabcbaec8260b58dc0ba4324beb7815a5a56d664 Mon Sep 17 00:00:00 2001 From: bymyself Date: Wed, 9 Oct 2024 12:05:19 -0700 Subject: [PATCH 25/66] Restore backend state when Playwright test finishes (#1168) * Restore backend state when Playwright test finishes. Resolve #1094 * Add warning when env var not set * Rename and replace with scaffolding option for models dir * Rename * Define another env var [skip ci] * Fix paths [skip ci] * Update README.md --------- Co-authored-by: Chenlei Hu --- .env_example | 4 ++ browser_tests/README.md | 2 +- browser_tests/globalSetup.ts | 20 +++++++++ browser_tests/globalTeardown.ts | 12 ++++++ browser_tests/utils/backupUtils.ts | 69 ++++++++++++++++++++++++++++++ playwright.config.ts | 4 ++ 6 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 browser_tests/globalSetup.ts create mode 100644 browser_tests/globalTeardown.ts create mode 100644 browser_tests/utils/backupUtils.ts diff --git a/.env_example b/.env_example index 3ddf31fa2e..7bebc69367 100644 --- a/.env_example +++ b/.env_example @@ -12,6 +12,10 @@ DEV_SERVER_COMFYUI_URL=http://127.0.0.1:8188 # to ComfyUI launch script to serve the custom web version. DEPLOY_COMFYUI_DIR=/home/ComfyUI/web +# The directory containing the ComfyUI installation used to run Playwright tests. +# If you aren't using a separate install for testing, point this to your regular install. +TEST_COMFYUI_DIR=/home/ComfyUI + # The directory containing the ComfyUI_examples repo used to extract test workflows. EXAMPLE_REPO_PATH=tests-ui/ComfyUI_examples diff --git a/browser_tests/README.md b/browser_tests/README.md index 1a58c89885..b63db0ddbd 100644 --- a/browser_tests/README.md +++ b/browser_tests/README.md @@ -5,7 +5,7 @@ This document outlines the setup and usage of Playwright for testing the ComfyUI ## WARNING The browser tests will change the ComfyUI backend state, such as user settings and saved workflows. -Please backup your ComfyUI data before running the tests locally. +If `TEST_COMFYUI_DIR` in `.env` isn't set to your `(Comfy Path)/ComfyUI` directory, these changes won't be automatically restored. ## Setup diff --git a/browser_tests/globalSetup.ts b/browser_tests/globalSetup.ts new file mode 100644 index 0000000000..86626480f2 --- /dev/null +++ b/browser_tests/globalSetup.ts @@ -0,0 +1,20 @@ +import { FullConfig } from '@playwright/test' +import { backupPath } from './utils/backupUtils' +import dotenv from 'dotenv' + +dotenv.config() + +export default function globalSetup(config: FullConfig) { + if (!process.env.CI) { + if (process.env.TEST_COMFYUI_DIR) { + backupPath([process.env.TEST_COMFYUI_DIR, 'user']) + backupPath([process.env.TEST_COMFYUI_DIR, 'models'], { + renameAndReplaceWithScaffolding: true + }) + } else { + console.warn( + 'Set TEST_COMFYUI_DIR in .env to prevent user data (settings, workflows, etc.) from being overwritten' + ) + } + } +} diff --git a/browser_tests/globalTeardown.ts b/browser_tests/globalTeardown.ts new file mode 100644 index 0000000000..8ebb713396 --- /dev/null +++ b/browser_tests/globalTeardown.ts @@ -0,0 +1,12 @@ +import { FullConfig } from '@playwright/test' +import { restorePath } from './utils/backupUtils' +import dotenv from 'dotenv' + +dotenv.config() + +export default function globalTeardown(config: FullConfig) { + if (!process.env.CI && process.env.TEST_COMFYUI_DIR) { + restorePath([process.env.TEST_COMFYUI_DIR, 'user']) + restorePath([process.env.TEST_COMFYUI_DIR, 'models']) + } +} diff --git a/browser_tests/utils/backupUtils.ts b/browser_tests/utils/backupUtils.ts new file mode 100644 index 0000000000..0d2ae1f4dc --- /dev/null +++ b/browser_tests/utils/backupUtils.ts @@ -0,0 +1,69 @@ +import path from 'path' +import fs from 'fs-extra' + +type PathParts = readonly [string, ...string[]] + +const getBackupPath = (originalPath: string): string => `${originalPath}.bak` + +const resolvePathIfExists = (pathParts: PathParts): string | null => { + const resolvedPath = path.resolve(...pathParts) + if (!fs.pathExistsSync(resolvedPath)) { + console.warn(`Path not found: ${resolvedPath}`) + return null + } + return resolvedPath +} + +const createScaffoldingCopy = (srcDir: string, destDir: string) => { + // Get all items (files and directories) in the source directory + const items = fs.readdirSync(srcDir, { withFileTypes: true }) + + for (const item of items) { + const srcPath = path.join(srcDir, item.name) + const destPath = path.join(destDir, item.name) + + if (item.isDirectory()) { + // Create the corresponding directory in the destination + fs.ensureDirSync(destPath) + + // Recursively copy the directory structure + createScaffoldingCopy(srcPath, destPath) + } + } +} + +export function backupPath( + pathParts: PathParts, + { renameAndReplaceWithScaffolding = false } = {} +) { + const originalPath = resolvePathIfExists(pathParts) + if (!originalPath) return + + const backupPath = getBackupPath(originalPath) + try { + if (renameAndReplaceWithScaffolding) { + // Rename the original path and create scaffolding in its place + fs.moveSync(originalPath, backupPath) + createScaffoldingCopy(backupPath, originalPath) + } else { + // Create a copy of the original path + fs.copySync(originalPath, backupPath) + } + } catch (error) { + console.error(`Failed to backup ${originalPath} from ${backupPath}`, error) + } +} + +export function restorePath(pathParts: PathParts) { + const originalPath = resolvePathIfExists(pathParts) + if (!originalPath) return + + const backupPath = getBackupPath(originalPath) + if (!fs.pathExistsSync(backupPath)) return + + try { + fs.moveSync(backupPath, originalPath, { overwrite: true }) + } catch (error) { + console.error(`Failed to restore ${originalPath} from ${backupPath}`, error) + } +} diff --git a/playwright.config.ts b/playwright.config.ts index 45807ad824..a915a209d0 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -30,6 +30,10 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry' }, + /* Path to global setup file. Exported function runs once before all the tests */ + globalSetup: './browser_tests/globalSetup.ts', + /* Path to global teardown file. Exported function runs once after all the tests */ + globalTeardown: './browser_tests/globalTeardown.ts', /* Configure projects for major browsers */ projects: [ From e99329cff5f74671f03a3d9298f207623fafb296 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 9 Oct 2024 15:10:19 -0400 Subject: [PATCH 26/66] Remove class-transformer dependency (#1187) * nit * Fix test * Remove class-transformer and its deps * nit * Fix invalid type for dummy node --- index.html | 1 - package-lock.json | 12 -- package.json | 2 - src/main.ts | 1 - src/stores/nodeDefStore.ts | 157 ++++++++++------------- tests-ui/globalSetup.ts | 1 - tests-ui/tests/nodeDef.test.ts | 47 +++---- tests-ui/tests/nodeSearchService.test.ts | 3 +- 8 files changed, 84 insertions(+), 140 deletions(-) diff --git a/index.html b/index.html index 07f29df3e2..5e6f66d545 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,6 @@ - diff --git a/package-lock.json b/package-lock.json index 1cbe09f1f5..52b57ede8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,14 +14,12 @@ "@vitejs/plugin-vue": "^5.0.5", "@vueuse/core": "^11.0.0", "axios": "^1.7.4", - "class-transformer": "^0.5.1", "dotenv": "^16.4.5", "fuse.js": "^7.0.0", "lodash": "^4.17.21", "pinia": "^2.1.7", "primeicons": "^7.0.0", "primevue": "^4.0.5", - "reflect-metadata": "^0.2.2", "vue": "^3.4.31", "vue-i18n": "^9.13.1", "vue-router": "^4.4.3", @@ -5313,11 +5311,6 @@ "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, - "node_modules/class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" - }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -10666,11 +10659,6 @@ "node": ">=8.10.0" } }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", diff --git a/package.json b/package.json index 79a515a276..6adbec2b7a 100644 --- a/package.json +++ b/package.json @@ -69,14 +69,12 @@ "@vitejs/plugin-vue": "^5.0.5", "@vueuse/core": "^11.0.0", "axios": "^1.7.4", - "class-transformer": "^0.5.1", "dotenv": "^16.4.5", "fuse.js": "^7.0.0", "lodash": "^4.17.21", "pinia": "^2.1.7", "primeicons": "^7.0.0", "primevue": "^4.0.5", - "reflect-metadata": "^0.2.2", "vue": "^3.4.31", "vue-i18n": "^9.13.1", "vue-router": "^4.4.3", diff --git a/src/main.ts b/src/main.ts index 69b60c253d..7f3f329905 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,6 @@ import Aura from '@primevue/themes/aura' import ConfirmationService from 'primevue/confirmationservice' import ToastService from 'primevue/toastservice' import Tooltip from 'primevue/tooltip' -import 'reflect-metadata' import '@comfyorg/litegraph/style.css' import '@/assets/css/style.css' diff --git a/src/stores/nodeDefStore.ts b/src/stores/nodeDefStore.ts index 3bd00fc3c3..ecd10df712 100644 --- a/src/stores/nodeDefStore.ts +++ b/src/stores/nodeDefStore.ts @@ -2,9 +2,11 @@ import { NodeSearchService, type SearchAuxScore } from '@/services/nodeSearchService' -import { ComfyNodeDef } from '@/types/apiTypes' +import { + type ComfyNodeDef, + type ComfyInputsSpec as ComfyInputsSpecSchema +} from '@/types/apiTypes' import { defineStore } from 'pinia' -import { Type, Transform, plainToClass, Expose } from 'class-transformer' import { ComfyWidgetConstructor } from '@/scripts/widgets' import { TreeNode } from 'primevue/treenode' import { buildTree } from '@/utils/treeUtil' @@ -12,87 +14,60 @@ import { computed, ref } from 'vue' import axios from 'axios' import { type NodeSource, getNodeSource } from '@/types/nodeSource' -export class BaseInputSpec { +export interface BaseInputSpec { name: string type: string tooltip?: string default?: T - @Type(() => Boolean) forceInput?: boolean - - static isInputSpec(obj: any): boolean { - return ( - Array.isArray(obj) && - obj.length >= 1 && - (typeof obj[0] === 'string' || Array.isArray(obj[0])) - ) - } } -export class NumericInputSpec extends BaseInputSpec { - @Type(() => Number) +export interface NumericInputSpec extends BaseInputSpec { min?: number - - @Type(() => Number) max?: number - - @Type(() => Number) step?: number } -export class IntInputSpec extends NumericInputSpec { - type: 'INT' = 'INT' +export interface IntInputSpec extends NumericInputSpec { + type: 'INT' } -export class FloatInputSpec extends NumericInputSpec { - type: 'FLOAT' = 'FLOAT' - - @Type(() => Number) +export interface FloatInputSpec extends NumericInputSpec { + type: 'FLOAT' round?: number } -export class BooleanInputSpec extends BaseInputSpec { - type: 'BOOLEAN' = 'BOOLEAN' - +export interface BooleanInputSpec extends BaseInputSpec { + type: 'BOOLEAN' labelOn?: string labelOff?: string } -export class StringInputSpec extends BaseInputSpec { - type: 'STRING' = 'STRING' - - @Type(() => Boolean) +export interface StringInputSpec extends BaseInputSpec { + type: 'STRING' multiline?: boolean - - @Type(() => Boolean) dynamicPrompts?: boolean } -export class ComboInputSpec extends BaseInputSpec { - type: string = 'COMBO' - - @Transform(({ value }) => value[0]) +export interface ComboInputSpec extends BaseInputSpec { + type: 'COMBO' comboOptions: any[] - - @Type(() => Boolean) controlAfterGenerate?: boolean - - @Type(() => Boolean) imageUpload?: boolean } -export class CustomInputSpec extends BaseInputSpec {} - export class ComfyInputsSpec { - @Transform(({ value }) => ComfyInputsSpec.transformInputSpecRecord(value)) - required: Record = {} - - @Transform(({ value }) => ComfyInputsSpec.transformInputSpecRecord(value)) - optional: Record = {} - + required: Record + optional: Record hidden?: Record + constructor(obj: ComfyInputsSpecSchema) { + this.required = ComfyInputsSpec.transformInputSpecRecord(obj.required) ?? {} + this.optional = ComfyInputsSpec.transformInputSpecRecord(obj.optional) ?? {} + this.hidden = obj.hidden + } + private static transformInputSpecRecord( record: Record ): Record { @@ -104,35 +79,39 @@ export class ComfyInputsSpec { return result } + private static isInputSpec(obj: any): boolean { + return ( + Array.isArray(obj) && + obj.length >= 1 && + (typeof obj[0] === 'string' || Array.isArray(obj[0])) + ) + } + private static transformSingleInputSpec( name: string, value: any ): BaseInputSpec { - if (!BaseInputSpec.isInputSpec(value)) return value + if (!ComfyInputsSpec.isInputSpec(value)) return value const [typeRaw, _spec] = value const spec = _spec ?? {} const type = Array.isArray(typeRaw) ? 'COMBO' : value[0] switch (type) { - case 'INT': - return plainToClass(IntInputSpec, { name, type, ...spec }) - case 'FLOAT': - return plainToClass(FloatInputSpec, { name, type, ...spec }) - case 'BOOLEAN': - return plainToClass(BooleanInputSpec, { name, type, ...spec }) - case 'STRING': - return plainToClass(StringInputSpec, { name, type, ...spec }) case 'COMBO': - return plainToClass(ComboInputSpec, { + return { name, type, ...spec, comboOptions: typeRaw, default: spec.default ?? typeRaw[0] - }) + } as ComboInputSpec + case 'INT': + case 'FLOAT': + case 'BOOLEAN': + case 'STRING': default: - return plainToClass(CustomInputSpec, { name, type, ...spec }) + return { name, type, ...spec } as BaseInputSpec } } @@ -165,42 +144,36 @@ export class ComfyOutputsSpec { } } +/** + * Note: This class does not implement the ComfyNodeDef interface, as we are + * using a custom output spec for output definitions. + */ export class ComfyNodeDefImpl { name: string display_name: string category: string python_module: string description: string - - @Transform(({ value, obj }) => value ?? obj.category === '', { - toClassOnly: true - }) - @Type(() => Boolean) - @Expose() deprecated: boolean - - @Transform( - ({ value, obj }) => value ?? obj.category.startsWith('_for_testing'), - { - toClassOnly: true - } - ) - @Type(() => Boolean) - @Expose() experimental: boolean - - @Type(() => ComfyInputsSpec) input: ComfyInputsSpec - - @Transform(({ obj }) => ComfyNodeDefImpl.transformOutputSpec(obj)) output: ComfyOutputsSpec - - @Transform(({ obj }) => getNodeSource(obj.python_module), { - toClassOnly: true - }) - @Expose() nodeSource: NodeSource + constructor(obj: ComfyNodeDef) { + this.name = obj.name + this.display_name = obj.display_name + this.category = obj.category + this.python_module = obj.python_module + this.description = obj.description + this.deprecated = obj.deprecated ?? obj.category === '' + this.experimental = + obj.experimental ?? obj.category.startsWith('_for_testing') + this.input = new ComfyInputsSpec(obj.input) + this.output = ComfyNodeDefImpl.transformOutputSpec(obj) + this.nodeSource = getNodeSource(obj.python_module) + } + private static transformOutputSpec(obj: any): ComfyOutputsSpec { const { output, output_is_list, output_name, output_tooltips } = obj const result = output.map((type: string | any[], index: number) => { @@ -276,13 +249,17 @@ export function buildNodeDefTree(nodeDefs: ComfyNodeDefImpl[]): TreeNode { } export function createDummyFolderNodeDef(folderPath: string): ComfyNodeDefImpl { - return plainToClass(ComfyNodeDefImpl, { + return new ComfyNodeDefImpl({ name: '', display_name: '', category: folderPath.endsWith('/') ? folderPath.slice(0, -1) : folderPath, python_module: 'nodes', - description: 'Dummy Folder Node (User should never see this string)' - }) + description: 'Dummy Folder Node (User should never see this string)', + input: {}, + output: [], + output_name: [], + output_is_list: [] + } as ComfyNodeDef) } interface State { @@ -325,7 +302,7 @@ export const useNodeDefStore = defineStore('nodeDef', { const newNodeDefsByName: { [key: string]: ComfyNodeDefImpl } = {} const nodeDefsByDisplayName: { [key: string]: ComfyNodeDefImpl } = {} for (const nodeDef of nodeDefs) { - const nodeDefImpl = plainToClass(ComfyNodeDefImpl, nodeDef) + const nodeDefImpl = new ComfyNodeDefImpl(nodeDef) newNodeDefsByName[nodeDef.name] = nodeDefImpl nodeDefsByDisplayName[nodeDef.display_name] = nodeDefImpl } @@ -333,7 +310,7 @@ export const useNodeDefStore = defineStore('nodeDef', { this.nodeDefsByDisplayName = nodeDefsByDisplayName }, addNodeDef(nodeDef: ComfyNodeDef) { - const nodeDefImpl = plainToClass(ComfyNodeDefImpl, nodeDef) + const nodeDefImpl = new ComfyNodeDefImpl(nodeDef) this.nodeDefsByName[nodeDef.name] = nodeDefImpl this.nodeDefsByDisplayName[nodeDef.display_name] = nodeDefImpl }, diff --git a/tests-ui/globalSetup.ts b/tests-ui/globalSetup.ts index 54643f7749..e0e5365f05 100644 --- a/tests-ui/globalSetup.ts +++ b/tests-ui/globalSetup.ts @@ -5,7 +5,6 @@ module.exports = async function () { disconnect() {} } - require('reflect-metadata') const { nop } = require('./utils/nopProxy') global.enableWebGLCanvas = nop diff --git a/tests-ui/tests/nodeDef.test.ts b/tests-ui/tests/nodeDef.test.ts index 01fba5cb43..664531f32d 100644 --- a/tests-ui/tests/nodeDef.test.ts +++ b/tests-ui/tests/nodeDef.test.ts @@ -1,12 +1,9 @@ -import { plainToClass } from 'class-transformer' import { ComfyInputsSpec, IntInputSpec, StringInputSpec, BooleanInputSpec, FloatInputSpec, - CustomInputSpec, - ComboInputSpec, ComfyNodeDefImpl } from '@/stores/nodeDefStore' // Adjust the import path as needed @@ -29,7 +26,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) + const result = new ComfyInputsSpec(plainObject) expect(result).toBeInstanceOf(ComfyInputsSpec) expect(result.required).toBeDefined() @@ -45,10 +42,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.required.intInput).toBeInstanceOf(IntInputSpec) - expect(result.required.stringInput).toBeInstanceOf(StringInputSpec) + const result = new ComfyInputsSpec(plainObject) const intInput = result.required.intInput as IntInputSpec const stringInput = result.required.stringInput as StringInputSpec @@ -73,10 +67,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.optional.booleanInput).toBeInstanceOf(BooleanInputSpec) - expect(result.optional.floatInput).toBeInstanceOf(FloatInputSpec) + const result = new ComfyInputsSpec(plainObject) const booleanInput = result.optional.booleanInput as BooleanInputSpec const floatInput = result.optional.floatInput as FloatInputSpec @@ -96,9 +87,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.optional.comboInput).toBeInstanceOf(ComboInputSpec) + const result = new ComfyInputsSpec(plainObject) expect(result.optional.comboInput.type).toBe('COMBO') expect(result.optional.comboInput.default).toBe(2) }) @@ -110,9 +99,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.optional.comboInput).toBeInstanceOf(ComboInputSpec) + const result = new ComfyInputsSpec(plainObject) expect(result.optional.comboInput.type).toBe('COMBO') // Should pick the first choice as default expect(result.optional.comboInput.default).toBe(1) @@ -125,9 +112,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) - - expect(result.optional.customInput).toBeInstanceOf(CustomInputSpec) + const result = new ComfyInputsSpec(plainObject) expect(result.optional.customInput.type).toBe('CUSTOM_TYPE') expect(result.optional.customInput.default).toBe('custom value') }) @@ -140,7 +125,7 @@ describe('ComfyInputsSpec', () => { } } - const result = plainToClass(ComfyInputsSpec, plainObject) + const result = new ComfyInputsSpec(plainObject) expect(result.hidden).toEqual(plainObject.hidden) expect(result.hidden?.someHiddenValue).toBe(42) @@ -150,7 +135,7 @@ describe('ComfyInputsSpec', () => { it('should handle empty or undefined fields', () => { const plainObject = {} - const result = plainToClass(ComfyInputsSpec, plainObject) + const result = new ComfyInputsSpec(plainObject) expect(result).toBeInstanceOf(ComfyInputsSpec) expect(result.required).toEqual({}) @@ -177,7 +162,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['intOutput'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result).toBeInstanceOf(ComfyNodeDefImpl) expect(result.name).toBe('TestNode') @@ -215,7 +200,7 @@ describe('ComfyNodeDefImpl', () => { deprecated: true } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.deprecated).toBe(true) }) @@ -238,7 +223,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['intOutput'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.deprecated).toBe(true) }) @@ -255,7 +240,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['stringOutput', 'comboOutput', 'floatOutput'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.output.all).toEqual([ { @@ -293,7 +278,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['INT', 'FLOAT', 'FLOAT'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.output.all).toEqual([ { @@ -330,7 +315,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['output', 'output', 'uniqueOutput'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.output.all).toEqual([ { index: 0, @@ -366,7 +351,7 @@ describe('ComfyNodeDefImpl', () => { output_name: [] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.output.all).toEqual([]) }) @@ -393,7 +378,7 @@ describe('ComfyNodeDefImpl', () => { output_name: ['result'] } - const result = plainToClass(ComfyNodeDefImpl, plainObject) + const result = new ComfyNodeDefImpl(plainObject) expect(result.input).toBeInstanceOf(ComfyInputsSpec) expect(result.input.required).toBeDefined() diff --git a/tests-ui/tests/nodeSearchService.test.ts b/tests-ui/tests/nodeSearchService.test.ts index fea642ac90..e89244a309 100644 --- a/tests-ui/tests/nodeSearchService.test.ts +++ b/tests-ui/tests/nodeSearchService.test.ts @@ -1,6 +1,5 @@ import { NodeSearchService } from '@/services/nodeSearchService' import { ComfyNodeDefImpl } from '@/stores/nodeDefStore' -import { plainToClass } from 'class-transformer' const EXAMPLE_NODE_DEFS: ComfyNodeDefImpl[] = [ { @@ -52,7 +51,7 @@ const EXAMPLE_NODE_DEFS: ComfyNodeDefImpl[] = [ output_node: false } ].map((nodeDef) => { - const def = plainToClass(ComfyNodeDefImpl, nodeDef) + const def = new ComfyNodeDefImpl(nodeDef) def['postProcessSearchScores'] = (s) => s return def }) From 1c5fd2465e175f044c5d51ef49c42a05cee6ba51 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 9 Oct 2024 15:16:43 -0400 Subject: [PATCH 27/66] Move vitejs/plugin-vue to devDep (#1189) --- package-lock.json | 60 +++++++++++++++++++++++++++++++++++++++++------ package.json | 2 +- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52b57ede8c..9084ec8b53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "@atlaskit/pragmatic-drag-and-drop": "^1.2.1", "@comfyorg/litegraph": "^0.8.0", "@primevue/themes": "^4.0.5", - "@vitejs/plugin-vue": "^5.0.5", "@vueuse/core": "^11.0.0", "axios": "^1.7.4", "dotenv": "^16.4.5", @@ -36,6 +35,7 @@ "@types/jest": "^29.5.12", "@types/lodash": "^4.17.6", "@types/node": "^20.14.8", + "@vitejs/plugin-vue": "^5.1.4", "@vue/test-utils": "^2.4.6", "@vue/vue3-jest": "^29.2.6", "autoprefixer": "^10.4.19", @@ -1943,6 +1943,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "aix" @@ -1958,6 +1959,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -1973,6 +1975,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -1988,6 +1991,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "android" @@ -2003,6 +2007,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -2018,6 +2023,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -2033,6 +2039,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -2048,6 +2055,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -2063,6 +2071,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2078,6 +2087,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2093,6 +2103,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2108,6 +2119,7 @@ "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2123,6 +2135,7 @@ "cpu": [ "mips64el" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2138,6 +2151,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2153,6 +2167,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2168,6 +2183,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2183,6 +2199,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -2198,6 +2215,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "netbsd" @@ -2213,6 +2231,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -2228,6 +2247,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "sunos" @@ -2243,6 +2263,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -2258,6 +2279,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -2273,6 +2295,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -3556,6 +3579,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -3568,6 +3592,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -3580,6 +3605,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -3592,6 +3618,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -3604,6 +3631,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3616,6 +3644,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3628,6 +3657,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3640,6 +3670,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3652,6 +3683,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3664,6 +3696,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3676,6 +3709,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3688,6 +3722,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3700,6 +3735,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3712,6 +3748,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -3724,6 +3761,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -3736,6 +3774,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -3842,7 +3881,8 @@ "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true }, "node_modules/@types/graceful-fs": { "version": "4.1.9", @@ -3915,7 +3955,7 @@ "version": "20.14.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", - "devOptional": true, + "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -4210,9 +4250,11 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz", - "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", + "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", + "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" }, @@ -5989,6 +6031,7 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -6745,6 +6788,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -10836,6 +10880,7 @@ "version": "4.22.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "dev": true, "dependencies": { "@types/estree": "1.0.5" }, @@ -11804,7 +11849,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "devOptional": true + "dev": true }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -12059,6 +12104,7 @@ "version": "5.4.6", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "dev": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/package.json b/package.json index 6adbec2b7a..9214656b66 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@types/jest": "^29.5.12", "@types/lodash": "^4.17.6", "@types/node": "^20.14.8", + "@vitejs/plugin-vue": "^5.1.4", "@vue/test-utils": "^2.4.6", "@vue/vue3-jest": "^29.2.6", "autoprefixer": "^10.4.19", @@ -66,7 +67,6 @@ "@atlaskit/pragmatic-drag-and-drop": "^1.2.1", "@comfyorg/litegraph": "^0.8.0", "@primevue/themes": "^4.0.5", - "@vitejs/plugin-vue": "^5.0.5", "@vueuse/core": "^11.0.0", "axios": "^1.7.4", "dotenv": "^16.4.5", From f6466d7062b15f335cc6813498e0ec9c876e7fc3 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 9 Oct 2024 16:02:14 -0400 Subject: [PATCH 28/66] Avoid calling settingStore.set when there is no legacy node bookmark (#1191) * Avoid calling settingStore.set when there is no legacy node bookmark * nit --- src/stores/nodeBookmarkStore.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/stores/nodeBookmarkStore.ts b/src/stores/nodeBookmarkStore.ts index 091b5cb57e..15047375f2 100644 --- a/src/stores/nodeBookmarkStore.ts +++ b/src/stores/nodeBookmarkStore.ts @@ -15,21 +15,24 @@ export const useNodeBookmarkStore = defineStore('nodeBookmark', () => { const nodeDefStore = useNodeDefStore() const migrateLegacyBookmarks = () => { - settingStore - .get('Comfy.NodeLibrary.Bookmarks') - .forEach((bookmark: string) => { - // If the bookmark is a folder, add it as a bookmark - if (bookmark.endsWith('/')) { - addBookmark(bookmark) - return - } - const category = bookmark.split('/').slice(0, -1).join('/') - const displayName = bookmark.split('/').pop() - const nodeDef = nodeDefStore.nodeDefsByDisplayName[displayName] + const legacyBookmarks = settingStore.get('Comfy.NodeLibrary.Bookmarks') + if (!legacyBookmarks.length) { + return + } - if (!nodeDef) return - addBookmark(`${category === '' ? '' : category + '/'}${nodeDef.name}`) - }) + legacyBookmarks.forEach((bookmark: string) => { + // If the bookmark is a folder, add it as a bookmark + if (bookmark.endsWith('/')) { + addBookmark(bookmark) + return + } + const category = bookmark.split('/').slice(0, -1).join('/') + const displayName = bookmark.split('/').pop() + const nodeDef = nodeDefStore.nodeDefsByDisplayName[displayName] + + if (!nodeDef) return + addBookmark(`${category === '' ? '' : category + '/'}${nodeDef.name}`) + }) settingStore.set('Comfy.NodeLibrary.Bookmarks', []) } From f94bdc358bb5053bd6763a91b07e24b1f3701481 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 9 Oct 2024 16:02:27 -0400 Subject: [PATCH 29/66] Disable node def validation by default (#1190) * Add setting * Make node def validation optional --- src/scripts/api.ts | 8 +++++++- src/scripts/app.ts | 8 ++++++-- src/stores/coreSettings.ts | 9 +++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/scripts/api.ts b/src/scripts/api.ts index 30a35dcf53..cfde56b614 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -273,9 +273,15 @@ class ComfyApi extends EventTarget { * Loads node object definitions for the graph * @returns The node definitions */ - async getNodeDefs(): Promise> { + async getNodeDefs({ validate = false }: { validate?: boolean } = {}): Promise< + Record + > { const resp = await this.fetchApi('/object_info', { cache: 'no-store' }) const objectInfoUnsafe = await resp.json() + if (!validate) { + return objectInfoUnsafe + } + // Validate node definitions against zod schema. (slow) const objectInfo: Record = {} for (const key in objectInfoUnsafe) { const validatedDef = validateComfyNodeDef( diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 11a3b4a8cb..858cc7a484 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -1968,7 +1968,9 @@ export class ComfyApp { */ async registerNodes() { // Load node definitions from the backend - const defs = await api.getNodeDefs() + const defs = await api.getNodeDefs({ + validate: useSettingStore().get('Comfy.Validation.NodeDefs') + }) await this.registerNodesFromDefs(defs) await this.#invokeExtensionsAsync('registerCustomNodes') if (this.vueAppReady) { @@ -2911,7 +2913,9 @@ export class ComfyApp { useModelStore().clearCache() } - const defs = await api.getNodeDefs() + const defs = await api.getNodeDefs({ + validate: useSettingStore().get('Comfy.Validation.NodeDefs') + }) for (const nodeId in defs) { this.registerNodeDef(nodeId, defs[nodeId]) diff --git a/src/stores/coreSettings.ts b/src/stores/coreSettings.ts index 72ad7f5588..99b7581898 100644 --- a/src/stores/coreSettings.ts +++ b/src/stores/coreSettings.ts @@ -435,5 +435,14 @@ export const CORE_SETTINGS: SettingParams[] = [ defaultValue: false, experimental: true, versionAdded: '1.3.11' + }, + { + id: 'Comfy.Validation.NodeDefs', + name: 'Validate node definitions (slow)', + type: 'boolean', + tooltip: + 'Recommended for node developers. This will validate all node definitions on startup.', + defaultValue: false, + versionAdded: '1.3.14' } ] From 82112c2c6e847f76742d2f0d6a49f411d1db3102 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 9 Oct 2024 16:22:58 -0400 Subject: [PATCH 30/66] Move low priority init to idle task (#1192) --- src/components/graph/GraphCanvas.vue | 20 +------------------- src/views/GraphView.vue | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 71d4265469..49f47b179e 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -26,11 +26,7 @@ import { ref, computed, onUnmounted, onMounted, watchEffect } from 'vue' import { app as comfyApp } from '@/scripts/app' import { useSettingStore } from '@/stores/settingStore' import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter' -import { - ComfyNodeDefImpl, - useNodeDefStore, - useNodeFrequencyStore -} from '@/stores/nodeDefStore' +import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore' import { useWorkspaceStore } from '@/stores/workspaceStateStore' import { LiteGraph, @@ -44,7 +40,6 @@ import { LGraphBadge } from '@comfyorg/litegraph' import type { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes' -import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore' import { useCanvasStore } from '@/stores/graphStore' import { ComfyModelDef } from '@/stores/modelStore' import { @@ -52,7 +47,6 @@ import { useModelToNodeStore } from '@/stores/modelToNodeStore' import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue' -import { useKeybindingStore } from '@/stores/keybindingStore' const emit = defineEmits(['ready']) const canvasRef = ref(null) @@ -202,18 +196,6 @@ onMounted(async () => { } }) - // Load keybindings. This must be done after comfyApp loads settings. - useKeybindingStore().loadUserKeybindings() - - // Migrate legacy bookmarks - useNodeBookmarkStore().migrateLegacyBookmarks() - - // Explicitly initialize nodeSearchService to avoid indexing delay when - // node search is triggered - useNodeDefStore().nodeSearchService.endsWithFilterStartSequence('') - - // Non-blocking load of node frequencies - useNodeFrequencyStore().loadNodeFrequencies() emit('ready') }) diff --git a/src/views/GraphView.vue b/src/views/GraphView.vue index 9b08d152c3..850c820546 100644 --- a/src/views/GraphView.vue +++ b/src/views/GraphView.vue @@ -2,7 +2,7 @@ - + @@ -34,6 +34,8 @@ import TopMenubar from '@/components/topbar/TopMenubar.vue' import { setupAutoQueueHandler } from '@/services/autoQueueService' import { useKeybindingStore } from '@/stores/keybindingStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' +import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore' +import { useNodeDefStore, useNodeFrequencyStore } from '@/stores/nodeDefStore' setupAutoQueueHandler() @@ -147,4 +149,26 @@ onBeforeUnmount(() => { api.removeEventListener('reconnected', onReconnected) executionStore.unbindExecutionEvents() }) + +const onGraphReady = () => { + requestIdleCallback( + () => { + // Setting values now available after comfyApp.setup. + // Load keybindings. + useKeybindingStore().loadUserKeybindings() + + // Migrate legacy bookmarks + useNodeBookmarkStore().migrateLegacyBookmarks() + + // Node defs now available after comfyApp.setup. + // Explicitly initialize nodeSearchService to avoid indexing delay when + // node search is triggered + useNodeDefStore().nodeSearchService.endsWithFilterStartSequence('') + + // Non-blocking load of node frequencies + useNodeFrequencyStore().loadNodeFrequencies() + }, + { timeout: 1000 } + ) +} From 32fa950aa1873cdaab81ed241ed5e56576d1823e Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 9 Oct 2024 16:41:38 -0400 Subject: [PATCH 31/66] 1.3.14 (#1194) --- 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 9084ec8b53..7fe7bcd93e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "comfyui-frontend", - "version": "1.3.13", + "version": "1.3.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "comfyui-frontend", - "version": "1.3.13", + "version": "1.3.14", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "^1.2.1", "@comfyorg/litegraph": "^0.8.0", diff --git a/package.json b/package.json index 9214656b66..93b342f5a0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "comfyui-frontend", "private": true, - "version": "1.3.13", + "version": "1.3.14", "type": "module", "scripts": { "dev": "vite", From 2d5faa7f3d33bcd2bc87a5f5d4ac8613f9625a51 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 9 Oct 2024 17:41:36 -0400 Subject: [PATCH 32/66] Anchor floating actionbar to closest side when resizing window (#1195) * Anchor floating actionbar to closest side when resizing window * nit * nit --- src/components/actionbar/ComfyActionbar.vue | 57 +++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/components/actionbar/ComfyActionbar.vue b/src/components/actionbar/ComfyActionbar.vue index c7007d2e23..978e7a697e 100644 --- a/src/components/actionbar/ComfyActionbar.vue +++ b/src/components/actionbar/ComfyActionbar.vue @@ -83,6 +83,7 @@ const setInitialPosition = () => { if (storedPosition.value.x !== 0 || storedPosition.value.y !== 0) { x.value = storedPosition.value.x y.value = storedPosition.value.y + captureLastDragState() return } if (panelRef.value) { @@ -97,6 +98,7 @@ const setInitialPosition = () => { x.value = (screenWidth - menuWidth) / 2 y.value = screenHeight - menuHeight - 10 // 10px margin from bottom + captureLastDragState() } } onMounted(setInitialPosition) @@ -106,6 +108,31 @@ watch(visible, (newVisible) => { } }) +const lastDragState = ref({ + x: x.value, + y: y.value, + windowWidth: window.innerWidth, + windowHeight: window.innerHeight +}) +const captureLastDragState = () => { + lastDragState.value = { + x: x.value, + y: y.value, + windowWidth: window.innerWidth, + windowHeight: window.innerHeight + } +} +watch( + isDragging, + (newIsDragging) => { + if (!newIsDragging) { + // Stop dragging + captureLastDragState() + } + }, + { immediate: true } +) + const adjustMenuPosition = () => { if (panelRef.value) { const screenWidth = window.innerWidth @@ -113,10 +140,34 @@ const adjustMenuPosition = () => { const menuWidth = panelRef.value.offsetWidth const menuHeight = panelRef.value.offsetHeight - // Adjust x position if menu is off-screen horizontally - x.value = clamp(x.value, 0, screenWidth - menuWidth) + // Calculate the distance from each edge + const distanceRight = + lastDragState.value.windowWidth - (lastDragState.value.x + menuWidth) + const distanceBottom = + lastDragState.value.windowHeight - (lastDragState.value.y + menuHeight) - // Adjust y position if menu is off-screen vertically + // Determine if the menu is closer to right/bottom or left/top + const anchorRight = distanceRight < lastDragState.value.x + const anchorBottom = distanceBottom < lastDragState.value.y + + // Calculate new position + if (anchorRight) { + x.value = + screenWidth - (lastDragState.value.windowWidth - lastDragState.value.x) + } else { + x.value = lastDragState.value.x + } + + if (anchorBottom) { + y.value = + screenHeight - + (lastDragState.value.windowHeight - lastDragState.value.y) + } else { + y.value = lastDragState.value.y + } + + // Ensure the menu stays within the screen bounds + x.value = clamp(x.value, 0, screenWidth - menuWidth) y.value = clamp(y.value, 0, screenHeight - menuHeight) } } From 59a5f5f5d03bd30f60ef2e716cdb0bfd3785d0e5 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 9 Oct 2024 20:35:17 -0400 Subject: [PATCH 33/66] Add help menu on command menu bar (#1197) * Add help menu on command menu bar * nit --- src/components/topbar/CommandMenubar.vue | 7 ++++++- src/stores/menuItemStore.ts | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/components/topbar/CommandMenubar.vue b/src/components/topbar/CommandMenubar.vue index cfa79486e7..2e2913c0f3 100644 --- a/src/components/topbar/CommandMenubar.vue +++ b/src/components/topbar/CommandMenubar.vue @@ -9,7 +9,12 @@ }" > diff --git a/src/components/sidebar/tabs/WorkflowsSidebarTab.vue b/src/components/sidebar/tabs/WorkflowsSidebarTab.vue index 3506be4f43..10c76193ef 100644 --- a/src/components/sidebar/tabs/WorkflowsSidebarTab.vue +++ b/src/components/sidebar/tabs/WorkflowsSidebarTab.vue @@ -23,13 +23,15 @@ text /> -