Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions browser_tests/fixtures/VueNodeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*/
import type { Locator, Page } from '@playwright/test'

import type { NodeId } from '@/lib/litegraph/src/LGraphNode'

export class VueNodeHelpers {
constructor(private page: Page) {}

Expand Down Expand Up @@ -39,7 +41,7 @@ export class VueNodeHelpers {
/**
* Get all Vue node IDs currently in the DOM
*/
async getNodeIds(): Promise<string[]> {
async getNodeIds(): Promise<NodeId[]> {
return await this.nodes.evaluateAll((nodes) =>
nodes
.map((n) => n.getAttribute('data-node-id'))
Expand All @@ -50,14 +52,14 @@ export class VueNodeHelpers {
/**
* Select a specific Vue node by ID
*/
async selectNode(nodeId: string): Promise<void> {
async selectNode(nodeId: nodeId): Promise<void> {
await this.page.locator(`[data-node-id="${nodeId}"]`).click()
}

/**
* Select multiple Vue nodes by IDs using Ctrl+click
*/
async selectNodes(nodeIds: string[]): Promise<void> {
async selectNodes(nodeIds: NodeId[]): Promise<void> {
if (nodeIds.length === 0) return

// Select first node normally
Expand Down
4 changes: 3 additions & 1 deletion browser_tests/tests/chatHistory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Page } from '@playwright/test'
import { expect } from '@playwright/test'

import type { NodeId } from '@/lib/litegraph/src/LGraphNode'

import { comfyPageFixture as test } from '../fixtures/ComfyPage'

interface ChatHistoryEntry {
Expand Down Expand Up @@ -33,7 +35,7 @@ async function renderChatHistory(page: Page, history: ChatHistoryEntry[]) {
}

test.describe('Chat History Widget', () => {
let nodeId: string
let nodeId: NodeId

test.beforeEach(async ({ comfyPage }) => {
nodeId = await renderChatHistory(comfyPage.page, [
Expand Down
2 changes: 1 addition & 1 deletion src/lib/litegraph/src/LGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ export class LGraph
const S: LGraphNode[] = []
const M: Dictionary<LGraphNode> = {}
// to avoid repeating links
const visited_links: Record<NodeId, boolean> = {}
const visited_links: Record<LinkId, boolean> = {}
const remaining_links: Record<NodeId, number> = {}

// search for the nodes without inputs (starting nodes)
Expand Down
16 changes: 8 additions & 8 deletions src/lib/litegraph/src/LGraphCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6168,14 +6168,14 @@ export class LGraphCanvas

case 'Delete': {
// segment can be a Reroute object, in which case segment.id is the reroute id
const linkId =
segment instanceof Reroute
? segment.linkIds.values().next().value
: segment.id
if (linkId !== undefined) {
graph.removeLink(linkId)
// Clean up layout store
layoutStore.deleteLinkLayout(linkId)
const linkIds =
segment instanceof Reroute ? segment.linkIds : [segment.id]
for (const linkId of linkIds) {
if (linkId !== undefined) {
graph.removeLink(linkId)
// Clean up layout store
layoutStore.deleteLinkLayout(linkId)
}
}
break
}
Expand Down
5 changes: 3 additions & 2 deletions src/lib/litegraph/src/LGraphNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import type {
ReadOnlyPoint,
ReadOnlyRect,
Rect,
Size
Size,
UniqueId
} from './interfaces'
import {
type LGraphNodeConstructor,
Expand Down Expand Up @@ -90,7 +91,7 @@ import { type WidgetTypeMap, toConcreteWidget } from './widgets/widgetMap'

// #region Types

export type NodeId = number | string
export type NodeId = UniqueId<number | string, 'NodeId'>

export type NodeProperty = string | number | boolean | object

Expand Down
8 changes: 5 additions & 3 deletions src/lib/litegraph/src/LLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import type { LGraphNode, NodeId } from './LGraphNode'
import type { Reroute, RerouteId } from './Reroute'
import type {
CanvasColour,
ILinkSegment,
INodeInputSlot,
INodeOutputSlot,
ISlotType,
LinkNetwork,
LinkSegment,
ReadonlyLinkNetwork
ReadonlyLinkNetwork,
UniqueId
} from './interfaces'
import type {
Serialisable,
Expand All @@ -24,7 +26,7 @@ import type {

const layoutMutations = useLayoutMutations()

export type LinkId = number
export type LinkId = UniqueId<number, 'LinkId'>

export type SerialisedLLinkArray = [
id: LinkId,
Expand Down Expand Up @@ -90,7 +92,7 @@ type BasicReadonlyNetwork = Pick<
>

// this is the class in charge of storing link information
export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
export class LLink implements ILinkSegment, Serialisable<SerialisableLLink> {
static _drawDebug = false

/** Link ID */
Expand Down
9 changes: 5 additions & 4 deletions src/lib/litegraph/src/Reroute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@ import type { LGraphNode, NodeId } from './LGraphNode'
import { LLink, type LinkId } from './LLink'
import type {
CanvasColour,
ILinkSegment,
INodeInputSlot,
INodeOutputSlot,
LinkNetwork,
LinkSegment,
Point,
Positionable,
ReadOnlyRect,
ReadonlyLinkNetwork
ReadonlyLinkNetwork,
UniqueId
} from './interfaces'
import { distance, isPointInRect } from './measure'
import type { Serialisable, SerialisableReroute } from './types/serialisation'

const layoutMutations = useLayoutMutations()

export type RerouteId = number
export type RerouteId = UniqueId<number, 'RerouteId'>

/** The input or output slot that an incomplete reroute link is connected to. */
export interface FloatingRerouteSlot {
Expand All @@ -36,7 +37,7 @@ export interface FloatingRerouteSlot {
* and a `WeakRef` to a {@link LinkNetwork} to resolve them.
*/
export class Reroute
implements Positionable, LinkSegment, Serialisable<SerialisableReroute>
implements Positionable, ILinkSegment, Serialisable<SerialisableReroute>
{
static radius: number = 10
/** Maximum distance from reroutes to their bezier curve control points. */
Expand Down
7 changes: 6 additions & 1 deletion src/lib/litegraph/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import type { SubgraphOutputNode } from './subgraph/SubgraphOutputNode'
import type { LinkDirection, RenderShape } from './types/globalEnums'
import type { IBaseWidget } from './types/widgets'

declare const __brand: unique symbol
export type UniqueId<T, B> = T & { [__brand]?: B }

export type Dictionary<T> = { [key: string]: T }

/** Allows all properties to be null. The same as `Partial<T>`, but adds null instead of undefined. */
Expand Down Expand Up @@ -183,8 +186,10 @@ export interface ItemLocator {
): SubgraphInputNode | SubgraphOutputNode | undefined
}

export type LinkSegment = Reroute | LLink

/** Contains a cached 2D canvas path and a centre point, with an optional forward angle. */
export interface LinkSegment {
export interface ILinkSegment {
/** Link / reroute ID */
readonly id: LinkId | RerouteId
/** The {@link id} of the reroute that this segment starts from (output side), otherwise `undefined`. */
Expand Down
24 changes: 11 additions & 13 deletions src/platform/workflow/management/stores/workflowStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,14 @@ interface WorkflowStore {
activeSubgraph: Subgraph | undefined
/** Updates the {@link subgraphNamePath} and {@link isSubgraphActive} values. */
updateActiveGraph: () => void
executionIdToCurrentId: (id: string) => any
executionIdToCurrentId: (id: NodeExecutionId) => any
nodeIdToNodeLocatorId: (nodeId: NodeId, subgraph?: Subgraph) => NodeLocatorId
nodeExecutionIdToNodeLocatorId: (
nodeExecutionId: NodeExecutionId | string
nodeExecutionId: NodeExecutionId
) => NodeLocatorId | null
nodeLocatorIdToNodeId: (locatorId: NodeLocatorId | string) => NodeId | null
nodeLocatorIdToNodeId: (locatorId: NodeLocatorId) => NodeId | null
nodeLocatorIdToNodeExecutionId: (
locatorId: NodeLocatorId | string,
locatorId: NodeLocatorId,
targetSubgraph?: Subgraph
) => NodeExecutionId | null
}
Expand Down Expand Up @@ -518,14 +518,14 @@ export const useWorkflowStore = defineStore('workflow', () => {
isSubgraphActive.value = isSubgraph(subgraph)
}

const subgraphNodeIdToSubgraph = (id: string, graph: LGraph | Subgraph) => {
const subgraphNodeIdToSubgraph = (id: NodeId, graph: LGraph | Subgraph) => {
const node = graph.getNodeById(id)
if (node?.isSubgraphNode()) return node.subgraph
}

const getSubgraphsFromInstanceIds = (
currentGraph: LGraph | Subgraph,
subgraphNodeIds: string[],
subgraphNodeIds: NodeId[],
subgraphs: Subgraph[] = []
): Subgraph[] => {
const currentPart = subgraphNodeIds.shift()
Expand All @@ -538,7 +538,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
return getSubgraphsFromInstanceIds(subgraph, subgraphNodeIds, subgraphs)
}

const executionIdToCurrentId = (id: string) => {
const executionIdToCurrentId = (id: NodeExecutionId) => {
const subgraph = activeSubgraph.value

// Short-circuit: ID belongs to the parent workflow / no active subgraph
Expand Down Expand Up @@ -588,11 +588,11 @@ export const useWorkflowStore = defineStore('workflow', () => {
* @returns The NodeLocatorId or null if conversion fails
*/
const nodeExecutionIdToNodeLocatorId = (
nodeExecutionId: NodeExecutionId | string
nodeExecutionId: NodeExecutionId
): NodeLocatorId | null => {
// Handle simple node IDs (root graph - no colons)
if (!nodeExecutionId.includes(':')) {
return nodeExecutionId
return nodeExecutionId as NodeLocatorId
}

const parts = parseNodeExecutionId(nodeExecutionId)
Expand Down Expand Up @@ -623,9 +623,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
* @param locatorId The NodeLocatorId
* @returns The local node ID or null if invalid
*/
const nodeLocatorIdToNodeId = (
locatorId: NodeLocatorId | string
): NodeId | null => {
const nodeLocatorIdToNodeId = (locatorId: NodeLocatorId): NodeId | null => {
const parsed = parseNodeLocatorId(locatorId)
return parsed?.localNodeId ?? null
}
Expand All @@ -637,7 +635,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
* @returns The execution ID or null if the node is not accessible from the target context
*/
const nodeLocatorIdToNodeExecutionId = (
locatorId: NodeLocatorId | string,
locatorId: NodeLocatorId,
targetSubgraph?: Subgraph
): NodeExecutionId | null => {
const parsed = parseNodeLocatorId(locatorId)
Expand Down
14 changes: 8 additions & 6 deletions src/services/litegraphService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,9 @@ export const useLitegraphService = () => {
*/
#setupStrokeStyles() {
this.strokeStyles['running'] = function (this: LGraphNode) {
const nodeId = String(this.id)
const nodeLocatorId = useWorkflowStore().nodeIdToNodeLocatorId(nodeId)
const nodeLocatorId = useWorkflowStore().nodeIdToNodeLocatorId(
this.id
)
const state =
useExecutionStore().nodeLocationProgressStates[nodeLocatorId]?.state
if (state === 'running') {
Expand Down Expand Up @@ -376,7 +377,7 @@ export const useLitegraphService = () => {
node.title = nodeDef.display_name || nodeDef.name
}

async function registerNodeDef(nodeId: string, nodeDefV1: ComfyNodeDefV1) {
async function registerNodeDef(type: string, nodeDefV1: ComfyNodeDefV1) {
const node = class ComfyNode extends LGraphNode {
static comfyClass: string
static override title: string
Expand Down Expand Up @@ -416,8 +417,9 @@ export const useLitegraphService = () => {
*/
#setupStrokeStyles() {
this.strokeStyles['running'] = function (this: LGraphNode) {
const nodeId = String(this.id)
const nodeLocatorId = useWorkflowStore().nodeIdToNodeLocatorId(nodeId)
const nodeLocatorId = useWorkflowStore().nodeIdToNodeLocatorId(
this.id
)
const state =
useExecutionStore().nodeLocationProgressStates[nodeLocatorId]?.state
if (state === 'running') {
Expand Down Expand Up @@ -649,7 +651,7 @@ export const useLitegraphService = () => {

const nodeDef = new ComfyNodeDefImpl(nodeDefV1)
node.nodeData = nodeDef
LiteGraph.registerNodeType(nodeId, node)
LiteGraph.registerNodeType(type, node)
// Note: Do not following assignments before `LiteGraph.registerNodeType`
// because `registerNodeType` will overwrite the assignments.
node.category = nodeDef.category
Expand Down
8 changes: 5 additions & 3 deletions src/stores/executionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import type {
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
import type { NodeLocatorId } from '@/types/nodeIdentification'
import type { NodeExecutionId, NodeLocatorId } from '@/types/nodeIdentification'
import { createNodeLocatorId } from '@/types/nodeIdentification'

interface QueuedPrompt {
Expand Down Expand Up @@ -78,8 +78,10 @@ function getSubgraphsFromInstanceIds(
* @param nodeId The node ID from execution context (could be execution ID)
* @returns The NodeLocatorId
*/
function executionIdToNodeLocatorId(nodeId: string | number): NodeLocatorId {
const nodeIdStr = String(nodeId)
function executionIdToNodeLocatorId(
executionId: NodeExecutionId
): NodeLocatorId {
const nodeIdStr = String(executionId)

if (!nodeIdStr.includes(':')) {
// It's a top-level node ID
Expand Down
8 changes: 5 additions & 3 deletions src/types/nodeIdentification.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { UniqueId } from '@/lib/litegraph/src/interfaces'
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'

/**
Expand All @@ -15,7 +16,7 @@ import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSche
* Unlike execution IDs which change based on the instance path,
* NodeLocatorId remains the same for all instances of a particular node.
*/
export type NodeLocatorId = string
export type NodeLocatorId = UniqueId<string, 'NodeLocatorId'>

/**
* An execution identifier representing a node's position in nested subgraphs.
Expand All @@ -24,7 +25,8 @@ export type NodeLocatorId = string
* Format: Colon-separated path of node IDs
* Example: "123:456:789" (node 789 in subgraph 456 in subgraph 123)
*/
export type NodeExecutionId = string
declare const __executionIdBrand: unique symbol
export type NodeExecutionId = UniqueId<string, 'NodeExecutionId'>

/**
* Type guard to check if a value is a NodeLocatorId
Expand Down Expand Up @@ -105,7 +107,7 @@ export function createNodeLocatorId(
* @param id The NodeExecutionId to parse
* @returns Array of node IDs from root to target, or null if not an execution ID
*/
export function parseNodeExecutionId(id: string): NodeId[] | null {
export function parseNodeExecutionId(id: NodeExecutionId): NodeId[] | null {
if (!isNodeExecutionId(id)) return null

return id
Expand Down
Loading