From a8f6bea371082351939f5cf4713bfb4dcdb787e0 Mon Sep 17 00:00:00 2001 From: AustinMroz Date: Sat, 6 Dec 2025 12:00:07 -0800 Subject: [PATCH] Color links as common type (#7211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the color of a link would simply use the type of the target slot and fallback to the type of the origin slot. When a connection is made to a node that accepts the any type ('*'), the link has the green color of an unknown type. Instead, when a connection is made, the type of a link is now calculated as the greatest common type of the source and destination. This means that connections to reroutes are correctly colored. | Before | After | | ------ | ----- | | before| after| The code for calculating common types already exists, it has simply been moved into litegraph and given a more descriptive name. Resolves #7196 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7211-Color-links-as-common-type-2c16d73d365081188460f6b5973db962) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/core/graph/widgets/dynamicWidgets.ts | 34 +++--------------------- src/lib/litegraph/src/LGraphNode.ts | 7 +++-- src/lib/litegraph/src/utils/type.ts | 32 +++++++++++++++++++++- src/utils/typeGuardUtil.ts | 4 --- 4 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/core/graph/widgets/dynamicWidgets.ts b/src/core/graph/widgets/dynamicWidgets.ts index 18a317ee6..9319fdd8d 100644 --- a/src/core/graph/widgets/dynamicWidgets.ts +++ b/src/core/graph/widgets/dynamicWidgets.ts @@ -1,5 +1,3 @@ -import { without } from 'es-toolkit' - import { useChainCallback } from '@/composables/functional/useChainCallback' import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums' import type { @@ -10,6 +8,7 @@ import type { import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import { LiteGraph } from '@/lib/litegraph/src/litegraph' import type { LLink } from '@/lib/litegraph/src/LLink' +import { commonType } from '@/lib/litegraph/src/utils/type' import { transformInputSpecV1ToV2 } from '@/schemas/nodeDef/migration' import type { ComboInputSpec, InputSpec } from '@/schemas/nodeDefSchema' import type { InputSpec as InputSpecV2 } from '@/schemas/nodeDef/nodeDefSchemaV2' @@ -20,7 +19,6 @@ import { import { useLitegraphService } from '@/services/litegraphService' import { app } from '@/scripts/app' import type { ComfyApp } from '@/scripts/app' -import { isStrings } from '@/utils/typeGuardUtil' const INLINE_INPUTS = false @@ -244,30 +242,6 @@ function changeOutputType( } } -function combineTypes(...types: ISlotType[]): ISlotType | undefined { - if (!isStrings(types)) return undefined - - const withoutWildcards = without(types, '*') - if (withoutWildcards.length === 0) return '*' - - const typeLists: string[][] = withoutWildcards.map((type) => type.split(',')) - - const combinedTypes = intersection(...typeLists) - if (combinedTypes.length === 0) return undefined - - return combinedTypes.join(',') -} - -function intersection(...sets: string[][]): string[] { - const itemCounts: Record = {} - for (const set of sets) - for (const item of new Set(set)) - itemCounts[item] = (itemCounts[item] ?? 0) + 1 - return Object.entries(itemCounts) - .filter(([, count]) => count == sets.length) - .map(([key]) => key) -} - function withComfyMatchType(node: LGraphNode): asserts node is MatchTypeNode { if (node.comfyMatchType) return node.comfyMatchType = {} @@ -290,8 +264,6 @@ function withComfyMatchType(node: LGraphNode): asserts node is MatchTypeNode { if (!matchGroup) return if (iscon && linf) { const { output, subgraphInput } = linf.resolve(this.graph) - //TODO: fix this bug globally. A link type (and therefore color) - //should be the combinedType of origin and target type const connectingType = (output ?? subgraphInput)?.type if (connectingType) linf.type = connectingType } @@ -316,14 +288,14 @@ function withComfyMatchType(node: LGraphNode): asserts node is MatchTypeNode { ...connectedTypes.slice(0, idx), ...connectedTypes.slice(idx + 1) ] - const combinedType = combineTypes( + const combinedType = commonType( ...otherConnected, matchGroup[input.name] ) if (!combinedType) throw new Error('invalid connection') input.type = combinedType }) - const outputType = combineTypes(...connectedTypes) + const outputType = commonType(...connectedTypes) if (!outputType) throw new Error('invalid connection') this.outputs.forEach((output, idx) => { if (!(outputGroups?.[idx] == matchKey)) return diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index 17fce3edd..88f113a4c 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -10,6 +10,7 @@ import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMuta import { LayoutSource } from '@/renderer/core/layout/types' import { adjustColor } from '@/utils/colorUtil' import type { ColorAdjustOptions } from '@/utils/colorUtil' +import { commonType, toClass } from '@/lib/litegraph/src/utils/type' import { SUBGRAPH_OUTPUT_ID } from '@/lib/litegraph/src/constants' import type { DragAndScale } from './DragAndScale' @@ -84,7 +85,6 @@ import { findFreeSlotOfType } from './utils/collections' import { warnDeprecated } from './utils/feedback' import { distributeSpace } from './utils/spaceDistribution' import { truncateText } from './utils/textUtils' -import { toClass } from './utils/type' import { BaseWidget } from './widgets/BaseWidget' import { toConcreteWidget } from './widgets/widgetMap' import type { WidgetTypeMap } from './widgets/widgetMap' @@ -2832,9 +2832,12 @@ export class LGraphNode inputNode.disconnectInput(inputIndex, true) } + const maybeCommonType = + input.type && output.type && commonType(input.type, output.type) + const link = new LLink( ++graph.state.lastLinkId, - input.type || output.type, + maybeCommonType || input.type || output.type, this.id, outputIndex, inputNode.id, diff --git a/src/lib/litegraph/src/utils/type.ts b/src/lib/litegraph/src/utils/type.ts index 57c45872a..84891fb7f 100644 --- a/src/lib/litegraph/src/utils/type.ts +++ b/src/lib/litegraph/src/utils/type.ts @@ -1,4 +1,6 @@ -import type { IColorable } from '@/lib/litegraph/src/interfaces' +import { without } from 'es-toolkit' + +import type { IColorable, ISlotType } from '@/lib/litegraph/src/interfaces' /** * Converts a plain object to a class instance if it is not already an instance of the class. @@ -26,3 +28,31 @@ export function isColorable(obj: unknown): obj is IColorable { 'getColorOption' in obj ) } + +export function commonType(...types: ISlotType[]): ISlotType | undefined { + if (!isStrings(types)) return undefined + + const withoutWildcards = without(types, '*') + if (withoutWildcards.length === 0) return '*' + + const typeLists: string[][] = withoutWildcards.map((type) => type.split(',')) + + const combinedTypes = intersection(...typeLists) + if (combinedTypes.length === 0) return undefined + + return combinedTypes.join(',') +} + +function intersection(...sets: string[][]): string[] { + const itemCounts: Record = {} + for (const set of sets) + for (const item of new Set(set)) + itemCounts[item] = (itemCounts[item] ?? 0) + 1 + return Object.entries(itemCounts) + .filter(([, count]) => count === sets.length) + .map(([key]) => key) +} + +function isStrings(types: unknown[]): types is string[] { + return types.every((t) => typeof t === 'string') +} diff --git a/src/utils/typeGuardUtil.ts b/src/utils/typeGuardUtil.ts index 9914d7953..d97a28374 100644 --- a/src/utils/typeGuardUtil.ts +++ b/src/utils/typeGuardUtil.ts @@ -60,7 +60,3 @@ export const isResultItemType = ( ): value is ResultItemType => { return value === 'input' || value === 'output' || value === 'temp' } - -export function isStrings(types: unknown[]): types is string[] { - return types.every((t) => typeof t === 'string') -}