Skip to content

Commit 7eb3eb2

Browse files
guillgithub-actionschristian-byrne
authored
Update the frontend to support async nodes. (#4382)
Co-authored-by: github-actions <[email protected]> Co-authored-by: Christian Byrne <[email protected]>
1 parent ff68c42 commit 7eb3eb2

28 files changed

+1185
-120
lines changed

src/components/graph/GraphCanvas.vue

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ import { useWorkflowAutoSave } from '@/composables/useWorkflowAutoSave'
7272
import { useWorkflowPersistence } from '@/composables/useWorkflowPersistence'
7373
import { CORE_SETTINGS } from '@/constants/coreSettings'
7474
import { i18n, t } from '@/i18n'
75-
import type { NodeId } from '@/schemas/comfyWorkflowSchema'
7675
import { UnauthorizedError, api } from '@/scripts/api'
7776
import { app as comfyApp } from '@/scripts/app'
7877
import { ChangeTracker } from '@/scripts/changeTracker'
@@ -192,22 +191,26 @@ watch(
192191
}
193192
)
194193
195-
// Update the progress of the executing node
194+
// Update the progress of executing nodes
196195
watch(
197196
() =>
198-
[
199-
executionStore.executingNodeId,
200-
executionStore.executingNodeProgress
201-
] satisfies [NodeId | null, number | null],
202-
([executingNodeId, executingNodeProgress]) => {
203-
for (const node of comfyApp.graph.nodes) {
204-
if (node.id == executingNodeId) {
205-
node.progress = executingNodeProgress ?? undefined
197+
[executionStore.nodeLocationProgressStates, canvasStore.canvas] as const,
198+
([nodeLocationProgressStates, canvas]) => {
199+
if (!canvas?.graph) return
200+
for (const node of canvas.graph.nodes) {
201+
const nodeLocatorId = useWorkflowStore().nodeIdToNodeLocatorId(node.id)
202+
const progressState = nodeLocationProgressStates[nodeLocatorId]
203+
if (progressState && progressState.state === 'running') {
204+
node.progress = progressState.value / progressState.max
206205
} else {
207206
node.progress = undefined
208207
}
209208
}
210-
}
209+
210+
// Force canvas redraw to ensure progress updates are visible
211+
canvas.graph.setDirtyCanvas(true, false)
212+
},
213+
{ deep: true }
211214
)
212215
213216
// Update node slot errors

src/components/graph/widgets/TextPreviewWidget.vue

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,44 @@ import { useExecutionStore } from '@/stores/executionStore'
2020
import { linkifyHtml, nl2br } from '@/utils/formatUtil'
2121
2222
const modelValue = defineModel<string>({ required: true })
23-
defineProps<{
23+
const props = defineProps<{
2424
widget?: object
25+
nodeId: NodeId
2526
}>()
2627
2728
const executionStore = useExecutionStore()
2829
const isParentNodeExecuting = ref(true)
2930
const formattedText = computed(() => nl2br(linkifyHtml(modelValue.value)))
3031
31-
let executingNodeId: NodeId | null = null
32+
let parentNodeId: NodeId | null = null
3233
onMounted(() => {
33-
executingNodeId = executionStore.executingNodeId
34+
// Get the parent node ID from props if provided
35+
// For backward compatibility, fall back to the first executing node
36+
parentNodeId = props.nodeId
3437
})
3538
3639
// Watch for either a new node has starting execution or overall execution ending
3740
const stopWatching = watch(
38-
[() => executionStore.executingNode, () => executionStore.isIdle],
41+
[() => executionStore.executingNodeIds, () => executionStore.isIdle],
3942
() => {
43+
if (executionStore.isIdle) {
44+
isParentNodeExecuting.value = false
45+
stopWatching()
46+
return
47+
}
48+
49+
// Check if parent node is no longer in the executing nodes list
4050
if (
41-
executionStore.isIdle ||
42-
(executionStore.executingNode &&
43-
executionStore.executingNode.id !== executingNodeId)
51+
parentNodeId &&
52+
!executionStore.executingNodeIds.includes(parentNodeId)
4453
) {
4554
isParentNodeExecuting.value = false
4655
stopWatching()
4756
}
48-
if (!executingNodeId) {
49-
executingNodeId = executionStore.executingNodeId
57+
58+
// Set parent node ID if not set yet
59+
if (!parentNodeId && executionStore.executingNodeIds.length > 0) {
60+
parentNodeId = executionStore.executingNodeIds[0]
5061
}
5162
}
5263
)

src/composables/useBrowserTabTitle.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useTitle } from '@vueuse/core'
22
import { computed } from 'vue'
33

4+
import { t } from '@/i18n'
45
import { useExecutionStore } from '@/stores/executionStore'
56
import { useSettingStore } from '@/stores/settingStore'
67
import { useWorkflowStore } from '@/stores/workflowStore'
@@ -36,11 +37,34 @@ export const useBrowserTabTitle = () => {
3637
: DEFAULT_TITLE
3738
})
3839

39-
const nodeExecutionTitle = computed(() =>
40-
executionStore.executingNode && executionStore.executingNodeProgress
41-
? `${executionText.value}[${Math.round(executionStore.executingNodeProgress * 100)}%] ${executionStore.executingNode.type}`
42-
: ''
43-
)
40+
const nodeExecutionTitle = computed(() => {
41+
// Check if any nodes are in progress
42+
const nodeProgressEntries = Object.entries(
43+
executionStore.nodeProgressStates
44+
)
45+
const runningNodes = nodeProgressEntries.filter(
46+
([_, state]) => state.state === 'running'
47+
)
48+
49+
if (runningNodes.length === 0) {
50+
return ''
51+
}
52+
53+
// If multiple nodes are running
54+
if (runningNodes.length > 1) {
55+
return `${executionText.value}[${runningNodes.length} ${t('g.nodesRunning', 'nodes running')}]`
56+
}
57+
58+
// If only one node is running
59+
const [nodeId, state] = runningNodes[0]
60+
const progress = Math.round((state.value / state.max) * 100)
61+
const nodeType =
62+
executionStore.activePrompt?.workflow?.changeTracker?.activeState.nodes.find(
63+
(n) => String(n.id) === nodeId
64+
)?.type || 'Node'
65+
66+
return `${executionText.value}[${progress}%] ${nodeType}`
67+
})
4468

4569
const workflowTitle = computed(
4670
() =>

src/composables/widgets/useChatHistoryWidget.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,23 @@ import { ref } from 'vue'
33

44
import ChatHistoryWidget from '@/components/graph/widgets/ChatHistoryWidget.vue'
55
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
6-
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
6+
import {
7+
ComponentWidgetImpl,
8+
type ComponentWidgetStandardProps,
9+
addWidget
10+
} from '@/scripts/domWidget'
711
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
812

13+
type ChatHistoryCustomProps = Omit<
14+
InstanceType<typeof ChatHistoryWidget>['$props'],
15+
ComponentWidgetStandardProps
16+
>
17+
918
const PADDING = 16
1019

1120
export const useChatHistoryWidget = (
1221
options: {
13-
props?: Omit<InstanceType<typeof ChatHistoryWidget>['$props'], 'widget'>
22+
props?: ChatHistoryCustomProps
1423
} = {}
1524
) => {
1625
const widgetConstructor: ComfyWidgetConstructorV2 = (
@@ -20,7 +29,7 @@ export const useChatHistoryWidget = (
2029
const widgetValue = ref<string>('')
2130
const widget = new ComponentWidgetImpl<
2231
string | object,
23-
InstanceType<typeof ChatHistoryWidget>['$props']
32+
ChatHistoryCustomProps
2433
>({
2534
node,
2635
name: inputSpec.name,

src/composables/widgets/useProgressTextWidget.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,18 @@ import { ref } from 'vue'
33

44
import TextPreviewWidget from '@/components/graph/widgets/TextPreviewWidget.vue'
55
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
6-
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
6+
import {
7+
ComponentWidgetImpl,
8+
type ComponentWidgetStandardProps,
9+
addWidget
10+
} from '@/scripts/domWidget'
711
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
812

13+
type TextPreviewCustomProps = Omit<
14+
InstanceType<typeof TextPreviewWidget>['$props'],
15+
ComponentWidgetStandardProps
16+
>
17+
918
const PADDING = 16
1019

1120
export const useTextPreviewWidget = (
@@ -18,11 +27,17 @@ export const useTextPreviewWidget = (
1827
inputSpec: InputSpec
1928
) => {
2029
const widgetValue = ref<string>('')
21-
const widget = new ComponentWidgetImpl<string | object>({
30+
const widget = new ComponentWidgetImpl<
31+
string | object,
32+
TextPreviewCustomProps
33+
>({
2234
node,
2335
name: inputSpec.name,
2436
component: TextPreviewWidget,
2537
inputSpec,
38+
props: {
39+
nodeId: node.id
40+
},
2641
options: {
2742
getValue: () => widgetValue.value,
2843
setValue: (value: string | object) => {

src/config/clientFeatureFlags.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"supports_preview_metadata": false
2+
"supports_preview_metadata": true
33
}

src/extensions/core/groupNode.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from '@/schemas/comfyWorkflowSchema'
1616
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
1717
import { useDialogService } from '@/services/dialogService'
18+
import { useExecutionStore } from '@/stores/executionStore'
1819
import { useNodeDefStore } from '@/stores/nodeDefStore'
1920
import { useToastStore } from '@/stores/toastStore'
2021
import { useWidgetStore } from '@/stores/widgetStore'
@@ -1224,9 +1225,10 @@ export class GroupNodeHandler {
12241225
node.onDrawForeground = function (ctx) {
12251226
// @ts-expect-error fixme ts strict error
12261227
onDrawForeground?.apply?.(this, arguments)
1228+
const progressState = useExecutionStore().nodeProgressStates[this.id]
12271229
if (
1228-
// @ts-expect-error fixme ts strict error
1229-
+app.runningNodeId === this.id &&
1230+
progressState &&
1231+
progressState.state === 'running' &&
12301232
this.runningInternalNodeId !== null
12311233
) {
12321234
// @ts-expect-error fixme ts strict error
@@ -1340,6 +1342,7 @@ export class GroupNodeHandler {
13401342
this.node.onRemoved = function () {
13411343
// @ts-expect-error fixme ts strict error
13421344
onRemoved?.apply(this, arguments)
1345+
// api.removeEventListener('progress_state', progress_state)
13431346
api.removeEventListener('executing', executing)
13441347
api.removeEventListener('executed', executed)
13451348
}

src/locales/en/main.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@
133133
"copyURL": "Copy URL",
134134
"releaseTitle": "{package} {version} Release",
135135
"progressCountOf": "of",
136-
"keybindingAlreadyExists": "Keybinding already exists on"
136+
"keybindingAlreadyExists": "Keybinding already exists on",
137+
"nodesRunning": "nodes running"
137138
},
138139
"manager": {
139140
"title": "Custom Nodes Manager",

src/locales/es/main.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@
336336
"noTasksFoundMessage": "No hay tareas en la cola.",
337337
"noWorkflowsFound": "No se encontraron flujos de trabajo.",
338338
"nodes": "Nodos",
339+
"nodesRunning": "nodos en ejecución",
339340
"ok": "OK",
340341
"openNewIssue": "Abrir nuevo problema",
341342
"overwrite": "Sobrescribir",

src/locales/fr/main.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@
336336
"noTasksFoundMessage": "Il n'y a pas de tâches dans la file d'attente.",
337337
"noWorkflowsFound": "Aucun flux de travail trouvé.",
338338
"nodes": "Nœuds",
339+
"nodesRunning": "nœuds en cours d’exécution",
339340
"ok": "OK",
340341
"openNewIssue": "Ouvrir un nouveau problème",
341342
"overwrite": "Écraser",

0 commit comments

Comments
 (0)