diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index 04924a0cd..d87967434 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -4,10 +4,11 @@ import type { GroupNodeWorkflowData } from '@/lib/litegraph/src/LGraph' import type { GroupNodeInputConfig, GroupNodeInputsSpec, + GroupNodeInternalLink, GroupNodeOutputType, PartialLinkInfo } 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 { IContextMenuValue } from '@/lib/litegraph/src/interfaces' import { @@ -39,7 +40,7 @@ import { app } from '../../scripts/app' import { ManageGroupDialog } from './groupNodeManage' import { mergeIfValid } from './widgetInputs' -type GroupNodeLink = SerialisedLLinkArray +type GroupNodeLink = GroupNodeInternalLink type LinksFromMap = Record> type LinksToMap = Record> type ExternalFromMap = Record> @@ -1005,7 +1006,7 @@ export class GroupNodeHandler { return inputNode } - innerNode.getInputLink = ((slot: number): PartialLinkInfo | null => { + innerNode.getInputLink = (slot: number): PartialLinkInfo | null => { const nodeIdx = innerNode.index ?? 0 const externalSlot = this.groupData.oldToNewInputMap[nodeIdx]?.[slot] if (externalSlot != null) { @@ -1025,14 +1026,15 @@ export class GroupNodeHandler { const innerLink = this.groupData.linksTo[nodeIdx]?.[slot] if (!innerLink) return null const linkSrcIdx = innerLink[0] - if (linkSrcIdx == null) return null + const linkSrcSlot = innerLink[1] + if (linkSrcIdx == null || linkSrcSlot == null) return null return { origin_id: innerNodes[Number(linkSrcIdx)].id, - origin_slot: innerLink[1], + origin_slot: linkSrcSlot, target_id: innerNode.id, target_slot: +slot } - }) as typeof innerNode.getInputLink + } } } @@ -1042,7 +1044,7 @@ export class GroupNodeHandler { if (!output || !this.innerNodes) return null const nodeIdx = output.node.index ?? 0 let innerNode: LGraphNode | null = this.innerNodes[nodeIdx] - let l + let l = innerNode?.getInputLink(0) while (innerNode?.type === 'Reroute') { l = innerNode.getInputLink(0) innerNode = innerNode.getInputNode(0) @@ -1053,7 +1055,7 @@ export class GroupNodeHandler { } if ( - l && + l instanceof LLink && GroupNodeHandler.isGroupNode(innerNode) && innerNode.updateLink ) { @@ -1098,10 +1100,9 @@ export class GroupNodeHandler { const subgraphInstanceIdPath = [...subgraphNodePath, this.node.id] - // Assertion: Deprecated, does not matter. - const subgraphNode = (this.node.graph?.getNodeById( - subgraphNodePath.at(-1) - ) ?? undefined) as SubgraphNode | undefined + // Get the parent subgraph node if we're inside a subgraph + const parentNode = this.node.graph?.getNodeById(subgraphNodePath.at(-1)) + const subgraphNode = parentNode?.isSubgraphNode() ? parentNode : undefined for (const node of this.innerNodes ?? []) { node.graph ??= this.node.graph @@ -1437,13 +1438,9 @@ export class GroupNodeHandler { type EventDetail = { display_node?: string; node?: string } | string const handleEvent = ( - type: string, + type: 'executing' | 'executed', getId: (detail: EventDetail) => string | undefined, - getEvent: ( - detail: EventDetail, - id: string, - node: LGraphNode - ) => EventDetail + getEvent: (detail: EventDetail, id: string, node: LGraphNode) => unknown ) => { const handler = ({ detail }: CustomEvent) => { const id = getId(detail) @@ -1457,16 +1454,14 @@ export class GroupNodeHandler { ;( this.node as LGraphNode & { runningInternalNodeId?: number } ).runningInternalNodeId = innerNodeIndex + // Cast needed: dispatching synthetic events for inner nodes with transformed payloads api.dispatchCustomEvent( - type as 'executing', + type, getEvent(detail, `${this.node.id}`, this.node) as string ) } } - api.addEventListener( - type as 'executing' | 'executed', - handler as EventListener - ) + api.addEventListener(type, handler as EventListener) return handler } diff --git a/src/extensions/core/groupNodeTypes.ts b/src/extensions/core/groupNodeTypes.ts index d24b83c25..6aa91e48d 100644 --- a/src/extensions/core/groupNodeTypes.ts +++ b/src/extensions/core/groupNodeTypes.ts @@ -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' +/** + * 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 */ export interface GroupNodeSerializedNode extends Partial { /** Position of this node within the group */ @@ -9,7 +24,7 @@ export interface GroupNodeSerializedNode extends Partial { export interface GroupNodeWorkflowData { external: (number | string)[][] - links: SerialisedLLinkArray[] + links: GroupNodeInternalLink[] nodes: GroupNodeSerializedNode[] config?: Record } @@ -37,11 +52,6 @@ export type GroupNodeOutputType = string | (string | number)[] /** * 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 { - origin_id: string | number - origin_slot: number | string - target_id: string | number - target_slot: number -} +export interface PartialLinkInfo extends ILinkRouting {} diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index 27559e021..fc7e76bc7 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -34,6 +34,7 @@ import type { IColorable, IContextMenuValue, IFoundSlot, + ILinkRouting, INodeFlags, INodeInputSlot, INodeOutputSlot, @@ -1153,10 +1154,11 @@ export class LGraphNode } /** - * Returns the link info in the connection of an input slot - * @returns object or null + * Returns the link info in the connection of an input slot. + * 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 (slot < this.inputs.length) { diff --git a/src/lib/litegraph/src/LLink.ts b/src/lib/litegraph/src/LLink.ts index 6ca8832a1..af7dab2d6 100644 --- a/src/lib/litegraph/src/LLink.ts +++ b/src/lib/litegraph/src/LLink.ts @@ -11,6 +11,7 @@ import type { LGraphNode, NodeId } from './LGraphNode' import type { Reroute, RerouteId } from './Reroute' import type { CanvasColour, + ILinkRouting, INodeInputSlot, INodeOutputSlot, ISlotType, @@ -89,7 +90,9 @@ type BasicReadonlyNetwork = Pick< > // this is the class in charge of storing link information -export class LLink implements LinkSegment, Serialisable { +export class LLink + implements LinkSegment, Serialisable, ILinkRouting +{ static _drawDebug = false /** Link ID */ diff --git a/src/lib/litegraph/src/interfaces.ts b/src/lib/litegraph/src/interfaces.ts index 0983770a1..4e5dbbdc7 100644 --- a/src/lib/litegraph/src/interfaces.ts +++ b/src/lib/litegraph/src/interfaces.ts @@ -184,6 +184,21 @@ export interface ItemLocator { ): 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. */ export interface LinkSegment { /** Link / reroute ID */ diff --git a/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts b/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts index 9e9454a81..fdb1e6547 100644 --- a/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts +++ b/src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts @@ -9,7 +9,11 @@ import type { CallbackReturn, ISlotType } 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 { SubgraphNode } from './SubgraphNode' @@ -289,7 +293,7 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode { if (node.isVirtualNode) { const virtualLink = this.node.getInputLink(slot) - if (virtualLink) { + if (virtualLink instanceof LLink) { const { inputNode } = virtualLink.resolve(this.graph) if (!inputNode) throw new InvalidLinkError(