mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
## Summary Adds a node replacement UI to the Errors Tab so users can swap missing nodes with compatible alternatives directly from the error panel, without opening a separate dialog. ## Changes - **What**: New `SwapNodesCard` and `SwapNodeGroupRow` components render swap groups in the Errors Tab; each group shows the missing node type, its instances (with locate buttons), and a Replace button. Added `useMissingNodeScan` composable to scan the graph for missing nodes and populate `executionErrorStore`. Added `removeMissingNodesByType()` to `executionErrorStore` so replaced nodes are pruned from the error list reactively. ## Bug Fixes Found During Implementation ### Bug 1: Replaced nodes render as empty shells until page refresh `replaceWithMapping()` directly mutates `_nodes[idx]`, bypassing the Vue rendering pipeline entirely. Because the replacement node reuses the same ID, `vueNodeData` retains the stale entry from the old placeholder (`hasErrors: true`, empty widgets/inputs). `graph.setDirtyCanvas()` only repaints the LiteGraph canvas and has no effect on Vue. **Fix**: After `replaceWithMapping()`, manually call `nodeGraph.onNodeAdded?.(newNode)` to trigger `handleNodeAdded` in `useGraphNodeManager`, which runs `extractVueNodeData(newNode)` and updates `vueNodeData` correctly. Also added a guard in `handleNodeAdded` to skip `layoutStore.createNode()` when a layout for the same ID already exists, preventing a duplicate `spatialIndex.insert()`. ### Bug 2: Missing node error list overwritten by incomplete server response Two compounding issues: (A) the server's `missing_node_type` error only reports the *first* missing node — the old handler parsed this and called `surfaceMissingNodes([singleNode])`, overwriting the full list collected at load time. (B) `queuePrompt()` calls `clearAllErrors()` before the API request; if the subsequent rescan used the stale `has_errors` flag and found nothing, the missing nodes were permanently lost. **Fix**: Created `useMissingNodeScan.ts` which scans `LiteGraph.registered_node_types` directly (not `has_errors`). The `missing_node_type` catch block in `app.ts` now calls `rescanAndSurfaceMissingNodes(this.rootGraph)` instead of parsing the server's partial response. ## Review Focus - `handleReplaceNode` removes the group from the store only when `replaceNodesInPlace` returns at least one replaced node — should we always clear, or only on full success? - `useMissingNodeScan` re-scans on every execution-error change; confirm no performance concerns for large graphs with many subgraphs. ## Screenshots https://github.com/user-attachments/assets/78310fc4-0424-4920-b369-cef60a123d50 https://github.com/user-attachments/assets/3d2fd5e1-5e85-4c20-86aa-8bf920e86987 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9253-feat-add-node-replacement-UI-to-Errors-Tab-3136d73d365081718d4ddfd628cb4449) by [Unito](https://www.unito.io)
45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
|
import type { LGraph } from '@/lib/litegraph/src/litegraph'
|
|
import { useNodeReplacementStore } from '@/platform/nodeReplacement/nodeReplacementStore'
|
|
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
|
import type { MissingNodeType } from '@/types/comfy'
|
|
import {
|
|
collectAllNodes,
|
|
getExecutionIdByNode
|
|
} from '@/utils/graphTraversalUtil'
|
|
import { getCnrIdFromNode } from '@/workbench/extensions/manager/utils/missingNodeErrorUtil'
|
|
|
|
/** Scan the live graph for unregistered node types and build a full MissingNodeType list. */
|
|
function scanMissingNodes(rootGraph: LGraph): MissingNodeType[] {
|
|
const nodeReplacementStore = useNodeReplacementStore()
|
|
const missingNodeTypes: MissingNodeType[] = []
|
|
|
|
const allNodes = collectAllNodes(rootGraph)
|
|
|
|
for (const node of allNodes) {
|
|
const originalType = node.last_serialization?.type ?? node.type ?? 'Unknown'
|
|
|
|
if (originalType in LiteGraph.registered_node_types) continue
|
|
|
|
const cnrId = getCnrIdFromNode(node)
|
|
const replacement = nodeReplacementStore.getReplacementFor(originalType)
|
|
const executionId = getExecutionIdByNode(rootGraph, node)
|
|
|
|
missingNodeTypes.push({
|
|
type: originalType,
|
|
nodeId: executionId ?? String(node.id),
|
|
cnrId,
|
|
isReplaceable: replacement !== null,
|
|
replacement: replacement ?? undefined
|
|
})
|
|
}
|
|
|
|
return missingNodeTypes
|
|
}
|
|
|
|
/** Re-scan the graph for missing nodes and update the error store. */
|
|
export function rescanAndSurfaceMissingNodes(rootGraph: LGraph): void {
|
|
const types = scanMissingNodes(rootGraph)
|
|
useExecutionErrorStore().surfaceMissingNodes(types)
|
|
}
|