diff --git a/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue b/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue index c9e8495549..036d575bf5 100644 --- a/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue +++ b/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue @@ -58,7 +58,7 @@ const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore()) const nodeDefStore = useNodeDefStore() const hasMissingNodes = computed(() => - graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName) + graphHasMissingNodes(app.rootGraph, nodeDefStore.nodeDefsByName) ) const { t } = useI18n() diff --git a/src/components/breadcrumb/SubgraphBreadcrumbItem.vue b/src/components/breadcrumb/SubgraphBreadcrumbItem.vue index 2181cce1b0..1c73e9a32b 100644 --- a/src/components/breadcrumb/SubgraphBreadcrumbItem.vue +++ b/src/components/breadcrumb/SubgraphBreadcrumbItem.vue @@ -83,7 +83,7 @@ const props = withDefaults(defineProps(), { const nodeDefStore = useNodeDefStore() const hasMissingNodes = computed(() => - graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName) + graphHasMissingNodes(app.rootGraph, nodeDefStore.nodeDefsByName) ) const { t } = useI18n() diff --git a/src/components/dialog/content/ErrorDialogContent.vue b/src/components/dialog/content/ErrorDialogContent.vue index c709540c58..56c7e2b43b 100644 --- a/src/components/dialog/content/ErrorDialogContent.vue +++ b/src/components/dialog/content/ErrorDialogContent.vue @@ -128,7 +128,7 @@ onMounted(async () => { reportContent.value = generateErrorReport({ systemStats: systemStatsStore.systemStats!, serverLogs: logs, - workflow: app.graph.serialize(), + workflow: app.rootGraph.serialize(), exceptionType: error.exceptionType, exceptionMessage: error.exceptionMessage, traceback: error.traceback, diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 35f2290340..56722d40d7 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -160,6 +160,7 @@ import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore' import { useWorkspaceStore } from '@/stores/workspaceStore' import { isNativeWindow } from '@/utils/envUtil' +import { forEachNode } from '@/utils/graphTraversalUtil' import TryVueNodeBanner from '../topbar/TryVueNodeBanner.vue' import SelectionRectangle from './SelectionRectangle.vue' @@ -271,20 +272,18 @@ watch( () => { if (!canvasStore.canvas) return - for (const n of comfyApp.graph.nodes) { - if (!n.widgets) continue + forEachNode(comfyApp.rootGraph, (n) => { + if (!n.widgets) return for (const w of n.widgets) { - if (w[IS_CONTROL_WIDGET]) { - updateControlWidgetLabel(w) - if (w.linkedWidgets) { - for (const l of w.linkedWidgets) { - updateControlWidgetLabel(l) - } - } + if (!w[IS_CONTROL_WIDGET]) continue + updateControlWidgetLabel(w) + if (!w.linkedWidgets) continue + for (const l of w.linkedWidgets) { + updateControlWidgetLabel(l) } } - } - comfyApp.graph.setDirtyCanvas(true) + }) + canvasStore.canvas.setDirty(true) } ) @@ -334,7 +333,7 @@ watch( } // Force canvas redraw to ensure progress updates are visible - canvas.graph.setDirtyCanvas(true, false) + canvas.setDirty(true, false) }, { deep: true } ) @@ -346,7 +345,7 @@ watch( (lastNodeErrors) => { if (!comfyApp.graph) return - for (const node of comfyApp.graph.nodes) { + forEachNode(comfyApp.rootGraph, (node) => { // Clear existing errors for (const slot of node.inputs) { delete slot.hasErrors @@ -356,7 +355,7 @@ watch( } const nodeErrors = lastNodeErrors?.[node.id] - if (!nodeErrors) continue + if (!nodeErrors) return const validErrors = nodeErrors.errors.filter( (error) => error.extra_info?.input_name !== undefined @@ -369,9 +368,9 @@ watch( node.inputs[inputIndex].hasErrors = true } }) - } + }) - comfyApp.canvas.draw(true, true) + comfyApp.canvas.setDirty(true, true) } ) diff --git a/src/components/graph/TitleEditor.vue b/src/components/graph/TitleEditor.vue index cf95c0ab5b..bcfef87578 100644 --- a/src/components/graph/TitleEditor.vue +++ b/src/components/graph/TitleEditor.vue @@ -58,7 +58,7 @@ const onEdit = (newValue: string) => { target.subgraph.name = trimmedTitle } - app.graph.setDirtyCanvas(true, true) + app.canvas.setDirty(true, true) } showInput.value = false titleEditorStore.titleEditorTarget = null diff --git a/src/components/toast/RerouteMigrationToast.vue b/src/components/toast/RerouteMigrationToast.vue index c084926d15..8c6fd4edb0 100644 --- a/src/components/toast/RerouteMigrationToast.vue +++ b/src/components/toast/RerouteMigrationToast.vue @@ -33,7 +33,7 @@ const toast = useToast() const workflowStore = useWorkflowStore() const migrateToLitegraphReroute = async () => { - const workflowJSON = app.graph.serialize() as unknown as WorkflowJSON04 + const workflowJSON = app.rootGraph.serialize() as unknown as WorkflowJSON04 const migratedWorkflowJSON = migrateLegacyRerouteNodes(workflowJSON) await app.loadGraphData( migratedWorkflowJSON, diff --git a/src/composables/maskeditor/useMaskEditorSaver.ts b/src/composables/maskeditor/useMaskEditorSaver.ts index 4af771399e..8aa6bb0da8 100644 --- a/src/composables/maskeditor/useMaskEditorSaver.ts +++ b/src/composables/maskeditor/useMaskEditorSaver.ts @@ -51,7 +51,7 @@ export function useMaskEditorSaver() { updateNodeWithServerReferences(sourceNode, outputData) - app.graph.setDirtyCanvas(true) + app.canvas.setDirty(true) } catch (error) { console.error('[MaskEditorSaver] Save failed:', error) throw error @@ -308,7 +308,7 @@ export function useMaskEditorSaver() { const mainImg = await loadImageFromUrl(dataUrl) node.imgs = [mainImg] - app.graph.setDirtyCanvas(true) + app.canvas.setDirty(true) } function updateNodeWithServerReferences( diff --git a/src/composables/node/useNodeBadge.ts b/src/composables/node/useNodeBadge.ts index 30c4487a38..2f31ad0ac5 100644 --- a/src/composables/node/useNodeBadge.ts +++ b/src/composables/node/useNodeBadge.ts @@ -55,7 +55,7 @@ export const useNodeBadge = () => { showApiPricingBadge ], () => { - app.graph?.setDirtyCanvas(true, true) + app.canvas?.setDirty(true, true) } ) diff --git a/src/composables/useCanvasDrop.ts b/src/composables/useCanvasDrop.ts index d3870b04b7..51379e98b4 100644 --- a/src/composables/useCanvasDrop.ts +++ b/src/composables/useCanvasDrop.ts @@ -41,7 +41,7 @@ export const useCanvasDrop = (canvasRef: Ref) => { } else if (node.data instanceof ComfyModelDef) { const model = node.data const pos = basePos - const nodeAtPos = comfyApp.graph.getNodeOnPos(pos[0], pos[1]) + const nodeAtPos = comfyApp.canvas.graph?.getNodeOnPos(pos[0], pos[1]) let targetProvider: ModelNodeProvider | null = null let targetGraphNode: LGraphNode | null = null if (nodeAtPos) { diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 1cbfec9091..63c141f762 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -107,7 +107,7 @@ export function useCoreCommands(): ComfyCommand[] { menubarLabel: 'New', category: 'essentials' as const, function: async () => { - const previousWorkflowHadNodes = app.graph._nodes.length > 0 + const previousWorkflowHadNodes = app.rootGraph._nodes.length > 0 await workflowService.loadBlankWorkflow() telemetry?.trackWorkflowCreated({ workflow_type: 'blank', @@ -130,7 +130,7 @@ export function useCoreCommands(): ComfyCommand[] { icon: 'pi pi-code', label: 'Load Default Workflow', function: async () => { - const previousWorkflowHadNodes = app.graph._nodes.length > 0 + const previousWorkflowHadNodes = app.rootGraph._nodes.length > 0 await workflowService.loadDefaultWorkflow() telemetry?.trackWorkflowCreated({ workflow_type: 'default', @@ -746,7 +746,7 @@ export function useCoreCommands(): ComfyCommand[] { 'Comfy.GroupSelectedNodes.Padding' ) group.resizeTo(group.children, padding) - app.graph.change() + app.canvas.setDirty(false, true) } } } diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index 5b4146c2d8..cbc8d25654 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -47,8 +47,8 @@ const Workflow = { const id = `${PREFIX}${SEPARATOR}${name}` // Check if lready registered/in use in this workflow // @ts-expect-error fixme ts strict error - if (app.graph.extra?.groupNodes?.[name]) { - if (app.graph.nodes.find((n) => n.type === id)) { + if (app.rootGraph.extra?.groupNodes?.[name]) { + if (app.rootGraph.nodes.find((n) => n.type === id)) { return Workflow.InUse.InWorkflow } else { return Workflow.InUse.Registered @@ -57,8 +57,8 @@ const Workflow = { return Workflow.InUse.Free }, storeGroupNode(name: string, data: GroupNodeWorkflowData) { - let extra = app.graph.extra - if (!extra) app.graph.extra = extra = {} + let extra = app.rootGraph.extra + if (!extra) app.rootGraph.extra = extra = {} let groupNodes = extra.groupNodes if (!groupNodes) extra.groupNodes = groupNodes = {} // @ts-expect-error fixme ts strict error @@ -118,7 +118,7 @@ class GroupNodeBuilder { sortNodes() { // Gets the builders nodes in graph execution order - const nodesInOrder = app.graph.computeExecutionOrder(false) + const nodesInOrder = app.rootGraph.computeExecutionOrder(false) this.nodes = this.nodes .map((node) => ({ index: nodesInOrder.indexOf(node), node })) // @ts-expect-error id might be string @@ -131,7 +131,7 @@ class GroupNodeBuilder { const storeLinkTypes = (config) => { // Store link types for dynamically typed nodes e.g. reroutes for (const link of config.links) { - const origin = app.graph.getNodeById(link[4]) + const origin = app.rootGraph.getNodeById(link[4]) // @ts-expect-error fixme ts strict error const type = origin.outputs[link[1]].type link.push(type) @@ -151,7 +151,7 @@ class GroupNodeBuilder { let type = output.type if (!output.links?.length) continue for (const l of output.links) { - const link = app.graph.links[l] + const link = app.rootGraph.links[l] if (!link) continue if (type === '*') type = link.type @@ -853,7 +853,7 @@ export class GroupNodeHandler { // The inner node is connected via the group node inputs const linkId = this.node.inputs[externalSlot].link // @ts-expect-error fixme ts strict error - let link = app.graph.links[linkId] + let link = app.rootGraph.links[linkId] // Use the outer link, but update the target to the inner node link = { @@ -980,7 +980,7 @@ export class GroupNodeHandler { // @ts-expect-error fixme ts strict error groupNode[GROUP].populateWidgets() // @ts-expect-error fixme ts strict error - app.graph.add(groupNode) + app.rootGraph.add(groupNode) // @ts-expect-error fixme ts strict error groupNode.setSize([ // @ts-expect-error fixme ts strict error @@ -1032,7 +1032,7 @@ export class GroupNodeHandler { const newNodes = [] for (let i = 0; i < selectedIds.length; i++) { const id = selectedIds[i] - const newNode = app.graph.getNodeById(id) + const newNode = app.rootGraph.getNodeById(id) const innerNode = innerNodes[i] newNodes.push(newNode) @@ -1111,17 +1111,17 @@ export class GroupNodeHandler { const reconnectInputs = (selectedIds) => { for (const innerNodeIndex in this.groupData.oldToNewInputMap) { const id = selectedIds[innerNodeIndex] - const newNode = app.graph.getNodeById(id) + const newNode = app.rootGraph.getNodeById(id) const map = this.groupData.oldToNewInputMap[innerNodeIndex] for (const innerInputId in map) { const groupSlotId = map[innerInputId] if (groupSlotId == null) continue const slot = node.inputs[groupSlotId] if (slot.link == null) continue - const link = app.graph.links[slot.link] + const link = app.rootGraph.links[slot.link] if (!link) continue // connect this node output to the input of another node - const originNode = app.graph.getNodeById(link.origin_id) + const originNode = app.rootGraph.getNodeById(link.origin_id) // @ts-expect-error fixme ts strict error originNode.connect(link.origin_slot, newNode, +innerInputId) } @@ -1140,9 +1140,11 @@ export class GroupNodeHandler { const links = [...output.links] for (const l of links) { const slot = this.groupData.newToOldOutputMap[groupOutputId] - const link = app.graph.links[l] - const targetNode = app.graph.getNodeById(link.target_id) - const newNode = app.graph.getNodeById(selectedIds[slot.node.index]) + const link = app.rootGraph.links[l] + const targetNode = app.rootGraph.getNodeById(link.target_id) + const newNode = app.rootGraph.getNodeById( + selectedIds[slot.node.index] + ) // @ts-expect-error fixme ts strict error newNode.connect(slot.slot, targetNode, link.target_slot) } @@ -1155,7 +1157,7 @@ export class GroupNodeHandler { const { newNodes, selectedIds } = addInnerNodes() reconnectInputs(selectedIds) reconnectOutputs(selectedIds) - app.graph.remove(this.node) + app.rootGraph.remove(this.node) return newNodes } finally { @@ -1291,7 +1293,7 @@ export class GroupNodeHandler { const handler = ({ detail }) => { const id = getId(detail) if (!id) return - const node = app.graph.getNodeById(id) + const node = app.rootGraph.getNodeById(id) if (node) return // @ts-expect-error fixme ts strict error @@ -1546,7 +1548,7 @@ export class GroupNodeHandler { } this.linkOutputs(node, i) - app.graph.remove(node) + app.rootGraph.remove(node) // Set internal ID to what is expected after workflow is reloaded node.id = `${this.node.id}:${i}` @@ -1565,10 +1567,10 @@ export class GroupNodeHandler { // Clone the links as they'll be changed if we reconnect const links = [...output.links] for (const l of links) { - const link = app.graph.links[l] + const link = app.rootGraph.links[l] if (!link) continue - const targetNode = app.graph.getNodeById(link.target_id) + const targetNode = app.rootGraph.getNodeById(link.target_id) const newSlot = this.groupData.oldToNewOutputMap[nodeId]?.[link.origin_slot] if (newSlot != null) { @@ -1582,7 +1584,7 @@ export class GroupNodeHandler { linkInputs() { for (const link of this.groupData.nodeData.links ?? []) { const [, originSlot, targetId, targetSlot, actualOriginId] = link - const originNode = app.graph.getNodeById(actualOriginId) + const originNode = app.rootGraph.getNodeById(actualOriginId) if (!originNode) continue // this node is in the group originNode.connect( originSlot, @@ -1621,7 +1623,7 @@ export class GroupNodeHandler { // @ts-expect-error fixme ts strict error groupNode[GROUP].populateWidgets() // @ts-expect-error fixme ts strict error - app.graph.add(groupNode) + app.rootGraph.add(groupNode) // Remove all converted nodes and relink them // @ts-expect-error fixme ts strict error @@ -1802,7 +1804,7 @@ const ext: ComfyExtension = { // Re-register group nodes so new ones are created with the correct options // @ts-expect-error fixme ts strict error Object.assign(globalDefs, defs) - const nodes = app.graph.extra?.groupNodes + const nodes = app.rootGraph.extra?.groupNodes if (nodes) { await GroupNodeConfig.registerFromWorkflow(nodes, {}) } diff --git a/src/extensions/core/groupNodeManage.ts b/src/extensions/core/groupNodeManage.ts index 8e52ffccc3..7ea480614b 100644 --- a/src/extensions/core/groupNodeManage.ts +++ b/src/extensions/core/groupNodeManage.ts @@ -270,7 +270,7 @@ export class ManageGroupDialog extends ComfyDialog { this.groupData.oldToNewWidgetMap[this.selectedNodeInnerIndex] const items = Object.keys(widgets ?? {}) // @ts-expect-error fixme ts strict error - const type = app.graph.extra.groupNodes[this.selectedGroup] + const type = app.rootGraph.extra.groupNodes[this.selectedGroup] const config = type.config?.[this.selectedNodeInnerIndex]?.input this.widgetsPage.replaceChildren( ...items.map((oldName) => { @@ -290,7 +290,7 @@ export class ManageGroupDialog extends ComfyDialog { const inputs = this.groupData.nodeInputs[this.selectedNodeInnerIndex] const items = Object.keys(inputs ?? {}) // @ts-expect-error fixme ts strict error - const type = app.graph.extra.groupNodes[this.selectedGroup] + const type = app.rootGraph.extra.groupNodes[this.selectedGroup] const config = type.config?.[this.selectedNodeInnerIndex]?.input this.inputsPage.replaceChildren( // @ts-expect-error fixme ts strict error @@ -324,7 +324,7 @@ export class ManageGroupDialog extends ComfyDialog { this.groupData.oldToNewOutputMap[this.selectedNodeInnerIndex] // @ts-expect-error fixme ts strict error - const type = app.graph.extra.groupNodes[this.selectedGroup] + const type = app.rootGraph.extra.groupNodes[this.selectedGroup] const config = type.config?.[this.selectedNodeInnerIndex]?.output const node = this.groupData.nodeData.nodes[this.selectedNodeInnerIndex] const checkable = node.type !== 'PrimitiveNode' @@ -355,7 +355,7 @@ export class ManageGroupDialog extends ComfyDialog { // @ts-expect-error fixme ts strict error show(type?) { - const groupNodes = Object.keys(app.graph.extra?.groupNodes ?? {}).sort( + const groupNodes = Object.keys(app.rootGraph.extra?.groupNodes ?? {}).sort( (a, b) => a.localeCompare(b) ) @@ -425,7 +425,7 @@ export class ManageGroupDialog extends ComfyDialog { 'button.comfy-btn', { onclick: () => { - const node = app.graph.nodes.find( + const node = app.rootGraph.nodes.find( (n) => n.type === `${PREFIX}${SEPARATOR}` + this.selectedGroup ) if (node) { @@ -440,7 +440,7 @@ export class ManageGroupDialog extends ComfyDialog { ) ) { // @ts-expect-error fixme ts strict error - delete app.graph.extra.groupNodes[this.selectedGroup] + delete app.rootGraph.extra.groupNodes[this.selectedGroup] LiteGraph.unregisterNodeType( `${PREFIX}${SEPARATOR}` + this.selectedGroup ) @@ -459,7 +459,7 @@ export class ManageGroupDialog extends ComfyDialog { const types = {} for (const g in this.modifications) { // @ts-expect-error fixme ts strict error - const type = app.graph.extra.groupNodes[g] + const type = app.rootGraph.extra.groupNodes[g] let config = (type.config ??= {}) let nodeMods = this.modifications[g]?.nodes @@ -515,7 +515,7 @@ export class ManageGroupDialog extends ComfyDialog { types[g] = type if (!nodesByType) { - nodesByType = app.graph.nodes.reduce((p, n) => { + nodesByType = app.rootGraph.nodes.reduce((p, n) => { // @ts-expect-error fixme ts strict error p[n.type] ??= [] // @ts-expect-error fixme ts strict error @@ -536,7 +536,7 @@ export class ManageGroupDialog extends ComfyDialog { } this.modifications = {} - this.app.graph.setDirtyCanvas(true, true) + this.app.canvas.setDirty(true, true) this.changeGroup(this.selectedGroup, false) } }, diff --git a/src/extensions/core/nodeTemplates.ts b/src/extensions/core/nodeTemplates.ts index c16ebed72d..457f54c482 100644 --- a/src/extensions/core/nodeTemplates.ts +++ b/src/extensions/core/nodeTemplates.ts @@ -367,7 +367,7 @@ const ext: ComfyExtension = { data = JSON.parse(data || '{}') const nodeIds = Object.keys(app.canvas.selected_nodes) for (let i = 0; i < nodeIds.length; i++) { - const node = app.graph.getNodeById(nodeIds[i]) + const node = app.canvas.graph?.getNodeById(nodeIds[i]) const nodeData = node?.constructor.nodeData let groupData = GroupNodeHandler.getGroupData(node) diff --git a/src/extensions/core/uploadAudio.ts b/src/extensions/core/uploadAudio.ts index dfefc88a0f..71c2295d10 100644 --- a/src/extensions/core/uploadAudio.ts +++ b/src/extensions/core/uploadAudio.ts @@ -142,7 +142,7 @@ app.registerExtension({ onNodeOutputsUpdated(nodeOutputs: Record) { for (const [nodeLocatorId, output] of Object.entries(nodeOutputs)) { if ('audio' in output) { - const node = getNodeByLocatorId(app.graph, nodeLocatorId) + const node = getNodeByLocatorId(app.rootGraph, nodeLocatorId) if (!node) continue // @ts-expect-error fixme ts strict error diff --git a/src/extensions/core/webcamCapture.ts b/src/extensions/core/webcamCapture.ts index f429ddda49..6c02229c5f 100644 --- a/src/extensions/core/webcamCapture.ts +++ b/src/extensions/core/webcamCapture.ts @@ -97,7 +97,7 @@ app.registerExtension({ const img = new Image() img.onload = () => { node.imgs = [img] - app.graph.setDirtyCanvas(true) + app.canvas.setDirty(true) } img.src = data } diff --git a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts index d683288c41..86b3a5553f 100644 --- a/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts +++ b/src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.ts @@ -433,7 +433,7 @@ export class MixpanelTelemetryProvider implements TelemetryProvider { } const nodeCounts = reduceAllNodes( - app.graph, + app.rootGraph, (metrics, node) => { const nodeDef = nodeDefStore.nodeDefsByName[node.type] const isCustomNode = diff --git a/src/platform/workflow/management/stores/workflowStore.ts b/src/platform/workflow/management/stores/workflowStore.ts index e821d9c75e..4b8b64f3db 100644 --- a/src/platform/workflow/management/stores/workflowStore.ts +++ b/src/platform/workflow/management/stores/workflowStore.ts @@ -633,6 +633,7 @@ export const useWorkflowStore = defineStore('workflow', () => { return getSubgraphsFromInstanceIds(subgraph, subgraphNodeIds, subgraphs) } + //FIXME: use existing util function const executionIdToCurrentId = (id: string) => { const subgraph = activeSubgraph.value @@ -647,7 +648,7 @@ export const useWorkflowStore = defineStore('workflow', () => { const subgraphNodeIds = id.split(':') // Start from the root graph - const { graph } = comfyApp + const graph = comfyApp.rootGraph // If the last subgraph is the active subgraph, return the node ID const subgraphs = getSubgraphsFromInstanceIds(graph, subgraphNodeIds) @@ -714,7 +715,7 @@ export const useWorkflowStore = defineStore('workflow', () => { try { const subgraphs = getSubgraphsFromInstanceIds( - comfyApp.graph, + comfyApp.rootGraph, subgraphNodeIds.map((id) => String(id)) ) const immediateSubgraph = subgraphs[subgraphs.length - 1] @@ -779,7 +780,7 @@ export const useWorkflowStore = defineStore('workflow', () => { return null } - const path = findSubgraphPath(comfyApp.graph, subgraphUuid) + const path = findSubgraphPath(comfyApp.rootGraph, subgraphUuid) if (!path) return null // If we have a target subgraph, check if the path goes through it @@ -787,7 +788,7 @@ export const useWorkflowStore = defineStore('workflow', () => { targetSubgraph && !path.some((_, idx) => { const subgraphs = getSubgraphsFromInstanceIds( - comfyApp.graph, + comfyApp.rootGraph, path.slice(0, idx + 1).map((id) => String(id)) ) return subgraphs[subgraphs.length - 1] === targetSubgraph diff --git a/src/platform/workflow/persistence/composables/useWorkflowPersistence.ts b/src/platform/workflow/persistence/composables/useWorkflowPersistence.ts index a297b2a79f..09ffd58950 100644 --- a/src/platform/workflow/persistence/composables/useWorkflowPersistence.ts +++ b/src/platform/workflow/persistence/composables/useWorkflowPersistence.ts @@ -44,7 +44,7 @@ export function useWorkflowPersistence() { const persistCurrentWorkflow = () => { if (!workflowPersistenceEnabled.value) return - const workflow = JSON.stringify(comfyApp.graph.serialize()) + const workflow = JSON.stringify(comfyApp.rootGraph.serialize()) try { localStorage.setItem('workflow', workflow) diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index 109d0c5629..45e4de0031 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -396,7 +396,7 @@ const handleEnterSubgraph = () => { useTelemetry()?.trackUiButtonClicked({ button_id: 'graph_node_open_subgraph_clicked' }) - const graph = app.graph?.rootGraph || app.graph + const graph = app.rootGraph if (!graph) { console.warn('LGraphNode: No graph available for subgraph navigation') return @@ -428,9 +428,7 @@ const nodeOutputLocatorId = computed(() => const lgraphNode = computed(() => { const locatorId = getLocatorIdFromNodeData(nodeData) - const rootGraph = app.graph?.rootGraph || app.graph - if (!rootGraph) return null - return getNodeByLocatorId(rootGraph, locatorId) + return getNodeByLocatorId(app.rootGraph, locatorId) }) const nodeMedia = computed(() => { diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.vue b/src/renderer/extensions/vueNodes/components/NodeHeader.vue index debb358d62..9802f1f9be 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.vue +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.vue @@ -209,7 +209,7 @@ const isSubgraphNode = computed(() => { if (!nodeData?.id) return false // Get the underlying LiteGraph node - const graph = app.graph?.rootGraph || app.graph + const graph = app.rootGraph if (!graph) return false const locatorId = getLocatorIdFromNodeData(nodeData) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetAudioUI.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetAudioUI.vue index 2c54ed68ab..7b46670d21 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetAudioUI.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetAudioUI.vue @@ -39,8 +39,8 @@ defineEmits<{ // Get litegraph node const litegraphNode = computed(() => { - if (!props.nodeId || !app.rootGraph) return null - return app.rootGraph.getNodeById(props.nodeId) as LGraphNode | null + if (!props.nodeId || !app.canvas.graph) return null + return app.canvas.graph.getNodeById(props.nodeId) as LGraphNode | null }) // Check if this is an output node (PreviewAudio, SaveAudio, etc) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetRecordAudio.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetRecordAudio.vue index c3efc30423..e3b91d69cf 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetRecordAudio.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetRecordAudio.vue @@ -157,8 +157,8 @@ const isWaveformActive = computed(() => isRecording.value || isPlaying.value) const modelValue = defineModel({ default: '' }) const litegraphNode = computed(() => { - if (!props.nodeId || !app.rootGraph) return null - return app.rootGraph.getNodeById(props.nodeId) as LGraphNode | null + if (!props.nodeId || !app.canvas.graph) return null + return app.canvas.graph.getNodeById(props.nodeId) as LGraphNode | null }) async function handleRecordingComplete(blob: Blob) { diff --git a/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.vue b/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.vue index 16044b27c9..eee5dc96a1 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/audio/AudioPreviewPlayer.vue @@ -189,8 +189,8 @@ const showVolumeTwo = computed(() => !isMuted.value && volume.value > 0.5) const showVolumeOne = computed(() => isMuted.value && volume.value > 0) const litegraphNode = computed(() => { - if (!props.nodeId || !app.rootGraph) return null - return app.rootGraph.getNodeById(props.nodeId) as LGraphNode | null + if (!props.nodeId || !app.canvas.graph) return null + return app.canvas.graph.getNodeById(props.nodeId) as LGraphNode | null }) const hidden = computed(() => { diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 9c152d4bea..8a00050b77 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -64,7 +64,7 @@ import type { ComfyExtension, MissingNodeType } from '@/types/comfy' import { type ExtensionManager } from '@/types/extensionTypes' import type { NodeExecutionId } from '@/types/nodeIdentification' import { graphToPrompt } from '@/utils/executionUtil' -import { forEachNode } from '@/utils/graphTraversalUtil' +import { collectAllNodes, forEachNode } from '@/utils/graphTraversalUtil' import { getNodeByExecutionId, triggerCallbackOnAllNodes @@ -157,15 +157,15 @@ export class ComfyApp { // TODO: Migrate internal usage to the /** @deprecated Use {@link rootGraph} instead */ - get graph() { + get graph(): unknown { return this.rootGraphInternal! } - get rootGraph(): LGraph | undefined { + get rootGraph(): LGraph { if (!this.rootGraphInternal) { console.error('ComfyApp graph accessed before initialization') } - return this.rootGraphInternal + return this.rootGraphInternal! } // @ts-expect-error fixme ts strict error @@ -512,7 +512,7 @@ export class ComfyApp { } } - app.graph.setDirtyCanvas(true) + app.canvas.setDirty(true) useNodeOutputStore().updateNodeImages(node) } @@ -553,7 +553,7 @@ export class ComfyApp { useEventListener(this.canvasElRef, 'dragleave', async () => { if (!this.dragOverNode) return this.dragOverNode = null - this.graph.setDirtyCanvas(false, true) + this.canvas.setDirty(false, true) }) // Add handler for dropping onto a specific node @@ -562,7 +562,10 @@ export class ComfyApp { 'dragover', (event: DragEvent) => { this.canvas.adjustMouseEvent(event) - const node = this.graph.getNodeOnPos(event.canvasX, event.canvasY) + const node = this.canvas.graph?.getNodeOnPos( + event.canvasX, + event.canvasY + ) if (!node?.onDragOver?.(event)) { this.dragOverNode = null @@ -573,7 +576,7 @@ export class ComfyApp { // dragover event is fired very frequently, run this on an animation frame requestAnimationFrame(() => { - this.graph.setDirtyCanvas(false, true) + this.canvas.setDirty(false, true) }) }, false @@ -638,11 +641,11 @@ export class ComfyApp { }) api.addEventListener('progress', () => { - this.graph.setDirtyCanvas(true, false) + this.canvas.setDirty(true, false) }) api.addEventListener('executing', () => { - this.graph.setDirtyCanvas(true, false) + this.canvas.setDirty(true, false) }) api.addEventListener('executed', ({ detail }) => { @@ -653,14 +656,14 @@ export class ComfyApp { merge: detail.merge }) - const node = getNodeByExecutionId(this.graph, executionId) + const node = getNodeByExecutionId(this.rootGraph, executionId) if (node && node.onExecuted) { node.onExecuted(detail.output) } }) api.addEventListener('execution_start', () => { - triggerCallbackOnAllNodes(this.graph, 'onExecutionStart') + triggerCallbackOnAllNodes(this.rootGraph, 'onExecutionStart') }) api.addEventListener('execution_error', ({ detail }) => { @@ -844,7 +847,7 @@ export class ComfyApp { registerProxyWidgets(this.canvas) - this.graph.start() + this.rootGraph.start() // Ensure the canvas fills the window useResizeObserver(this.canvasElRef, ([canvasEl]) => { @@ -1194,17 +1197,18 @@ export class ComfyApp { try { // @ts-expect-error Discrepancies between zod and litegraph - in progress - this.graph.configure(graphData) + this.rootGraph.configure(graphData) // Save original renderer version before scaling (it gets modified during scaling) - const originalMainGraphRenderer = this.graph.extra.workflowRendererVersion + const originalMainGraphRenderer = + this.rootGraph.extra.workflowRendererVersion // Scale main graph ensureCorrectLayoutScale(originalMainGraphRenderer) // Scale all subgraphs that were loaded with the workflow // Use original main graph renderer as fallback (not the modified one) - for (const subgraph of this.graph.subgraphs.values()) { + for (const subgraph of this.rootGraph.subgraphs.values()) { ensureCorrectLayoutScale( subgraph.extra.workflowRendererVersion || originalMainGraphRenderer, subgraph @@ -1235,7 +1239,7 @@ export class ComfyApp { console.error(error) return } - for (const node of this.graph.nodes) { + forEachNode(this.rootGraph, (node) => { const size = node.computeSize() size[0] = Math.max(node.size[0], size[0]) size[1] = Math.max(node.size[1], size[1]) @@ -1284,7 +1288,7 @@ export class ComfyApp { } useExtensionService().invokeExtensions('loadedGraphNode', node) - } + }) if (missingNodeTypes.length && showMissingNodesDialog) { this.showMissingNodesError(missingNodeTypes) @@ -1309,14 +1313,14 @@ export class ComfyApp { useTelemetry()?.trackWorkflowImported(telemetryPayload) await useWorkflowService().afterLoadNewGraph( workflow, - this.graph.serialize() as unknown as ComfyWorkflowJSON + this.rootGraph.serialize() as unknown as ComfyWorkflowJSON ) requestAnimationFrame(() => { - this.graph.setDirtyCanvas(true, true) + this.canvas.setDirty(true, true) }) } - async graphToPrompt(graph = this.graph) { + async graphToPrompt(graph = this.rootGraph) { return graphToPrompt(graph, { sortNodes: useSettingStore().get('Comfy.Workflow.SortNodeIdOnSave') }) @@ -1348,12 +1352,12 @@ export class ComfyApp { 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') - for (const subgraph of this.graph.subgraphs.values()) { - executeWidgetsCallback(subgraph.nodes, 'beforeQueued') - } + forEachNode(this.rootGraph, (node) => { + for (const widget of node.widgets ?? []) widget.beforeQueued?.() + }) - const p = await this.graphToPrompt(this.graph) + const p = await this.graphToPrompt(this.rootGraph) + const queuedNodes = collectAllNodes(this.rootGraph) try { api.authToken = comfyOrgAuthToken api.apiKey = comfyOrgApiKey ?? undefined @@ -1393,16 +1397,7 @@ export class ComfyApp { // Allow widgets to run callbacks after a prompt has been queued // e.g. random seed after every gen - executeWidgetsCallback( - p.workflow.nodes - .map((n) => this.graph.getNodeById(n.id)) - .filter((n) => !!n), - 'afterQueued' - ) - for (const subgraph of this.graph.subgraphs.values()) { - executeWidgetsCallback(subgraph.nodes, 'afterQueued') - } - + executeWidgetsCallback(queuedNodes, 'afterQueued') this.canvas.draw(true, true) await this.ui.queue.update() } @@ -1477,7 +1472,7 @@ export class ComfyApp { importA1111(this.graph, parameters) useWorkflowService().afterLoadNewGraph( fileName, - this.graph.serialize() as unknown as ComfyWorkflowJSON + this.rootGraph.serialize() as unknown as ComfyWorkflowJSON ) return } @@ -1508,24 +1503,25 @@ export class ComfyApp { } const ids = Object.keys(apiData) - app.graph.clear() + app.rootGraph.clear() for (const id of ids) { const data = apiData[id] const node = LiteGraph.createNode(data.class_type) if (!node) continue node.id = isNaN(+id) ? id : +id node.title = data._meta?.title ?? node.title - app.graph.add(node) + app.rootGraph.add(node) } + //TODO: Investigate repeat of for loop. Can compress? for (const id of ids) { const data = apiData[id] - const node = app.graph.getNodeById(id) + const node = app.rootGraph.getNodeById(id) for (const input in data.inputs ?? {}) { const value = data.inputs[input] if (value instanceof Array) { const [fromId, fromSlot] = value - const fromNode = app.graph.getNodeById(fromId) + const fromNode = app.rootGraph.getNodeById(fromId) // @ts-expect-error fixme ts strict error let toSlot = node.inputs?.findIndex((inp) => inp.name === input) if (toSlot == null || toSlot === -1) { @@ -1554,16 +1550,16 @@ export class ComfyApp { } } } - app.graph.arrange() + app.rootGraph.arrange() for (const id of ids) { const data = apiData[id] - const node = app.graph.getNodeById(id) + const node = app.rootGraph.getNodeById(id) for (const input in data.inputs ?? {}) { const value = data.inputs[input] if (value instanceof Array) { const [fromId, fromSlot] = value - const fromNode = app.graph.getNodeById(fromId) + const fromNode = app.rootGraph.getNodeById(fromId) // @ts-expect-error fixme ts strict error let toSlot = node.inputs?.findIndex((inp) => inp.name === input) if (toSlot == null || toSlot === -1) { @@ -1593,11 +1589,11 @@ export class ComfyApp { } } - app.graph.arrange() + app.rootGraph.arrange() useWorkflowService().afterLoadNewGraph( fileName, - this.graph.serialize() as unknown as ComfyWorkflowJSON + this.rootGraph.serialize() as unknown as ComfyWorkflowJSON ) } @@ -1649,7 +1645,7 @@ export class ComfyApp { this.registerNodeDef(nodeId, defs[nodeId]) } // Refresh combo widgets in all nodes including those in subgraphs - forEachNode(this.graph, (node) => { + forEachNode(this.rootGraph, (node) => { const def = defs[node.type] // Allow primitive nodes to handle refresh node.refreshComboInNode?.(defs) @@ -1714,8 +1710,8 @@ export class ComfyApp { // Subgraph does not properly implement `clear` and the parent class's // (`LGraph`) `clear` breaks the subgraph structure. - if (this.graph && !this.canvas.subgraph) { - this.graph.clear() + if (this.rootGraph && !this.canvas.subgraph) { + this.rootGraph.clear() } } diff --git a/src/scripts/changeTracker.ts b/src/scripts/changeTracker.ts index f2b2afc30b..fa09ad49af 100644 --- a/src/scripts/changeTracker.ts +++ b/src/scripts/changeTracker.ts @@ -96,13 +96,13 @@ export class ChangeTracker { const activeId = navigation.at(-1) if (activeId) { // Navigate to the saved subgraph - const subgraph = app.graph.subgraphs.get(activeId) + const subgraph = app.rootGraph.subgraphs.get(activeId) if (subgraph) { app.canvas.setGraph(subgraph) } } else { // Empty navigation array means root level - app.canvas.setGraph(app.graph) + app.canvas.setGraph(app.rootGraph) } } } @@ -130,7 +130,7 @@ export class ChangeTracker { checkState() { if (!app.graph || this.changeCount) return - const currentState = clone(app.graph.serialize()) as ComfyWorkflowJSON + const currentState = clone(app.rootGraph.serialize()) as ComfyWorkflowJSON if (!this.activeState) { this.activeState = currentState return diff --git a/src/scripts/utils.ts b/src/scripts/utils.ts index 26327f6cbb..24cd535568 100644 --- a/src/scripts/utils.ts +++ b/src/scripts/utils.ts @@ -22,7 +22,7 @@ export function clone(obj: T): T { * There are external callers to this function, so we need to keep it for now */ export function applyTextReplacements(app: ComfyApp, value: string): string { - return _applyTextReplacements(app.graph, value) + return _applyTextReplacements(app.rootGraph, value) } /** @knipIgnoreUnusedButUsedByCustomNodes */ diff --git a/src/services/litegraphService.ts b/src/services/litegraphService.ts index 7cc451c032..d129c0c9d7 100644 --- a/src/services/litegraphService.ts +++ b/src/services/litegraphService.ts @@ -264,7 +264,7 @@ export const useLitegraphService = () => { _initialMinSize = { width: 1, height: 1 } constructor() { - super(app.graph, subgraph, instanceData) + super(app.rootGraph, subgraph, instanceData) // Set up event listener for promoted widget registration subgraph.events.addEventListener('widget-promoted', (event) => { @@ -862,7 +862,7 @@ export const useLitegraphService = () => { } function goToNode(nodeId: NodeId) { - const graphNode = app.graph.getNodeById(nodeId) + const graphNode = app.canvas.graph?.getNodeById(nodeId) if (!graphNode) return app.canvas.animateToBounds(graphNode.boundingRect) } @@ -883,7 +883,9 @@ export const useLitegraphService = () => { const canvas = canvasStore.canvas if (!canvas) return - const bounds = createBounds(app.graph.nodes) + const nodes = canvas.graph?.nodes + if (!nodes) return + const bounds = createBounds(nodes) if (!bounds) return canvas.ds.fitToBounds(bounds) diff --git a/src/services/subgraphService.ts b/src/services/subgraphService.ts index b16bf9ca96..3a9f1f7c70 100644 --- a/src/services/subgraphService.ts +++ b/src/services/subgraphService.ts @@ -67,8 +67,8 @@ export const useSubgraphService = () => { // Assertion: overriding Zod schema for (const subgraphData of subgraphs as ExportedSubgraph[]) { const subgraph = - comfyApp.graph.subgraphs.get(subgraphData.id) ?? - comfyApp.graph.createSubgraph(subgraphData) + comfyApp.rootGraph.subgraphs.get(subgraphData.id) ?? + comfyApp.rootGraph.createSubgraph(subgraphData) registerNewSubgraph(subgraph, subgraphData) } diff --git a/src/stores/executionStore.ts b/src/stores/executionStore.ts index eabce26132..b9f89c29a1 100644 --- a/src/stores/executionStore.ts +++ b/src/stores/executionStore.ts @@ -97,7 +97,7 @@ function executionIdToNodeLocatorId( // It's an execution node ID const parts = nodeIdStr.split(':') const localNodeId = parts[parts.length - 1] - const subgraphs = getSubgraphsFromInstanceIds(app.graph, parts) + const subgraphs = getSubgraphsFromInstanceIds(app.rootGraph, parts) if (!subgraphs) return undefined const nodeLocatorId = createNodeLocatorId(subgraphs.at(-1)!.id, localNodeId) return nodeLocatorId @@ -578,10 +578,10 @@ export const useExecutionStore = defineStore('execution', () => { * Propagates errors up subgraph chains. */ watch(lastNodeErrors, () => { - if (!app.graph || !app.graph.nodes) return + if (!app.rootGraph) return // Clear all error flags - forEachNode(app.graph, (node) => { + forEachNode(app.rootGraph, (node) => { node.has_errors = false if (node.inputs) { for (const slot of node.inputs) { @@ -596,7 +596,7 @@ export const useExecutionStore = defineStore('execution', () => { for (const [executionId, nodeError] of Object.entries( lastNodeErrors.value )) { - const node = getNodeByExecutionId(app.graph, executionId) + const node = getNodeByExecutionId(app.rootGraph, executionId) if (!node) continue node.has_errors = true @@ -618,7 +618,10 @@ export const useExecutionStore = defineStore('execution', () => { const parts = executionId.split(':') for (let i = parts.length - 1; i > 0; i--) { const parentExecutionId = parts.slice(0, i).join(':') - const parentNode = getNodeByExecutionId(app.graph, parentExecutionId) + const parentNode = getNodeByExecutionId( + app.rootGraph, + parentExecutionId + ) if (parentNode) { parentNode.has_errors = true } diff --git a/src/stores/subgraphNavigationStore.ts b/src/stores/subgraphNavigationStore.ts index b986299517..8c29f2723f 100644 --- a/src/stores/subgraphNavigationStore.ts +++ b/src/stores/subgraphNavigationStore.ts @@ -49,7 +49,7 @@ export const useSubgraphNavigationStore = defineStore( */ const navigationStack = computed(() => idStack.value - .map((id) => app.graph.subgraphs.get(id)) + .map((id) => app.rootGraph.subgraphs.get(id)) .filter(isNonNullish) ) diff --git a/src/types/index.ts b/src/types/index.ts index 6947f433fc..25f8e20900 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,3 @@ -import type { LGraph } from '@/lib/litegraph/src/litegraph' import type { DeviceStats, EmbeddingsResponse, @@ -71,6 +70,6 @@ declare global { app?: ComfyApp /** For use by extensions and in the browser console. Where possible, import `app` and access via `app.graph` instead. */ - graph?: LGraph + graph?: unknown } } diff --git a/src/views/LinearView.vue b/src/views/LinearView.vue index 3c81f3b395..1a02d37dc1 100644 --- a/src/views/LinearView.vue +++ b/src/views/LinearView.vue @@ -55,7 +55,7 @@ const nodeDatas = computed(() => { widgets } } - return app.graph.nodes + return app.rootGraph.nodes .filter((node) => node.mode === 0 && node.widgets?.length) .map(nodeToNodeData) }) diff --git a/src/workbench/extensions/manager/composables/nodePack/useMissingNodes.ts b/src/workbench/extensions/manager/composables/nodePack/useMissingNodes.ts index 580a687fcd..96f1d4403d 100644 --- a/src/workbench/extensions/manager/composables/nodePack/useMissingNodes.ts +++ b/src/workbench/extensions/manager/composables/nodePack/useMissingNodes.ts @@ -61,7 +61,7 @@ export const useMissingNodes = createSharedComposable(() => { } const missingCoreNodes = computed>(() => { - const missingNodes = collectAllNodes(app.graph, isMissingCoreNode) + const missingNodes = collectAllNodes(app.rootGraph, isMissingCoreNode) return groupBy(missingNodes, (node) => String(node.properties?.ver || '')) }) diff --git a/src/workbench/extensions/manager/composables/nodePack/useWorkflowPacks.ts b/src/workbench/extensions/manager/composables/nodePack/useWorkflowPacks.ts index b83508dddb..e2580ee8de 100644 --- a/src/workbench/extensions/manager/composables/nodePack/useWorkflowPacks.ts +++ b/src/workbench/extensions/manager/composables/nodePack/useWorkflowPacks.ts @@ -7,7 +7,7 @@ import { useComfyRegistryStore } from '@/stores/comfyRegistryStore' import { useNodeDefStore } from '@/stores/nodeDefStore' import { useSystemStatsStore } from '@/stores/systemStatsStore' import type { components } from '@/types/comfyRegistryTypes' -import { collectAllNodes } from '@/utils/graphTraversalUtil' +import { mapAllNodes } from '@/utils/graphTraversalUtil' import { useNodePacks } from '@/workbench/extensions/manager/composables/nodePack/useNodePacks' import type { UseNodePacksOptions } from '@/workbench/extensions/manager/types/comfyManagerTypes' @@ -112,13 +112,9 @@ export const useWorkflowPacks = (options: UseNodePacksOptions = {}) => { * Get the node packs for all nodes in the workflow (including subgraphs). */ const getWorkflowPacks = async () => { - if (!app.graph) return [] - const allNodes = collectAllNodes(app.graph) - if (!allNodes.length) { - workflowPacks.value = [] - return [] - } - const packs = await Promise.all(allNodes.map(workflowNodeToPack)) + if (!app.rootGraph) return [] + const packPromises = mapAllNodes(app.rootGraph, workflowNodeToPack) + const packs = await Promise.all(packPromises) workflowPacks.value = packs.filter((pack) => pack !== undefined) } diff --git a/tests-ui/tests/composables/useCoreCommands.test.ts b/tests-ui/tests/composables/useCoreCommands.test.ts index 6ab5d58db7..3c86faa5a2 100644 --- a/tests-ui/tests/composables/useCoreCommands.test.ts +++ b/tests-ui/tests/composables/useCoreCommands.test.ts @@ -32,7 +32,7 @@ vi.mock('@/scripts/app', () => { } }), canvas: mockCanvas, - graph: { + rootGraph: { clear: mockGraphClear } } @@ -161,7 +161,7 @@ describe('useCoreCommands', () => { await clearCommand.function() expect(app.clean).toHaveBeenCalled() - expect(app.graph.clear).toHaveBeenCalled() + expect(app.rootGraph.clear).toHaveBeenCalled() expect(api.dispatchCustomEvent).toHaveBeenCalledWith('graphCleared') }) @@ -178,7 +178,7 @@ describe('useCoreCommands', () => { await clearCommand.function() expect(app.clean).toHaveBeenCalled() - expect(app.graph.clear).not.toHaveBeenCalled() + expect(app.rootGraph.clear).not.toHaveBeenCalled() // Should only remove user nodes, not input/output nodes expect(mockSubgraph.remove).toHaveBeenCalledTimes(2) @@ -212,7 +212,7 @@ describe('useCoreCommands', () => { // Should not clear anything when user cancels expect(app.clean).not.toHaveBeenCalled() - expect(app.graph.clear).not.toHaveBeenCalled() + expect(app.rootGraph.clear).not.toHaveBeenCalled() expect(api.dispatchCustomEvent).not.toHaveBeenCalled() }) }) diff --git a/tests-ui/tests/composables/useMissingNodes.test.ts b/tests-ui/tests/composables/useMissingNodes.test.ts index 41bd098bb0..04a4e5ffd0 100644 --- a/tests-ui/tests/composables/useMissingNodes.test.ts +++ b/tests-ui/tests/composables/useMissingNodes.test.ts @@ -1,8 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick, ref } from 'vue' -import type { LGraphNode } from '@/lib/litegraph/src/litegraph' -import { app } from '@/scripts/app' +import type { LGraphNode, LGraph } from '@/lib/litegraph/src/litegraph' import { useNodeDefStore } from '@/stores/nodeDefStore' import { collectAllNodes } from '@/utils/graphTraversalUtil' import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes' @@ -40,12 +39,10 @@ vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ })) })) +const mockApp: { rootGraph?: Partial } = vi.hoisted(() => ({})) + vi.mock('@/scripts/app', () => ({ - app: { - graph: { - nodes: [] - } - } + app: mockApp })) vi.mock('@/utils/graphTraversalUtil', () => ({ @@ -107,9 +104,8 @@ describe('useMissingNodes', () => { nodeDefsByName: {} }) - // Reset app.graph.nodes - // @ts-expect-error - app.graph.nodes is readonly, but we need to modify it for testing. - app.graph.nodes = [] + // Reset app.rootGraph.nodes + mockApp.rootGraph = { nodes: [] } // Default mock for collectAllNodes - returns empty array mockCollectAllNodes.mockReturnValue([]) @@ -306,13 +302,7 @@ describe('useMissingNodes', () => { }) describe('missing core nodes detection', () => { - const createMockNode = ( - type: string, - packId?: string, - version?: string - ): LGraphNode => - // @ts-expect-error - Creating a partial mock of LGraphNode for testing. - // We only need specific properties for our tests, not the full LGraphNode interface. + const createMockNode = (type: string, packId?: string, version?: string) => ({ type, properties: { cnr_id: packId, ver: version }, @@ -325,7 +315,7 @@ describe('useMissingNodes', () => { mode: 0, inputs: [], outputs: [] - }) + }) as unknown as LGraphNode it('identifies missing core nodes not in nodeDefStore', () => { const coreNode1 = createMockNode('CoreNode1', 'comfy-core', '1.2.0') @@ -467,8 +457,7 @@ describe('useMissingNodes', () => { it('calls collectAllNodes with the app graph and filter function', () => { const mockGraph = { nodes: [], subgraphs: new Map() } - // @ts-expect-error - Mocking app.graph for testing - app.graph = mockGraph + mockApp.rootGraph = mockGraph const { missingCoreNodes } = useMissingNodes() // Access the computed to trigger the function @@ -490,8 +479,7 @@ describe('useMissingNodes', () => { it('filter function correctly identifies missing core nodes', () => { const mockGraph = { nodes: [], subgraphs: new Map() } - // @ts-expect-error - Mocking app.graph for testing - app.graph = mockGraph + mockApp.rootGraph = mockGraph mockUseNodeDefStore.mockReturnValue({ nodeDefsByName: { @@ -579,14 +567,13 @@ describe('useMissingNodes', () => { subgraph: mockSubgraph, type: 'SubgraphContainer', properties: { cnr_id: 'custom-pack' } - } + } as unknown as LGraphNode const mockMainGraph = { nodes: [mainMissingNode, mockSubgraphNode] - } + } as Partial as LGraph - // @ts-expect-error - Mocking app.graph for testing - app.graph = mockMainGraph + mockApp.rootGraph = mockMainGraph mockUseNodeDefStore.mockReturnValue({ nodeDefsByName: { diff --git a/tests-ui/tests/renderer/extensions/vueNodes/components/NodeHeader.subgraph.test.ts b/tests-ui/tests/renderer/extensions/vueNodes/components/NodeHeader.subgraph.test.ts index 740b9e7dbb..0f7ca7aefa 100644 --- a/tests-ui/tests/renderer/extensions/vueNodes/components/NodeHeader.subgraph.test.ts +++ b/tests-ui/tests/renderer/extensions/vueNodes/components/NodeHeader.subgraph.test.ts @@ -5,15 +5,19 @@ import { createTestingPinia } from '@pinia/testing' import { mount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { + LGraph, + LGraphNode, + SubgraphNode +} from '@/lib/litegraph/src/litegraph' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' import NodeHeader from '@/renderer/extensions/vueNodes/components/NodeHeader.vue' import { getNodeByLocatorId } from '@/utils/graphTraversalUtil' +const mockApp: { rootGraph?: Partial } = vi.hoisted(() => ({})) // Mock dependencies vi.mock('@/scripts/app', () => ({ - app: { - graph: null as any - } + app: mockApp })) vi.mock('@/utils/graphTraversalUtil', () => ({ @@ -55,17 +59,12 @@ vi.mock('@/i18n', () => ({ describe('NodeHeader - Subgraph Functionality', () => { // Helper to setup common mocks const setupMocks = async (isSubgraph = true, hasGraph = true) => { - const { app } = await import('@/scripts/app') - - if (hasGraph) { - ;(app as any).graph = { rootGraph: {} } - } else { - ;(app as any).graph = null - } + if (hasGraph) mockApp.rootGraph = {} + else mockApp.rootGraph = undefined vi.mocked(getNodeByLocatorId).mockReturnValue({ - isSubgraphNode: () => isSubgraph - } as any) + isSubgraphNode: (): this is SubgraphNode => isSubgraph + } as LGraphNode) } beforeEach(() => { diff --git a/tests-ui/tests/store/executionStore.test.ts b/tests-ui/tests/store/executionStore.test.ts index 3c56059a71..7c893bd601 100644 --- a/tests-ui/tests/store/executionStore.test.ts +++ b/tests-ui/tests/store/executionStore.test.ts @@ -38,9 +38,9 @@ vi.mock('@/composables/node/useNodeProgressText', () => ({ // Mock the app import with proper implementation vi.mock('@/scripts/app', () => ({ app: { - graph: { + rootGraph: { getNodeById: vi.fn(), - _nodes: [] // Add _nodes array for workflowStore iteration + nodes: [] // Add nodes array for workflowStore iteration }, revokePreviews: vi.fn(), nodePreviewImages: {} @@ -66,7 +66,7 @@ describe('useExecutionStore - NodeLocatorId conversions', () => { // Mock subgraph structure const mockSubgraph = { id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', - _nodes: [] + nodes: [] } const mockNode = { @@ -75,8 +75,8 @@ describe('useExecutionStore - NodeLocatorId conversions', () => { subgraph: mockSubgraph } as any - // Mock app.graph.getNodeById to return the mock node - vi.mocked(app.graph.getNodeById).mockReturnValue(mockNode) + // Mock app.rootGraph.getNodeById to return the mock node + vi.mocked(app.rootGraph.getNodeById).mockReturnValue(mockNode) const result = store.executionIdToNodeLocatorId('123:456') @@ -98,8 +98,8 @@ describe('useExecutionStore - NodeLocatorId conversions', () => { }) it('should return undefined when conversion fails', () => { - // Mock app.graph.getNodeById to return null (node not found) - vi.mocked(app.graph.getNodeById).mockReturnValue(null) + // Mock app.rootGraph.getNodeById to return null (node not found) + vi.mocked(app.rootGraph.getNodeById).mockReturnValue(null) expect(store.executionIdToNodeLocatorId('999:456')).toBe(undefined) }) @@ -171,7 +171,8 @@ describe('useExecutionStore - Node Error Lookups', () => { const subgraphUuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' const mockSubgraph = { id: subgraphUuid, - _nodes: [] + getNodeById: vi.fn(), + nodes: [] } const mockNode = { @@ -180,7 +181,7 @@ describe('useExecutionStore - Node Error Lookups', () => { subgraph: mockSubgraph } as any - vi.mocked(app.graph.getNodeById).mockReturnValue(mockNode) + vi.mocked(app.rootGraph.getNodeById).mockReturnValue(mockNode) store.lastNodeErrors = { '123:456': { diff --git a/tests-ui/tests/store/workflowStore.test.ts b/tests-ui/tests/store/workflowStore.test.ts index 6e82559feb..7dd549e087 100644 --- a/tests-ui/tests/store/workflowStore.test.ts +++ b/tests-ui/tests/store/workflowStore.test.ts @@ -622,7 +622,7 @@ describe('useWorkflowStore', () => { mockSubgraph.rootGraph = mockRootGraph as any - vi.mocked(comfyApp).graph = mockRootGraph as any + vi.mocked(comfyApp).rootGraph = mockRootGraph as any vi.mocked(comfyApp.canvas).subgraph = mockSubgraph as any store.activeSubgraph = mockSubgraph as any })