Skip to content

Commit c06bc63

Browse files
[feat] Update node output system to use NodeLocatorIds (#4500)
1 parent 052d532 commit c06bc63

File tree

7 files changed

+961
-52
lines changed

7 files changed

+961
-52
lines changed

src/extensions/core/uploadAudio.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { LGraphNode } from '@comfyorg/litegraph'
22
import type { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets'
33

4+
import { useChainCallback } from '@/composables/functional/useChainCallback'
45
import { useNodeDragAndDrop } from '@/composables/node/useNodeDragAndDrop'
56
import { useNodeFileInput } from '@/composables/node/useNodeFileInput'
67
import { useNodePaste } from '@/composables/node/useNodePaste'
@@ -9,6 +10,8 @@ import type { ResultItemType } from '@/schemas/apiSchema'
910
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
1011
import type { DOMWidget } from '@/scripts/domWidget'
1112
import { useToastStore } from '@/stores/toastStore'
13+
import { NodeLocatorId } from '@/types'
14+
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
1215

1316
import { api } from '../../scripts/api'
1417
import { app } from '../../scripts/app'
@@ -137,14 +140,27 @@ app.registerExtension({
137140
audioUIWidget.element.classList.remove('empty-audio-widget')
138141
}
139142
}
143+
144+
audioUIWidget.onRemove = useChainCallback(
145+
audioUIWidget.onRemove,
146+
() => {
147+
if (!audioUIWidget.element) return
148+
audioUIWidget.element.pause()
149+
audioUIWidget.element.src = ''
150+
audioUIWidget.element.remove()
151+
}
152+
)
153+
140154
return { widget: audioUIWidget }
141155
}
142156
}
143157
},
144-
onNodeOutputsUpdated(nodeOutputs: Record<number, any>) {
145-
for (const [nodeId, output] of Object.entries(nodeOutputs)) {
146-
const node = app.graph.getNodeById(nodeId)
158+
onNodeOutputsUpdated(nodeOutputs: Record<NodeLocatorId, any>) {
159+
for (const [nodeLocatorId, output] of Object.entries(nodeOutputs)) {
147160
if ('audio' in output) {
161+
const node = getNodeByLocatorId(app.graph, nodeLocatorId)
162+
if (!node) continue
163+
148164
// @ts-expect-error fixme ts strict error
149165
const audioUIWidget = node.widgets.find(
150166
(w) => w.name === 'audioUI'

src/scripts/app.ts

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { useDomWidgetStore } from '@/stores/domWidgetStore'
4747
import { useExecutionStore } from '@/stores/executionStore'
4848
import { useExtensionStore } from '@/stores/extensionStore'
4949
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
50+
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
5051
import { KeyComboImpl, useKeybindingStore } from '@/stores/keybindingStore'
5152
import { useModelStore } from '@/stores/modelStore'
5253
import { SYSTEM_NODE_DEFS, useNodeDefStore } from '@/stores/nodeDefStore'
@@ -60,6 +61,10 @@ import type { ComfyExtension, MissingNodeType } from '@/types/comfy'
6061
import { ExtensionManager } from '@/types/extensionTypes'
6162
import { ColorAdjustOptions, adjustColor } from '@/utils/colorUtil'
6263
import { graphToPrompt } from '@/utils/executionUtil'
64+
import {
65+
getNodeByExecutionId,
66+
triggerCallbackOnAllNodes
67+
} from '@/utils/graphTraversalUtil'
6368
import {
6469
executeWidgetsCallback,
6570
fixLinkInputSlots,
@@ -640,29 +645,21 @@ export class ComfyApp {
640645
})
641646

642647
api.addEventListener('executed', ({ detail }) => {
643-
const output = this.nodeOutputs[detail.display_node || detail.node]
644-
if (detail.merge && output) {
645-
for (const k in detail.output ?? {}) {
646-
const v = output[k]
647-
if (v instanceof Array) {
648-
output[k] = v.concat(detail.output[k])
649-
} else {
650-
output[k] = detail.output[k]
651-
}
652-
}
653-
} else {
654-
this.nodeOutputs[detail.display_node || detail.node] = detail.output
655-
}
656-
const node = this.graph.getNodeById(detail.display_node || detail.node)
657-
if (node) {
658-
if (node.onExecuted) node.onExecuted(detail.output)
648+
const nodeOutputStore = useNodeOutputStore()
649+
const executionId = String(detail.display_node || detail.node)
650+
651+
nodeOutputStore.setNodeOutputsByExecutionId(executionId, detail.output, {
652+
merge: detail.merge
653+
})
654+
655+
const node = getNodeByExecutionId(this.graph, executionId)
656+
if (node && node.onExecuted) {
657+
node.onExecuted(detail.output)
659658
}
660659
})
661660

662661
api.addEventListener('execution_start', () => {
663-
this.graph.nodes.forEach((node) => {
664-
if (node.onExecutionStart) node.onExecutionStart()
665-
})
662+
triggerCallbackOnAllNodes(this.graph, 'onExecutionStart')
666663
})
667664

668665
api.addEventListener('execution_error', ({ detail }) => {
@@ -690,11 +687,13 @@ export class ComfyApp {
690687
api.addEventListener('b_preview_with_metadata', ({ detail }) => {
691688
// Enhanced preview with explicit node context
692689
const { blob, displayNodeId } = detail
690+
const { setNodePreviewsByExecutionId, revokePreviewsByExecutionId } =
691+
useNodeOutputStore()
693692
// Ensure clean up if `executing` event is missed.
694-
this.revokePreviews(displayNodeId)
693+
revokePreviewsByExecutionId(displayNodeId)
695694
const blobUrl = URL.createObjectURL(blob)
696-
// Preview cleanup is now handled in progress_state event to support multiple concurrent previews
697-
this.nodePreviewImages[displayNodeId] = [blobUrl]
695+
// Preview cleanup is handled in progress_state event to support multiple concurrent previews
696+
setNodePreviewsByExecutionId(displayNodeId, [blobUrl])
698697
})
699698

700699
api.init()
@@ -1673,25 +1672,13 @@ export class ComfyApp {
16731672
}
16741673
}
16751674

1676-
/**
1677-
* Frees memory allocated to image preview blobs for a specific node, by revoking the URLs associated with them.
1678-
* @param nodeId ID of the node to revoke all preview images of
1679-
*/
1680-
revokePreviews(nodeId: NodeId) {
1681-
if (!this.nodePreviewImages[nodeId]?.[Symbol.iterator]) return
1682-
for (const url of this.nodePreviewImages[nodeId]) {
1683-
URL.revokeObjectURL(url)
1684-
}
1685-
}
16861675
/**
16871676
* Clean current state
16881677
*/
16891678
clean() {
16901679
this.nodeOutputs = {}
1691-
for (const id of Object.keys(this.nodePreviewImages)) {
1692-
this.revokePreviews(id)
1693-
}
1694-
this.nodePreviewImages = {}
1680+
const { revokeAllPreviews } = useNodeOutputStore()
1681+
revokeAllPreviews()
16951682
const executionStore = useExecutionStore()
16961683
executionStore.lastNodeErrors = null
16971684
executionStore.lastExecutionError = null

src/stores/executionStore.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
} from '@/schemas/comfyWorkflowSchema'
2525
import { api } from '@/scripts/api'
2626
import { app } from '@/scripts/app'
27+
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
2728
import type { NodeLocatorId } from '@/types/nodeIdentification'
2829
import { createNodeLocatorId } from '@/types/nodeIdentification'
2930

@@ -229,9 +230,9 @@ export const useExecutionStore = defineStore('execution', () => {
229230
api.addEventListener('progress_state', handleProgressState)
230231
api.addEventListener('status', handleStatus)
231232
api.addEventListener('execution_error', handleExecutionError)
233+
api.addEventListener('progress_text', handleProgressText)
234+
api.addEventListener('display_component', handleDisplayComponent)
232235
}
233-
api.addEventListener('progress_text', handleProgressText)
234-
api.addEventListener('display_component', handleDisplayComponent)
235236

236237
function unbindExecutionEvents() {
237238
api.removeEventListener('execution_start', handleExecutionStart)
@@ -244,6 +245,7 @@ export const useExecutionStore = defineStore('execution', () => {
244245
api.removeEventListener('status', handleStatus)
245246
api.removeEventListener('execution_error', handleExecutionError)
246247
api.removeEventListener('progress_text', handleProgressText)
248+
api.removeEventListener('display_component', handleDisplayComponent)
247249
}
248250

249251
function handleExecutionStart(e: CustomEvent<ExecutionStartWsMessage>) {
@@ -294,8 +296,8 @@ export const useExecutionStore = defineStore('execution', () => {
294296
// Note that we're doing the *actual* node id instead of the display node id
295297
// here intentionally. That way, we don't clear the preview every time a new node
296298
// within an expanded graph starts executing.
297-
app.revokePreviews(nodeId)
298-
delete app.nodePreviewImages[nodeId]
299+
const { revokePreviewsByExecutionId } = useNodeOutputStore()
300+
revokePreviewsByExecutionId(nodeId)
299301
}
300302
}
301303

0 commit comments

Comments
 (0)