Implement creating inputs by dragging link to widget (#1021)

* Implement creating inputs by dragging link to widget

* Update litegraph

* Add playwright test

* Update test expectations [skip ci]

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
pythongosssss
2024-10-02 01:53:38 +09:00
committed by GitHub
parent c42222cf0d
commit a2bd2a9bae
9 changed files with 247 additions and 10 deletions

View File

@@ -930,7 +930,8 @@ export class ComfyPage {
return this.getNodeRefById(id)
}
}
class NodeSlotReference {
export class NodeSlotReference {
constructor(
readonly type: 'input' | 'output',
readonly index: number,
@@ -980,7 +981,37 @@ class NodeSlotReference {
)
}
}
class NodeReference {
export class NodeWidgetReference {
constructor(
readonly index: number,
readonly node: NodeReference
) {}
async getPosition(): Promise<Position> {
const pos: [number, number] = await this.node.comfyPage.page.evaluate(
([id, index]) => {
const node = window['app'].graph.getNodeById(id)
if (!node) throw new Error(`Node ${id} not found.`)
const widget = node.widgets[index]
if (!widget) throw new Error(`Widget ${index} not found.`)
const [x, y, w, h] = node.getBounding()
return window['app'].canvas.ds.convertOffsetToCanvas([
x + w / 2,
y + window['LiteGraph']['NODE_TITLE_HEIGHT'] + widget.last_y + 1
])
},
[this.node.id, this.index] as const
)
return {
x: pos[0],
y: pos[1]
}
}
}
export class NodeReference {
constructor(
readonly id: NodeId,
readonly comfyPage: ComfyPage
@@ -1026,6 +1057,9 @@ class NodeReference {
async getInput(index: number) {
return new NodeSlotReference('input', index, this)
}
async getWidget(index: number) {
return new NodeWidgetReference(index, this)
}
async click(position: 'title', options?: Parameters<Page['click']>[1]) {
const nodePos = await this.getPosition()
const nodeSize = await this.getSize()
@@ -1043,6 +1077,19 @@ class NodeReference {
})
await this.comfyPage.nextFrame()
}
async connectWidget(
originSlotIndex: number,
targetNode: NodeReference,
targetWidgetIndex: number
) {
const originSlot = await this.getOutput(originSlotIndex)
const targetWidget = await targetNode.getWidget(targetWidgetIndex)
await this.comfyPage.dragAndDrop(
await originSlot.getPosition(),
await targetWidget.getPosition()
)
return originSlot
}
async connectOutput(
originSlotIndex: number,
targetNode: NodeReference,

View File

@@ -0,0 +1,104 @@
{
"last_node_id": 2,
"last_link_id": 1,
"nodes": [
{
"id": 2,
"type": "KSampler",
"pos": {
"0": 304.3653259277344,
"1": 42.15586471557617
},
"size": [
315,
262
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 1,
"type": "PrimitiveNode",
"pos": {
"0": 14,
"1": 43
},
"size": [
203.1999969482422,
40.368401303242536
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "connect to widget input",
"type": "*",
"links": [],
"slot_index": 0
}
],
"properties": {
"Run widget replace on values": false
},
"widgets_values": []
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

View File

@@ -1,9 +1,22 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './ComfyPage'
import { type NodeReference, comfyPageFixture as test } from './ComfyPage'
test.describe('Primitive Node', () => {
test('Can load with correct size', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('primitive_node')
await expect(comfyPage.canvas).toHaveScreenshot('primitive_node.png')
})
// When link is dropped on widget, it should automatically convert the widget
// to input.
test('Can connect to widget', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('primitive_node_unconnected')
const primitiveNode: NodeReference = await comfyPage.getNodeRefById(1)
const ksamplerNode: NodeReference = await comfyPage.getNodeRefById(2)
// Connect the output of the primitive node to the input of first widget of the ksampler node
await primitiveNode.connectWidget(0, ksamplerNode, 0)
await expect(comfyPage.canvas).toHaveScreenshot(
'primitive_node_connected.png'
)
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB