Fix group node naming compatibility (#969)

* Convery legacy group node names in workflow

* Add Playwright test

* Remove hardcoded strings
This commit is contained in:
bymyself
2024-09-24 23:25:08 -07:00
committed by GitHub
parent 941f71faea
commit 84fc0e9205
4 changed files with 302 additions and 16 deletions

View File

@@ -0,0 +1,252 @@
{
"last_node_id": 15,
"last_link_id": 9,
"nodes": [
{
"id": 15,
"type": "workflow/hello",
"pos": {
"0": 566,
"1": 316
},
"size": {
"0": 468.5999755859375,
"1": 582
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null,
"label": "model"
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null,
"label": "positive"
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null,
"label": "negative"
},
{
"name": "latent_image",
"type": "LATENT",
"link": null,
"label": "latent_image"
},
{
"name": "KSampler model",
"type": "MODEL",
"link": null,
"label": "KSampler model"
},
{
"name": "KSampler positive",
"type": "CONDITIONING",
"link": null,
"label": "KSampler positive"
},
{
"name": "KSampler negative",
"type": "CONDITIONING",
"link": null,
"label": "KSampler negative"
},
{
"name": "KSampler latent_image",
"type": "LATENT",
"link": null,
"label": "KSampler latent_image"
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null,
"shape": 3,
"label": "LATENT"
},
{
"name": "KSampler LATENT",
"type": "LATENT",
"links": null,
"shape": 3,
"label": "KSampler LATENT"
}
],
"properties": {
"Node name for S&R": "workflow/hello"
},
"widgets_values": [
"enable",
0,
"randomize",
20,
8,
"euler",
"normal",
0,
10000,
"disable",
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"groupNodes": {
"hello": {
"nodes": [
{
"id": -1,
"type": "KSamplerAdvanced",
"pos": {
"0": 351.3332824707031,
"1": 577.3333129882812
},
"size": {
"0": 315,
"1": 334
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null,
"label": "model"
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null,
"label": "positive"
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null,
"label": "negative"
},
{
"name": "latent_image",
"type": "LATENT",
"link": null,
"label": "latent_image"
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null,
"shape": 3,
"label": "LATENT"
}
],
"properties": {
"Node name for S&R": "KSamplerAdvanced"
},
"widgets_values": [
"enable",
0,
"randomize",
20,
8,
"euler",
"normal",
0,
10000,
"disable"
],
"index": 0
},
{
"id": -1,
"type": "KSampler",
"pos": {
"0": 636,
"1": 427
},
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null,
"label": "model"
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null,
"label": "positive"
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null,
"label": "negative"
},
{
"name": "latent_image",
"type": "LATENT",
"link": null,
"label": "latent_image"
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null,
"shape": 3,
"label": "LATENT"
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
],
"index": 1
}
],
"links": [],
"external": []
}
}
},
"version": 0.4
}

View File

@@ -140,4 +140,12 @@ test.describe('Group Node', () => {
// Ensure the link is still present // Ensure the link is still present
expect(await input.getLinkCount()).toBe(1) expect(await input.getLinkCount()).toBe(1)
}) })
test('Loads from a workflow using the legacy path separator ("/")', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('legacy_group_node')
expect(await comfyPage.getGraphNodesCount()).toBe(1)
expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible()
})
}) })

View File

