mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
Backport of #10123, #9967, and #9972 to `core/1.42` Includes three cherry-picks in dependency order: 1. #9972 — `fix: resolve all lint warnings` (clean) 2. #9967 — `test: harden subgraph test coverage and remove low-value tests` (clean) 3. #10123 — `test: subgraph integration contracts and expanded Playwright coverage` (1 conflict, auto-resolved by rerere from #10326) See #10326 for core/1.41 backport with detailed conflict resolution notes. --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: bymyself <cbyrne@comfy.org> Co-authored-by: GitHub Action <action@github.com>
128 lines
3.7 KiB
TypeScript
128 lines
3.7 KiB
TypeScript
import type { ResolvedPromotedWidget } from '@/core/graph/subgraph/promotedWidgetTypes'
|
|
import { isPromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetTypes'
|
|
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
|
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
|
|
|
type PromotedWidgetResolutionFailure =
|
|
| 'invalid-host'
|
|
| 'cycle'
|
|
| 'missing-node'
|
|
| 'missing-widget'
|
|
| 'max-depth-exceeded'
|
|
|
|
type PromotedWidgetResolutionResult =
|
|
| { status: 'resolved'; resolved: ResolvedPromotedWidget }
|
|
| { status: 'failure'; failure: PromotedWidgetResolutionFailure }
|
|
|
|
const MAX_PROMOTED_WIDGET_CHAIN_DEPTH = 100
|
|
|
|
function traversePromotedWidgetChain(
|
|
hostNode: SubgraphNode,
|
|
nodeId: string,
|
|
widgetName: string,
|
|
sourceNodeId?: string
|
|
): PromotedWidgetResolutionResult {
|
|
const visited = new Set<string>()
|
|
const hostUidByObject = new WeakMap<SubgraphNode, number>()
|
|
let nextHostUid = 0
|
|
let currentHost = hostNode
|
|
let currentNodeId = nodeId
|
|
let currentWidgetName = widgetName
|
|
let currentSourceNodeId = sourceNodeId
|
|
|
|
for (let depth = 0; depth < MAX_PROMOTED_WIDGET_CHAIN_DEPTH; depth++) {
|
|
let hostUid = hostUidByObject.get(currentHost)
|
|
if (hostUid === undefined) {
|
|
hostUid = nextHostUid
|
|
nextHostUid += 1
|
|
hostUidByObject.set(currentHost, hostUid)
|
|
}
|
|
|
|
const key = `${hostUid}:${currentNodeId}:${currentWidgetName}:${currentSourceNodeId ?? ''}`
|
|
if (visited.has(key)) {
|
|
return { status: 'failure', failure: 'cycle' }
|
|
}
|
|
visited.add(key)
|
|
|
|
const sourceNode = currentHost.subgraph.getNodeById(currentNodeId)
|
|
if (!sourceNode) {
|
|
return { status: 'failure', failure: 'missing-node' }
|
|
}
|
|
|
|
const sourceWidget = findWidgetByIdentity(
|
|
sourceNode.widgets,
|
|
currentWidgetName,
|
|
currentSourceNodeId
|
|
)
|
|
if (!sourceWidget) {
|
|
return { status: 'failure', failure: 'missing-widget' }
|
|
}
|
|
|
|
if (!isPromotedWidgetView(sourceWidget)) {
|
|
return {
|
|
status: 'resolved',
|
|
resolved: { node: sourceNode, widget: sourceWidget }
|
|
}
|
|
}
|
|
|
|
if (!sourceWidget.node?.isSubgraphNode()) {
|
|
return { status: 'failure', failure: 'missing-node' }
|
|
}
|
|
|
|
currentHost = sourceWidget.node
|
|
currentNodeId = sourceWidget.sourceNodeId
|
|
currentWidgetName = sourceWidget.sourceWidgetName
|
|
currentSourceNodeId = undefined
|
|
}
|
|
|
|
return { status: 'failure', failure: 'max-depth-exceeded' }
|
|
}
|
|
|
|
function findWidgetByIdentity(
|
|
widgets: IBaseWidget[] | undefined,
|
|
widgetName: string,
|
|
sourceNodeId?: string
|
|
): IBaseWidget | undefined {
|
|
if (!widgets) return undefined
|
|
|
|
if (sourceNodeId) {
|
|
return widgets.find(
|
|
(entry) =>
|
|
isPromotedWidgetView(entry) &&
|
|
(entry.disambiguatingSourceNodeId ?? entry.sourceNodeId) ===
|
|
sourceNodeId &&
|
|
(entry.sourceWidgetName === widgetName || entry.name === widgetName)
|
|
)
|
|
}
|
|
|
|
return widgets.find((entry) => entry.name === widgetName)
|
|
}
|
|
|
|
export function resolvePromotedWidgetAtHost(
|
|
hostNode: SubgraphNode,
|
|
nodeId: string,
|
|
widgetName: string,
|
|
sourceNodeId?: string
|
|
): ResolvedPromotedWidget | undefined {
|
|
const node = hostNode.subgraph.getNodeById(nodeId)
|
|
if (!node) return undefined
|
|
|
|
const widget = findWidgetByIdentity(node.widgets, widgetName, sourceNodeId)
|
|
if (!widget) return undefined
|
|
|
|
return { node, widget }
|
|
}
|
|
|
|
export function resolveConcretePromotedWidget(
|
|
hostNode: LGraphNode,
|
|
nodeId: string,
|
|
widgetName: string,
|
|
sourceNodeId?: string
|
|
): PromotedWidgetResolutionResult {
|
|
if (!hostNode.isSubgraphNode()) {
|
|
return { status: 'failure', failure: 'invalid-host' }
|
|
}
|
|
return traversePromotedWidgetChain(hostNode, nodeId, widgetName, sourceNodeId)
|
|
}
|