Skip to content

Commit 4982482

Browse files
authored
Add support for growable inputs (#6830)
![autogrow-optional_00002](https://github.com/user-attachments/assets/79bfe703-23d7-45fb-86ce-88baa9eaf582) Also fixes connections to widget inputs created by a dynamic combo breaking on reload. Performs some refactoring to group the prior dynamic inputs code. See also, the overarching frontend PR: comfyanonymous/ComfyUI#10832 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6830-Add-support-for-growable-inputs-2b36d73d365081c484ebc251a10aa6dd) by [Unito](https://www.unito.io)
1 parent 8e006bb commit 4982482

File tree

10 files changed

+543
-202
lines changed

10 files changed

+543
-202
lines changed

src/core/graph/widgets/dynamicWidgets.ts

Lines changed: 424 additions & 22 deletions
Large diffs are not rendered by default.

src/extensions/core/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import './groupNodeManage'
1010
import './groupOptions'
1111
import './load3d'
1212
import './maskeditor'
13-
import './matchType'
1413
import './nodeTemplates'
1514
import './noteNode'
1615
import './previewAny'

src/extensions/core/matchType.ts

Lines changed: 0 additions & 155 deletions
This file was deleted.

src/lib/litegraph/src/LGraphNode.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ export class LGraphNode
415415
selected?: boolean
416416
showAdvanced?: boolean
417417

418+
declare comfyMatchType?: Record<string, Record<string, string>>
418419
declare comfyClass?: string
419420
declare isVirtualNode?: boolean
420421
applyToGraph?(extraLinks?: LLink[]): void
@@ -1651,19 +1652,6 @@ export class LGraphNode
16511652
this.onInputRemoved?.(slot, slot_info[0])
16521653
this.setDirtyCanvas(true, true)
16531654
}
1654-
spliceInputs(
1655-
startIndex: number,
1656-
deleteCount = -1,
1657-
...toAdd: INodeInputSlot[]
1658-
): INodeInputSlot[] {
1659-
if (deleteCount < 0) return this.inputs.splice(startIndex)
1660-
const ret = this.inputs.splice(startIndex, deleteCount, ...toAdd)
1661-
this.inputs.slice(startIndex).forEach((input, index) => {
1662-
const link = input.link && this.graph?.links?.get(input.link)
1663-
if (link) link.target_slot = startIndex + index
1664-
})
1665-
return ret
1666-
}
16671655

16681656
/**
16691657
* computes the minimum size of a node according to its inputs and output slots
@@ -4002,7 +3990,8 @@ export class LGraphNode
40023990
isValidTarget ||
40033991
!slot.isWidgetInputSlot ||
40043992
this.#isMouseOverWidget(this.getWidgetFromSlot(slot)) ||
4005-
slot.isConnected
3993+
slot.isConnected ||
3994+
slot.alwaysVisible
40063995
) {
40073996
ctx.globalAlpha = isValid ? editorAlpha : 0.4 * editorAlpha
40083997
slot.draw(ctx, {

src/lib/litegraph/src/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ export interface IWidgetLocator {
343343
export interface INodeInputSlot extends INodeSlot {
344344
link: LinkId | null
345345
widget?: IWidgetLocator
346+
alwaysVisible?: boolean
346347

347348
/**
348349
* Internal use only; API is not finalised and may change at any time.

src/lib/litegraph/src/node/NodeInputSlot.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
1717

1818
export class NodeInputSlot extends NodeSlot implements INodeInputSlot {
1919
link: LinkId | null
20+
alwaysVisible?: boolean
2021

2122
get isWidgetInputSlot(): boolean {
2223
return !!this.widget

src/schemas/nodeDefSchema.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ const zRemoteWidgetConfig = z.object({
1414
timeout: z.number().gte(0).optional(),
1515
max_retries: z.number().gte(0).optional()
1616
})
17-
const zWidgetTemplate = z.object({
18-
template_id: z.string(),
19-
allowed_types: z.string().optional()
20-
})
2117
const zMultiSelectOption = z.object({
2218
placeholder: z.string().optional(),
2319
chip: z.boolean().optional()
@@ -34,7 +30,6 @@ export const zBaseInputOptions = z
3430
hidden: z.boolean().optional(),
3531
advanced: z.boolean().optional(),
3632
widgetType: z.string().optional(),
37-
template: zWidgetTemplate.optional(),
3833
/** Backend-only properties. */
3934
rawLink: z.boolean().optional(),
4035
lazy: z.boolean().optional()
@@ -232,9 +227,21 @@ export const zComfyNodeDef = z.object({
232227
input_order: z.record(z.array(z.string())).optional()
233228
})
234229

230+
export const zAutogrowOptions = z.object({
231+
...zBaseInputOptions.shape,
232+
template: z.object({
233+
input: zComfyInputsSpec,
234+
names: z.array(z.string()).optional(),
235+
max: z.number().optional(),
236+
//Backend defines as mandatory with min 1, Frontend is more forgiving
237+
min: z.number().optional(),
238+
prefix: z.string().optional()
239+
})
240+
})
241+
235242
export const zDynamicComboInputSpec = z.tuple([
236243
z.literal('COMFY_DYNAMICCOMBO_V3'),
237-
zComboInputOptions.extend({
244+
zBaseInputOptions.extend({
238245
options: z.array(
239246
z.object({
240247
inputs: zComfyInputsSpec,

src/services/litegraphService.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useNodeCanvasImagePreview } from '@/composables/node/useNodeCanvasImage
77
import { useNodeImage, useNodeVideo } from '@/composables/node/useNodeImage'
88
import { addWidgetPromotionOptions } from '@/core/graph/subgraph/proxyWidgetUtils'
99
import { showSubgraphNodeDialog } from '@/core/graph/subgraph/useSubgraphNodeDialog'
10+
import { applyDynamicInputs } from '@/core/graph/widgets/dynamicWidgets'
1011
import { st, t } from '@/i18n'
1112
import {
1213
LGraphCanvas,
@@ -93,7 +94,11 @@ export const useLitegraphService = () => {
9394
const widgetConstructor = widgetStore.widgets.get(
9495
inputSpec.widgetType ?? inputSpec.type
9596
)
96-
if (widgetConstructor && !inputSpec.forceInput) return
97+
if (
98+
(widgetConstructor && !inputSpec.forceInput) ||
99+
applyDynamicInputs(node, inputSpec)
100+
)
101+
return
97102

98103
const input = node.addInput(inputName, inputSpec.type, {
99104
shape: inputSpec.isOptional ? RenderShape.HollowCircle : undefined,

src/utils/typeGuardUtil.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,7 @@ export const isResultItemType = (
6060
): value is ResultItemType => {
6161
return value === 'input' || value === 'output' || value === 'temp'
6262
}
63+
64+
export function isStrings(types: unknown[]): types is string[] {
65+
return types.every((t) => typeof t === 'string')
66+
}

0 commit comments

Comments
 (0)