mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-21 15:24:09 +00:00
Cleanup app.graph usage (#7399)
Prior to the release of subgraphs, there was a single graph accessed through `app.graph`. Now that there's multiple graphs, there's a lot of code that needs to be reviewed and potentially updated depending on if it cares about nearby nodes, all nodes, or something else requiring specific attention. This was done by simply changing the type of `app.graph` to unknown so the typechecker will complain about every place it's currently used. References were then updated to `app.rootGraph` if the previous usage was correct, or actually rewritten. By not getting rid of `app.graph`, this change already ensures that there's no loss of functionality for custom nodes, but the prior typing of `app.graph` can always be restored if future dissuasion of `app.graph` usage creates issues. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7399-Cleanup-app-graph-usage-2c76d73d365081178743dfdcf07f44d0) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -83,7 +83,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const hasMissingNodes = computed(() =>
|
||||
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
|
||||
graphHasMissingNodes(app.rootGraph, nodeDefStore.nodeDefsByName)
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -159,6 +159,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 SelectionRectangle from './SelectionRectangle.vue'
|
||||
|
||||
@@ -269,20 +270,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)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -332,7 +331,7 @@ watch(
|
||||
}
|
||||
|
||||
// Force canvas redraw to ensure progress updates are visible
|
||||
canvas.graph.setDirtyCanvas(true, false)
|
||||
canvas.setDirty(true, false)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
@@ -344,7 +343,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
|
||||
@@ -354,7 +353,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
|
||||
@@ -367,9 +366,9 @@ watch(
|
||||
node.inputs[inputIndex].hasErrors = true
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
comfyApp.canvas.draw(true, true)
|
||||
comfyApp.canvas.setDirty(true, true)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -55,7 +55,7 @@ export const useNodeBadge = () => {
|
||||
showApiPricingBadge
|
||||
],
|
||||
() => {
|
||||
app.graph?.setDirtyCanvas(true, true)
|
||||
app.canvas?.setDirty(true, true)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export const useCanvasDrop = (canvasRef: Ref<HTMLCanvasElement | null>) => {
|
||||
} 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) {
|
||||
|
||||
@@ -104,7 +104,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',
|
||||
@@ -127,7 +127,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',
|
||||
@@ -705,7 +705,7 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
'Comfy.GroupSelectedNodes.Padding'
|
||||
)
|
||||
group.resizeTo(group.children, padding)
|
||||
app.graph.change()
|
||||
app.canvas.setDirty(false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, {})
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
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<HTMLDialogElement> {
|
||||
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<HTMLDialogElement> {
|
||||
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<HTMLDialogElement> {
|
||||
|
||||
// @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<HTMLDialogElement> {
|
||||
'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<HTMLDialogElement> {
|
||||
)
|
||||
) {
|
||||
// @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<HTMLDialogElement> {
|
||||
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<HTMLDialogElement> {
|
||||
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<HTMLDialogElement> {
|
||||
}
|
||||
|
||||
this.modifications = {}
|
||||
this.app.graph.setDirtyCanvas(true, true)
|
||||
this.app.canvas.setDirty(true, true)
|
||||
this.changeGroup(this.selectedGroup, false)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -142,7 +142,7 @@ app.registerExtension({
|
||||
onNodeOutputsUpdated(nodeOutputs: Record<NodeLocatorId, any>) {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -433,7 +433,7 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
|
||||
}
|
||||
|
||||
const nodeCounts = reduceAllNodes<NodeMetrics>(
|
||||
app.graph,
|
||||
app.rootGraph,
|
||||
(metrics, node) => {
|
||||
const nodeDef = nodeDefStore.nodeDefsByName[node.type]
|
||||
const isCustomNode =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -436,7 +436,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
|
||||
@@ -468,9 +468,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(() => {
|
||||
|
||||
@@ -243,7 +243,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -155,8 +155,8 @@ const isWaveformActive = computed(() => isRecording.value || isPlaying.value)
|
||||
const modelValue = defineModel<string>({ 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) {
|
||||
|
||||
@@ -185,8 +185,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(() => {
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
@@ -1351,12 +1355,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
|
||||
@@ -1397,16 +1401,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()
|
||||
}
|
||||
@@ -1481,7 +1476,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
|
||||
}
|
||||
@@ -1512,24 +1507,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) {
|
||||
@@ -1558,16 +1554,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) {
|
||||
@@ -1597,11 +1593,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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1653,7 +1649,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)
|
||||
@@ -1718,8 +1714,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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@ export function clone<T>(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 */
|
||||
|
||||
@@ -265,7 +265,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) => {
|
||||
@@ -863,7 +863,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)
|
||||
}
|
||||
@@ -884,7 +884,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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -61,7 +61,7 @@ export const useMissingNodes = createSharedComposable(() => {
|
||||
}
|
||||
|
||||
const missingCoreNodes = computed<Record<string, LGraphNode[]>>(() => {
|
||||
const missingNodes = collectAllNodes(app.graph, isMissingCoreNode)
|
||||
const missingNodes = collectAllNodes(app.rootGraph, isMissingCoreNode)
|
||||
return groupBy(missingNodes, (node) => String(node.properties?.ver || ''))
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<LGraph> } = 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<LGraph> as LGraph
|
||||
|
||||
// @ts-expect-error - Mocking app.graph for testing
|
||||
app.graph = mockMainGraph
|
||||
mockApp.rootGraph = mockMainGraph
|
||||
|
||||
mockUseNodeDefStore.mockReturnValue({
|
||||
nodeDefsByName: {
|
||||
|
||||
@@ -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<LGraph> } = 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(() => {
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user