@@ -5,9 +5,15 @@ import { ManageGroupDialog } from './groupNodeManage'
import type { LGraphNode } from '@comfyorg/litegraph' import type { LGraphNode } from '@comfyorg/litegraph'
import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph' import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph'
import { useNodeDefStore } from '@/stores/nodeDefStore' import { useNodeDefStore } from '@/stores/nodeDefStore'
import { ComfyNode, ComfyWorkflowJSON } from '@/types/comfyWorkflow'
const GROUP = Symbol() const GROUP = Symbol()
// v1 Prefix + Separator: workflow/
// v2 Prefix + Separator: workflow> (ComfyUI_frontend v1.2.63)
const PREFIX = 'workflow'
const SEPARATOR = '>'
const Workflow = { const Workflow = {
InUse: { InUse: {
Free: 0, Free: 0,
@@ -15,7 +21,7 @@ const Workflow = {
InWorkflow: 2 InWorkflow: 2
}, },
isInUseGroupNode(name) { isInUseGroupNode(name) {
const id = `workflow>${name}` const id = `${PREFIX}${SEPARATOR}${name}`
// Check if lready registered/in use in this workflow // Check if lready registered/in use in this workflow
if (app.graph.extra?.groupNodes?.[name]) { if (app.graph.extra?.groupNodes?.[name]) {
if (app.graph.nodes.find((n) => n.type === id)) { if (app.graph.nodes.find((n) => n.type === id)) {
@@ -185,15 +191,15 @@ export class GroupNodeConfig {
this.outputVisibility = [] this.outputVisibility = []
} }
async registerType(source = 'workflow') { async registerType(source = PREFIX) {
this.nodeDef = { this.nodeDef = {
output: [], output: [],
output_name: [], output_name: [],
output_is_list: [], output_is_list: [],
output_is_hidden: [], output_is_hidden: [],
name: source + '>' + this.name, name: source + SEPARATOR + this.name,
display_name: this.name, display_name: this.name,
category: 'group nodes' + ('>' + source), category: 'group nodes' + (SEPARATOR + source),
input: { required: {} }, input: { required: {} },
description: `Group node combining ${this.nodeData.nodes description: `Group node combining ${this.nodeData.nodes
.map((n) => n.type) .map((n) => n.type)
@@ -216,7 +222,7 @@ export class GroupNodeConfig {
p() p()
} }
this.#convertedToProcess = null this.#convertedToProcess = null
await app.registerNodeDef('workflow>' + this.name, this.nodeDef) await app.registerNodeDef(`${PREFIX}${SEPARATOR}` + this.name, this.nodeDef)
useNodeDefStore().addNodeDef(this.nodeDef) useNodeDefStore().addNodeDef(this.nodeDef)
} }
@@ -647,7 +653,7 @@ export class GroupNodeConfig {
app.clean = function () { app.clean = function () {
for (const g in groupNodes) { for (const g in groupNodes) {
try { try {
LiteGraph.unregisterNodeType('workflow/' + g) LiteGraph.unregisterNodeType(`${PREFIX}${SEPARATOR}` + g)
} catch (error) {} } catch (error) {}
} }
app.clean = clean app.clean = clean
@@ -662,11 +668,11 @@ export class GroupNodeConfig {
if (!(n.type in LiteGraph.registered_node_types)) { if (!(n.type in LiteGraph.registered_node_types)) {
missingNodeTypes.push({ missingNodeTypes.push({
type: n.type, type: n.type,
hint: ` (In group node 'workflow/${g}')` hint: ` (In group node '${PREFIX}${SEPARATOR}${g}')`
}) })
missingNodeTypes.push({ missingNodeTypes.push({
type: 'workflow/' + g, type: `${PREFIX}${SEPARATOR}` + g,
action: { action: {
text: 'Remove from workflow', text: 'Remove from workflow',
callback: (e) => { callback: (e) => {
@@ -1380,7 +1386,7 @@ export class GroupNodeHandler {
const config = new GroupNodeConfig(name, nodeData) const config = new GroupNodeConfig(name, nodeData)
await config.registerType() await config.registerType()
const groupNode = LiteGraph.createNode(`workflow>${name}`) const groupNode = LiteGraph.createNode(`${PREFIX}${SEPARATOR}${name}`)
// Reuse the existing nodes for this instance // Reuse the existing nodes for this instance
groupNode.setInnerNodes(builder.nodes) groupNode.setInnerNodes(builder.nodes)
groupNode[GROUP].populateWidgets() groupNode[GROUP].populateWidgets()
@@ -1444,6 +1450,14 @@ function addConvertToGroupOptions() {
} }
} }
const replaceLegacySeparators = (nodes: ComfyNode[]): void => {
for (const node of nodes) {
if (typeof node.type === 'string' && node.type.startsWith('workflow/')) {
node.type = node.type.replace(/^workflow\//, `${PREFIX}${SEPARATOR}`)
}
}
}
const id = 'Comfy.GroupNode' const id = 'Comfy.GroupNode'
let globalDefs let globalDefs
const ext = { const ext = {
@@ -1451,9 +1465,13 @@ const ext = {
setup() { setup() {
addConvertToGroupOptions() addConvertToGroupOptions()
}, },
async beforeConfigureGraph(graphData, missingNodeTypes) { async beforeConfigureGraph(
graphData: ComfyWorkflowJSON,
missingNodeTypes: string[]
) {
const nodes = graphData?.extra?.groupNodes const nodes = graphData?.extra?.groupNodes
if (nodes) { if (nodes) {
replaceLegacySeparators(graphData.nodes)
await GroupNodeConfig.registerFromWorkflow(nodes, missingNodeTypes) await GroupNodeConfig.registerFromWorkflow(nodes, missingNodeTypes)
} }
}, },

View File

@@ -10,6 +10,8 @@ import {
} from '@comfyorg/litegraph' } from '@comfyorg/litegraph'
const ORDER: symbol = Symbol() const ORDER: symbol = Symbol()
const PREFIX = 'workflow'
const SEPARATOR = '>'
function merge(target, source) { function merge(target, source) {
if (typeof target === 'object' && typeof source === 'object') { if (typeof target === 'object' && typeof source === 'object') {
@@ -102,7 +104,9 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
getGroupData() { getGroupData() {
this.groupNodeType = this.groupNodeType =
LiteGraph.registered_node_types['workflow>' + this.selectedGroup] LiteGraph.registered_node_types[
`${PREFIX}${SEPARATOR}` + this.selectedGroup
]
this.groupNodeDef = this.groupNodeType.nodeData this.groupNodeDef = this.groupNodeType.nodeData
this.groupData = GroupNodeHandler.getGroupData(this.groupNodeType) this.groupData = GroupNodeHandler.getGroupData(this.groupNodeType)
} }
@@ -367,7 +371,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
groupNodes.map((g) => groupNodes.map((g) =>
$el('option', { $el('option', {
textContent: g, textContent: g,
selected: 'workflow>' + g === type, selected: `${PREFIX}${SEPARATOR}` + g === type,
value: g value: g
}) })
) )
@@ -389,7 +393,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
{ {
onclick: (e) => { onclick: (e) => {
const node = app.graph.nodes.find( const node = app.graph.nodes.find(
(n) => n.type === 'workflow>' + this.selectedGroup (n) => n.type === `${PREFIX}${SEPARATOR}` + this.selectedGroup
) )
if (node) { if (node) {
alert( alert(
@@ -403,7 +407,9 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
) )
) { ) {
delete app.graph.extra.groupNodes[this.selectedGroup] delete app.graph.extra.groupNodes[this.selectedGroup]
LiteGraph.unregisterNodeType('workflow>' + this.selectedGroup) LiteGraph.unregisterNodeType(
`${PREFIX}${SEPARATOR}` + this.selectedGroup
)
} }
this.show() this.show()
} }
@@ -476,7 +482,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
}, {}) }, {})
} }
const nodes = nodesByType['workflow>' + g] const nodes = nodesByType[`${PREFIX}${SEPARATOR}` + g]
if (nodes) recreateNodes.push(...nodes) if (nodes) recreateNodes.push(...nodes)
} }
@@ -503,7 +509,9 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
this.element.replaceChildren(outer) this.element.replaceChildren(outer)
this.changeGroup( this.changeGroup(
type ? groupNodes.find((g) => 'workflow>' + g === type) : groupNodes[0] type
? groupNodes.find((g) => `${PREFIX}${SEPARATOR}` + g === type)
: groupNodes[0]
) )
this.element.showModal() this.element.showModal()