mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
refactor(groupNode): improve type safety and reduce casts
Amp-Thread-ID: https://ampcode.com/threads/T-019baa1c-6aa2-769b-a5f9-a705b5ef2b2b Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -4,10 +4,11 @@ import type { GroupNodeWorkflowData } from '@/lib/litegraph/src/LGraph'
|
|||||||
import type {
|
import type {
|
||||||
GroupNodeInputConfig,
|
GroupNodeInputConfig,
|
||||||
GroupNodeInputsSpec,
|
GroupNodeInputsSpec,
|
||||||
|
GroupNodeInternalLink,
|
||||||
GroupNodeOutputType,
|
GroupNodeOutputType,
|
||||||
PartialLinkInfo
|
PartialLinkInfo
|
||||||
} from './groupNodeTypes'
|
} from './groupNodeTypes'
|
||||||
import { LLink, type SerialisedLLinkArray } from '@/lib/litegraph/src/LLink'
|
import { LLink } from '@/lib/litegraph/src/LLink'
|
||||||
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||||
import {
|
import {
|
||||||
@@ -39,7 +40,7 @@ import { app } from '../../scripts/app'
|
|||||||
import { ManageGroupDialog } from './groupNodeManage'
|
import { ManageGroupDialog } from './groupNodeManage'
|
||||||
import { mergeIfValid } from './widgetInputs'
|
import { mergeIfValid } from './widgetInputs'
|
||||||
|
|
||||||
type GroupNodeLink = SerialisedLLinkArray
|
type GroupNodeLink = GroupNodeInternalLink
|
||||||
type LinksFromMap = Record<number, Record<number, GroupNodeLink[]>>
|
type LinksFromMap = Record<number, Record<number, GroupNodeLink[]>>
|
||||||
type LinksToMap = Record<number, Record<number, GroupNodeLink>>
|
type LinksToMap = Record<number, Record<number, GroupNodeLink>>
|
||||||
type ExternalFromMap = Record<number, Record<number, string | number>>
|
type ExternalFromMap = Record<number, Record<number, string | number>>
|
||||||
@@ -1005,7 +1006,7 @@ export class GroupNodeHandler {
|
|||||||
return inputNode
|
return inputNode
|
||||||
}
|
}
|
||||||
|
|
||||||
innerNode.getInputLink = ((slot: number): PartialLinkInfo | null => {
|
innerNode.getInputLink = (slot: number): PartialLinkInfo | null => {
|
||||||
const nodeIdx = innerNode.index ?? 0
|
const nodeIdx = innerNode.index ?? 0
|
||||||
const externalSlot = this.groupData.oldToNewInputMap[nodeIdx]?.[slot]
|
const externalSlot = this.groupData.oldToNewInputMap[nodeIdx]?.[slot]
|
||||||
if (externalSlot != null) {
|
if (externalSlot != null) {
|
||||||
@@ -1025,14 +1026,15 @@ export class GroupNodeHandler {
|
|||||||
const innerLink = this.groupData.linksTo[nodeIdx]?.[slot]
|
const innerLink = this.groupData.linksTo[nodeIdx]?.[slot]
|
||||||
if (!innerLink) return null
|
if (!innerLink) return null
|
||||||
const linkSrcIdx = innerLink[0]
|
const linkSrcIdx = innerLink[0]
|
||||||
if (linkSrcIdx == null) return null
|
const linkSrcSlot = innerLink[1]
|
||||||
|
if (linkSrcIdx == null || linkSrcSlot == null) return null
|
||||||
return {
|
return {
|
||||||
origin_id: innerNodes[Number(linkSrcIdx)].id,
|
origin_id: innerNodes[Number(linkSrcIdx)].id,
|
||||||
origin_slot: innerLink[1],
|
origin_slot: linkSrcSlot,
|
||||||
target_id: innerNode.id,
|
target_id: innerNode.id,
|
||||||
target_slot: +slot
|
target_slot: +slot
|
||||||
}
|
}
|
||||||
}) as typeof innerNode.getInputLink
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1042,7 +1044,7 @@ export class GroupNodeHandler {
|
|||||||
if (!output || !this.innerNodes) return null
|
if (!output || !this.innerNodes) return null
|
||||||
const nodeIdx = output.node.index ?? 0
|
const nodeIdx = output.node.index ?? 0
|
||||||
let innerNode: LGraphNode | null = this.innerNodes[nodeIdx]
|
let innerNode: LGraphNode | null = this.innerNodes[nodeIdx]
|
||||||
let l
|
let l = innerNode?.getInputLink(0)
|
||||||
while (innerNode?.type === 'Reroute') {
|
while (innerNode?.type === 'Reroute') {
|
||||||
l = innerNode.getInputLink(0)
|
l = innerNode.getInputLink(0)
|
||||||
innerNode = innerNode.getInputNode(0)
|
innerNode = innerNode.getInputNode(0)
|
||||||
@@ -1053,7 +1055,7 @@ export class GroupNodeHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
l &&
|
l instanceof LLink &&
|
||||||
GroupNodeHandler.isGroupNode(innerNode) &&
|
GroupNodeHandler.isGroupNode(innerNode) &&
|
||||||
innerNode.updateLink
|
innerNode.updateLink
|
||||||
) {
|
) {
|
||||||
@@ -1098,10 +1100,9 @@ export class GroupNodeHandler {
|
|||||||
|
|
||||||
const subgraphInstanceIdPath = [...subgraphNodePath, this.node.id]
|
const subgraphInstanceIdPath = [...subgraphNodePath, this.node.id]
|
||||||
|
|
||||||
// Assertion: Deprecated, does not matter.
|
// Get the parent subgraph node if we're inside a subgraph
|
||||||
const subgraphNode = (this.node.graph?.getNodeById(
|
const parentNode = this.node.graph?.getNodeById(subgraphNodePath.at(-1))
|
||||||
subgraphNodePath.at(-1)
|
const subgraphNode = parentNode?.isSubgraphNode() ? parentNode : undefined
|
||||||
) ?? undefined) as SubgraphNode | undefined
|
|
||||||
|
|
||||||
for (const node of this.innerNodes ?? []) {
|
for (const node of this.innerNodes ?? []) {
|
||||||
node.graph ??= this.node.graph
|
node.graph ??= this.node.graph
|
||||||
@@ -1437,13 +1438,9 @@ export class GroupNodeHandler {
|
|||||||
|
|
||||||
type EventDetail = { display_node?: string; node?: string } | string
|
type EventDetail = { display_node?: string; node?: string } | string
|
||||||
const handleEvent = (
|
const handleEvent = (
|
||||||
type: string,
|
type: 'executing' | 'executed',
|
||||||
getId: (detail: EventDetail) => string | undefined,
|
getId: (detail: EventDetail) => string | undefined,
|
||||||
getEvent: (
|
getEvent: (detail: EventDetail, id: string, node: LGraphNode) => unknown
|
||||||
detail: EventDetail,
|
|
||||||
id: string,
|
|
||||||
node: LGraphNode
|
|
||||||
) => EventDetail
|
|
||||||
) => {
|
) => {
|
||||||
const handler = ({ detail }: CustomEvent<EventDetail>) => {
|
const handler = ({ detail }: CustomEvent<EventDetail>) => {
|
||||||
const id = getId(detail)
|
const id = getId(detail)
|
||||||
@@ -1457,16 +1454,14 @@ export class GroupNodeHandler {
|
|||||||
;(
|
;(
|
||||||
this.node as LGraphNode & { runningInternalNodeId?: number }
|
this.node as LGraphNode & { runningInternalNodeId?: number }
|
||||||
).runningInternalNodeId = innerNodeIndex
|
).runningInternalNodeId = innerNodeIndex
|
||||||
|
// Cast needed: dispatching synthetic events for inner nodes with transformed payloads
|
||||||
api.dispatchCustomEvent(
|
api.dispatchCustomEvent(
|
||||||
type as 'executing',
|
type,
|
||||||
getEvent(detail, `${this.node.id}`, this.node) as string
|
getEvent(detail, `${this.node.id}`, this.node) as string
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
api.addEventListener(
|
api.addEventListener(type, handler as EventListener)
|
||||||
type as 'executing' | 'executed',
|
|
||||||
handler as EventListener
|
|
||||||
)
|
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
import type { SerialisedLLinkArray } from '@/lib/litegraph/src/LLink'
|
import type { ILinkRouting } from '@/lib/litegraph/src/interfaces'
|
||||||
|
import type { ISlotType } from '@/lib/litegraph/src/interfaces'
|
||||||
import type { ISerialisedNode } from '@/lib/litegraph/src/types/serialisation'
|
import type { ISerialisedNode } from '@/lib/litegraph/src/types/serialisation'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group node internal link format.
|
||||||
|
* This differs from standard SerialisedLLinkArray - indices represent node/slot positions within the group.
|
||||||
|
* Format: [sourceNodeIndex, sourceSlot, targetNodeIndex, targetSlot, ...optionalData]
|
||||||
|
* The type (ISlotType) may be at index 5 if present.
|
||||||
|
*/
|
||||||
|
export type GroupNodeInternalLink = [
|
||||||
|
sourceNodeIndex: number | null,
|
||||||
|
sourceSlot: number | null,
|
||||||
|
targetNodeIndex: number | null,
|
||||||
|
targetSlot: number | null,
|
||||||
|
...rest: (number | string | ISlotType | null | undefined)[]
|
||||||
|
]
|
||||||
|
|
||||||
/** Serialized node data within a group node workflow, with group-specific index */
|
/** Serialized node data within a group node workflow, with group-specific index */
|
||||||
export interface GroupNodeSerializedNode extends Partial<ISerialisedNode> {
|
export interface GroupNodeSerializedNode extends Partial<ISerialisedNode> {
|
||||||
/** Position of this node within the group */
|
/** Position of this node within the group */
|
||||||
@@ -9,7 +24,7 @@ export interface GroupNodeSerializedNode extends Partial<ISerialisedNode> {
|
|||||||
|
|
||||||
export interface GroupNodeWorkflowData {
|
export interface GroupNodeWorkflowData {
|
||||||
external: (number | string)[][]
|
external: (number | string)[][]
|
||||||
links: SerialisedLLinkArray[]
|
links: GroupNodeInternalLink[]
|
||||||
nodes: GroupNodeSerializedNode[]
|
nodes: GroupNodeSerializedNode[]
|
||||||
config?: Record<number, unknown>
|
config?: Record<number, unknown>
|
||||||
}
|
}
|
||||||
@@ -37,11 +52,6 @@ export type GroupNodeOutputType = string | (string | number)[]
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Partial link info used internally by group node getInputLink override.
|
* Partial link info used internally by group node getInputLink override.
|
||||||
* Contains only the properties needed for group node execution context.
|
* Extends ILinkRouting to be compatible with the base getInputLink return type.
|
||||||
*/
|
*/
|
||||||
export interface PartialLinkInfo {
|
export interface PartialLinkInfo extends ILinkRouting {}
|
||||||
origin_id: string | number
|
|
||||||
origin_slot: number | string
|
|
||||||
target_id: string | number
|
|
||||||
target_slot: number
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import type {
|
|||||||
IColorable,
|
IColorable,
|
||||||
IContextMenuValue,
|
IContextMenuValue,
|
||||||
IFoundSlot,
|
IFoundSlot,
|
||||||
|
ILinkRouting,
|
||||||
INodeFlags,
|
INodeFlags,
|
||||||
INodeInputSlot,
|
INodeInputSlot,
|
||||||
INodeOutputSlot,
|
INodeOutputSlot,
|
||||||
@@ -1153,10 +1154,11 @@ export class LGraphNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the link info in the connection of an input slot
|
* Returns the link info in the connection of an input slot.
|
||||||
* @returns object or null
|
* Group nodes may override this to return ILinkRouting for internal routing.
|
||||||
|
* @returns LLink, ILinkRouting, or null
|
||||||
*/
|
*/
|
||||||
getInputLink(slot: number): LLink | null {
|
getInputLink(slot: number): LLink | ILinkRouting | null {
|
||||||
if (!this.inputs) return null
|
if (!this.inputs) return null
|
||||||
|
|
||||||
if (slot < this.inputs.length) {
|
if (slot < this.inputs.length) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { LGraphNode, NodeId } from './LGraphNode'
|
|||||||
import type { Reroute, RerouteId } from './Reroute'
|
import type { Reroute, RerouteId } from './Reroute'
|
||||||
import type {
|
import type {
|
||||||
CanvasColour,
|
CanvasColour,
|
||||||
|
ILinkRouting,
|
||||||
INodeInputSlot,
|
INodeInputSlot,
|
||||||
INodeOutputSlot,
|
INodeOutputSlot,
|
||||||
ISlotType,
|
ISlotType,
|
||||||
@@ -89,7 +90,9 @@ type BasicReadonlyNetwork = Pick<
|
|||||||
>
|
>
|
||||||
|
|
||||||
// this is the class in charge of storing link information
|
// this is the class in charge of storing link information
|
||||||
export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
export class LLink
|
||||||
|
implements LinkSegment, Serialisable<SerialisableLLink>, ILinkRouting
|
||||||
|
{
|
||||||
static _drawDebug = false
|
static _drawDebug = false
|
||||||
|
|
||||||
/** Link ID */
|
/** Link ID */
|
||||||
|
|||||||
@@ -184,6 +184,21 @@ export interface ItemLocator {
|
|||||||
): SubgraphInputNode | SubgraphOutputNode | undefined
|
): SubgraphInputNode | SubgraphOutputNode | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal link routing information used for input resolution.
|
||||||
|
* Both LLink and partial link info objects satisfy this interface.
|
||||||
|
*/
|
||||||
|
export interface ILinkRouting {
|
||||||
|
/** Output node ID */
|
||||||
|
readonly origin_id: NodeId
|
||||||
|
/** Output slot index */
|
||||||
|
readonly origin_slot: number
|
||||||
|
/** Input node ID */
|
||||||
|
readonly target_id: NodeId
|
||||||
|
/** Input slot index */
|
||||||
|
readonly target_slot: number
|
||||||
|
}
|
||||||
|
|
||||||
/** Contains a cached 2D canvas path and a centre point, with an optional forward angle. */
|
/** Contains a cached 2D canvas path and a centre point, with an optional forward angle. */
|
||||||
export interface LinkSegment {
|
export interface LinkSegment {
|
||||||
/** Link / reroute ID */
|
/** Link / reroute ID */
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import type {
|
|||||||
CallbackReturn,
|
CallbackReturn,
|
||||||
ISlotType
|
ISlotType
|
||||||
} from '@/lib/litegraph/src/interfaces'
|
} from '@/lib/litegraph/src/interfaces'
|
||||||
import { LGraphEventMode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
import {
|
||||||
|
LGraphEventMode,
|
||||||
|
LiteGraph,
|
||||||
|
LLink
|
||||||
|
} from '@/lib/litegraph/src/litegraph'
|
||||||
|
|
||||||
import type { Subgraph } from './Subgraph'
|
import type { Subgraph } from './Subgraph'
|
||||||
import type { SubgraphNode } from './SubgraphNode'
|
import type { SubgraphNode } from './SubgraphNode'
|
||||||
@@ -289,7 +293,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode {
|
|||||||
|
|
||||||
if (node.isVirtualNode) {
|
if (node.isVirtualNode) {
|
||||||
const virtualLink = this.node.getInputLink(slot)
|
const virtualLink = this.node.getInputLink(slot)
|
||||||
if (virtualLink) {
|
if (virtualLink instanceof LLink) {
|
||||||
const { inputNode } = virtualLink.resolve(this.graph)
|
const { inputNode } = virtualLink.resolve(this.graph)
|
||||||
if (!inputNode)
|
if (!inputNode)
|
||||||
throw new InvalidLinkError(
|
throw new InvalidLinkError(
|
||||||
|
|||||||
Reference in New Issue
Block a user