mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
Improve execution logic / Fix group node execution (#4422)
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
import { LiteGraph } from '@comfyorg/litegraph'
|
import {
|
||||||
import { LGraphNode, type NodeId } from '@comfyorg/litegraph/dist/LGraphNode'
|
type ExecutableLGraphNode,
|
||||||
|
type ExecutionId,
|
||||||
|
LGraphNode,
|
||||||
|
LiteGraph,
|
||||||
|
SubgraphNode
|
||||||
|
} from '@comfyorg/litegraph'
|
||||||
|
import { type NodeId } from '@comfyorg/litegraph/dist/LGraphNode'
|
||||||
|
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import {
|
import {
|
||||||
@@ -13,6 +19,8 @@ import { useNodeDefStore } from '@/stores/nodeDefStore'
|
|||||||
import { useToastStore } from '@/stores/toastStore'
|
import { useToastStore } from '@/stores/toastStore'
|
||||||
import { useWidgetStore } from '@/stores/widgetStore'
|
import { useWidgetStore } from '@/stores/widgetStore'
|
||||||
import { ComfyExtension } from '@/types/comfy'
|
import { ComfyExtension } from '@/types/comfy'
|
||||||
|
import { ExecutableGroupNodeChildDTO } from '@/utils/executableGroupNodeChildDTO'
|
||||||
|
import { GROUP } from '@/utils/executableGroupNodeDto'
|
||||||
import { deserialiseAndCreate, serialise } from '@/utils/vintageClipboard'
|
import { deserialiseAndCreate, serialise } from '@/utils/vintageClipboard'
|
||||||
|
|
||||||
import { api } from '../../scripts/api'
|
import { api } from '../../scripts/api'
|
||||||
@@ -26,8 +34,6 @@ type GroupNodeWorkflowData = {
|
|||||||
nodes: ComfyNode[]
|
nodes: ComfyNode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const GROUP = Symbol()
|
|
||||||
|
|
||||||
// v1 Prefix + Separator: workflow/
|
// v1 Prefix + Separator: workflow/
|
||||||
// v2 Prefix + Separator: workflow> (ComfyUI_frontend v1.2.63)
|
// v2 Prefix + Separator: workflow> (ComfyUI_frontend v1.2.63)
|
||||||
const PREFIX = 'workflow'
|
const PREFIX = 'workflow'
|
||||||
@@ -813,6 +819,7 @@ export class GroupNodeHandler {
|
|||||||
innerNodeIndex++
|
innerNodeIndex++
|
||||||
) {
|
) {
|
||||||
const innerNode = this.innerNodes[innerNodeIndex]
|
const innerNode = this.innerNodes[innerNodeIndex]
|
||||||
|
innerNode.graph ??= this.node.graph
|
||||||
|
|
||||||
for (const w of innerNode.widgets ?? []) {
|
for (const w of innerNode.widgets ?? []) {
|
||||||
if (w.type === 'converted-widget') {
|
if (w.type === 'converted-widget') {
|
||||||
@@ -899,7 +906,20 @@ export class GroupNodeHandler {
|
|||||||
return link
|
return link
|
||||||
}
|
}
|
||||||
|
|
||||||
this.node.getInnerNodes = () => {
|
/** @internal Used to flatten the subgraph before execution. Recursive; call with no args. */
|
||||||
|
this.node.getInnerNodes = (
|
||||||
|
computedNodeDtos: Map<ExecutionId, ExecutableLGraphNode>,
|
||||||
|
/** The path of subgraph node IDs. */
|
||||||
|
subgraphNodePath: readonly NodeId[] = [],
|
||||||
|
/** The list of nodes to add to. */
|
||||||
|
nodes: ExecutableLGraphNode[] = [],
|
||||||
|
/** The set of visited nodes. */
|
||||||
|
visited = new Set<LGraphNode>()
|
||||||
|
): ExecutableLGraphNode[] => {
|
||||||
|
if (visited.has(this.node))
|
||||||
|
throw new Error('RecursionError: while flattening subgraph')
|
||||||
|
visited.add(this.node)
|
||||||
|
|
||||||
if (!this.innerNodes) {
|
if (!this.innerNodes) {
|
||||||
// @ts-expect-error fixme ts strict error
|
// @ts-expect-error fixme ts strict error
|
||||||
this.node.setInnerNodes(
|
this.node.setInnerNodes(
|
||||||
@@ -910,6 +930,8 @@ export class GroupNodeHandler {
|
|||||||
innerNode.configure(n)
|
innerNode.configure(n)
|
||||||
// @ts-expect-error fixme ts strict error
|
// @ts-expect-error fixme ts strict error
|
||||||
innerNode.id = `${this.node.id}:${i}`
|
innerNode.id = `${this.node.id}:${i}`
|
||||||
|
// @ts-expect-error fixme ts strict error
|
||||||
|
innerNode.graph = this.node.graph
|
||||||
return innerNode
|
return innerNode
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -917,7 +939,31 @@ export class GroupNodeHandler {
|
|||||||
|
|
||||||
this.updateInnerWidgets()
|
this.updateInnerWidgets()
|
||||||
|
|
||||||
return this.innerNodes
|
const subgraphInstanceIdPath = [...subgraphNodePath, this.node.id]
|
||||||
|
|
||||||
|
// Assertion: Deprecated, does not matter.
|
||||||
|
const subgraphNode = (this.node.graph?.getNodeById(
|
||||||
|
subgraphNodePath.at(-1)
|
||||||
|
) ?? undefined) as SubgraphNode | undefined
|
||||||
|
|
||||||
|
for (const node of this.innerNodes) {
|
||||||
|
node.graph ??= this.node.graph
|
||||||
|
|
||||||
|
// Create minimal DTOs rather than cloning the node
|
||||||
|
const currentId = String(node.id)
|
||||||
|
node.id = currentId.split(':').at(-1)
|
||||||
|
const aVeryRealNode = new ExecutableGroupNodeChildDTO(
|
||||||
|
node,
|
||||||
|
subgraphInstanceIdPath,
|
||||||
|
computedNodeDtos,
|
||||||
|
subgraphNode
|
||||||
|
)
|
||||||
|
node.id = currentId
|
||||||
|
aVeryRealNode.groupNodeHandler = this
|
||||||
|
|
||||||
|
nodes.push(aVeryRealNode)
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error fixme ts strict error
|
// @ts-expect-error fixme ts strict error
|
||||||
@@ -1503,6 +1549,9 @@ export class GroupNodeHandler {
|
|||||||
|
|
||||||
this.linkOutputs(node, i)
|
this.linkOutputs(node, i)
|
||||||
app.graph.remove(node)
|
app.graph.remove(node)
|
||||||
|
|
||||||
|
// Set internal ID to what is expected after workflow is reloaded
|
||||||
|
node.id = `${this.node.id}:${i}`
|
||||||
}
|
}
|
||||||
|
|
||||||
this.linkInputs()
|
this.linkInputs()
|
||||||
@@ -1608,8 +1657,14 @@ async function convertSelectedNodesToGroupNode() {
|
|||||||
if (nodes.length === 1) {
|
if (nodes.length === 1) {
|
||||||
throw new Error('Please select multiple nodes to convert to group node')
|
throw new Error('Please select multiple nodes to convert to group node')
|
||||||
}
|
}
|
||||||
if (nodes.some((n) => GroupNodeHandler.isGroupNode(n))) {
|
|
||||||
throw new Error('Selected nodes contain a group node')
|
for (const node of nodes) {
|
||||||
|
if (node instanceof SubgraphNode) {
|
||||||
|
throw new Error('Selected nodes contain a subgraph node')
|
||||||
|
}
|
||||||
|
if (GroupNodeHandler.isGroupNode(node)) {
|
||||||
|
throw new Error('Selected nodes contain a group node')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return await GroupNodeHandler.fromNodes(nodes)
|
return await GroupNodeHandler.fromNodes(nodes)
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/types/litegraph-augmentation.d.ts
vendored
6
src/types/litegraph-augmentation.d.ts
vendored
@@ -69,7 +69,7 @@ declare module '@comfyorg/litegraph/dist/interfaces' {
|
|||||||
* ComfyUI extensions of litegraph
|
* ComfyUI extensions of litegraph
|
||||||
*/
|
*/
|
||||||
declare module '@comfyorg/litegraph' {
|
declare module '@comfyorg/litegraph' {
|
||||||
import type { ExecutableLGraphNode } from '@comfyorg/litegraph'
|
import type { ExecutableLGraphNode, ExecutionId } from '@comfyorg/litegraph'
|
||||||
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||||
|
|
||||||
interface LGraphNodeConstructor<T extends LGraphNode = LGraphNode> {
|
interface LGraphNodeConstructor<T extends LGraphNode = LGraphNode> {
|
||||||
@@ -99,8 +99,10 @@ declare module '@comfyorg/litegraph' {
|
|||||||
setInnerNodes?(nodes: LGraphNode[]): void
|
setInnerNodes?(nodes: LGraphNode[]): void
|
||||||
/** Originally a group node API. */
|
/** Originally a group node API. */
|
||||||
getInnerNodes?(
|
getInnerNodes?(
|
||||||
|
nodesByExecutionId: Map<ExecutionId, ExecutableLGraphNode>,
|
||||||
|
subgraphNodePath?: readonly NodeId[],
|
||||||
nodes?: ExecutableLGraphNode[],
|
nodes?: ExecutableLGraphNode[],
|
||||||
subgraphs?: WeakSet<LGraphNode>
|
subgraphs?: Set<LGraphNode>
|
||||||
): ExecutableLGraphNode[]
|
): ExecutableLGraphNode[]
|
||||||
/** @deprecated groupNode */
|
/** @deprecated groupNode */
|
||||||
convertToNodes?(): LGraphNode[]
|
convertToNodes?(): LGraphNode[]
|
||||||
|
|||||||
53
src/utils/executableGroupNodeChildDTO.ts
Normal file
53
src/utils/executableGroupNodeChildDTO.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
type ExecutableLGraphNode,
|
||||||
|
ExecutableNodeDTO,
|
||||||
|
type ExecutionId,
|
||||||
|
type LGraphNode,
|
||||||
|
type NodeId,
|
||||||
|
type SubgraphNode
|
||||||
|
} from '@comfyorg/litegraph'
|
||||||
|
|
||||||
|
import type { GroupNodeHandler } from '@/extensions/core/groupNode'
|
||||||
|
|
||||||
|
export class ExecutableGroupNodeChildDTO extends ExecutableNodeDTO {
|
||||||
|
groupNodeHandler?: GroupNodeHandler
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
/** The actual node that this DTO wraps. */
|
||||||
|
node: LGraphNode | SubgraphNode,
|
||||||
|
/** A list of subgraph instance node IDs from the root graph to the containing instance. @see {@link id} */
|
||||||
|
subgraphNodePath: readonly NodeId[],
|
||||||
|
/** A flattened map of all DTOs in this node network. Subgraph instances have been expanded into their inner nodes. */
|
||||||
|
nodesByExecutionId: Map<ExecutionId, ExecutableLGraphNode>,
|
||||||
|
/** The actual subgraph instance that contains this node, otherise undefined. */
|
||||||
|
subgraphNode?: SubgraphNode | undefined,
|
||||||
|
groupNodeHandler?: GroupNodeHandler
|
||||||
|
) {
|
||||||
|
super(node, subgraphNodePath, nodesByExecutionId, subgraphNode)
|
||||||
|
this.groupNodeHandler = groupNodeHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
override resolveInput(slot: number) {
|
||||||
|
const inputNode = this.node.getInputNode(slot)
|
||||||
|
if (!inputNode) return
|
||||||
|
|
||||||
|
const link = this.node.getInputLink(slot)
|
||||||
|
if (!link) throw new Error('Failed to get input link')
|
||||||
|
|
||||||
|
const id = String(inputNode.id).split(':').at(-1)
|
||||||
|
if (id === undefined) throw new Error('Invalid input node id')
|
||||||
|
|
||||||
|
const inputNodeDto = this.nodesByExecutionId?.get(id)
|
||||||
|
if (!inputNodeDto) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to get input node ${id} for group node child ${this.id} with slot ${slot}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
node: inputNodeDto,
|
||||||
|
origin_id: inputNode.id,
|
||||||
|
origin_slot: link.origin_slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/utils/executableGroupNodeDto.ts
Normal file
71
src/utils/executableGroupNodeDto.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import {
|
||||||
|
type ExecutableLGraphNode,
|
||||||
|
ExecutableNodeDTO,
|
||||||
|
type ISlotType,
|
||||||
|
LGraphEventMode,
|
||||||
|
type LGraphNode
|
||||||
|
} from '@comfyorg/litegraph'
|
||||||
|
|
||||||
|
export const GROUP = Symbol()
|
||||||
|
|
||||||
|
export function isGroupNode(node: LGraphNode): boolean {
|
||||||
|
return node.constructor?.nodeData?.[GROUP] !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExecutableGroupNodeDTO extends ExecutableNodeDTO {
|
||||||
|
override get isVirtualNode(): true {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override getInnerNodes(): ExecutableLGraphNode[] {
|
||||||
|
return this.node.getInnerNodes?.(this.nodesByExecutionId) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
override resolveOutput(slot: number, type: ISlotType, visited: Set<string>) {
|
||||||
|
// Temporary duplication: Bypass nodes are bypassed using the first input with matching type
|
||||||
|
if (this.mode === LGraphEventMode.BYPASS) {
|
||||||
|
const { inputs } = this
|
||||||
|
|
||||||
|
// Bypass nodes by finding first input with matching type
|
||||||
|
const parentInputIndexes = Object.keys(inputs).map(Number)
|
||||||
|
// Prioritise exact slot index
|
||||||
|
const indexes = [slot, ...parentInputIndexes]
|
||||||
|
const matchingIndex = indexes.find((i) => inputs[i]?.type === type)
|
||||||
|
|
||||||
|
// No input types match
|
||||||
|
if (matchingIndex === undefined) return
|
||||||
|
|
||||||
|
return this.resolveInput(matchingIndex, visited)
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkId = this.node.outputs[slot]?.links?.at(0)
|
||||||
|
const link = this.node.graph?.getLink(linkId)
|
||||||
|
if (!link) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to get link for group node ${this.node.id} with link ${linkId}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = this.node.updateLink?.(link)
|
||||||
|
if (!updated) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to update link for group node ${this.node.id} with link ${linkId}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = this.node
|
||||||
|
.getInnerNodes?.(this.nodesByExecutionId)
|
||||||
|
.find((node) => node.id === updated.origin_id)
|
||||||
|
if (!node) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to get node for group node ${this.node.id} with link ${linkId}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
node,
|
||||||
|
origin_id: `${this.id}:${(updated.origin_id as string).split(':').at(-1)}`,
|
||||||
|
origin_slot: updated.origin_slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
import type { LGraph, NodeId } from '@comfyorg/litegraph'
|
import type {
|
||||||
|
ExecutableLGraphNode,
|
||||||
|
ExecutionId,
|
||||||
|
LGraph,
|
||||||
|
NodeId
|
||||||
|
} from '@comfyorg/litegraph'
|
||||||
import {
|
import {
|
||||||
ExecutableNodeDTO,
|
ExecutableNodeDTO,
|
||||||
LGraphEventMode,
|
LGraphEventMode,
|
||||||
@@ -10,6 +15,7 @@ import type {
|
|||||||
ComfyWorkflowJSON
|
ComfyWorkflowJSON
|
||||||
} from '@/schemas/comfyWorkflowSchema'
|
} from '@/schemas/comfyWorkflowSchema'
|
||||||
|
|
||||||
|
import { ExecutableGroupNodeDTO, isGroupNode } from './executableGroupNodeDto'
|
||||||
import { compressWidgetInputSlots } from './litegraphUtil'
|
import { compressWidgetInputSlots } from './litegraphUtil'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,7 +60,9 @@ export const graphToPrompt = async (
|
|||||||
const { sortNodes = false, queueNodeIds } = options
|
const { sortNodes = false, queueNodeIds } = options
|
||||||
|
|
||||||
for (const node of graph.computeExecutionOrder(false)) {
|
for (const node of graph.computeExecutionOrder(false)) {
|
||||||
const innerNodes = node.getInnerNodes ? node.getInnerNodes() : [node]
|
const innerNodes = node.getInnerNodes
|
||||||
|
? node.getInnerNodes(new Map())
|
||||||
|
: [node]
|
||||||
for (const innerNode of innerNodes) {
|
for (const innerNode of innerNodes) {
|
||||||
if (innerNode.isVirtualNode) {
|
if (innerNode.isVirtualNode) {
|
||||||
innerNode.applyToGraph?.()
|
innerNode.applyToGraph?.()
|
||||||
@@ -78,82 +86,80 @@ export const graphToPrompt = async (
|
|||||||
workflow.extra ??= {}
|
workflow.extra ??= {}
|
||||||
workflow.extra.frontendVersion = __COMFYUI_FRONTEND_VERSION__
|
workflow.extra.frontendVersion = __COMFYUI_FRONTEND_VERSION__
|
||||||
|
|
||||||
const computedNodeDtos = graph
|
const nodeDtoMap = new Map<ExecutionId, ExecutableLGraphNode>()
|
||||||
.computeExecutionOrder(false)
|
for (const node of graph.computeExecutionOrder(false)) {
|
||||||
.map(
|
const dto: ExecutableLGraphNode = isGroupNode(node)
|
||||||
(node) =>
|
? new ExecutableGroupNodeDTO(node, [], nodeDtoMap)
|
||||||
new ExecutableNodeDTO(
|
: new ExecutableNodeDTO(
|
||||||
node,
|
node,
|
||||||
[],
|
[],
|
||||||
|
nodeDtoMap,
|
||||||
node instanceof SubgraphNode ? node : undefined
|
node instanceof SubgraphNode ? node : undefined
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
for (const innerNode of dto.getInnerNodes()) {
|
||||||
|
nodeDtoMap.set(innerNode.id, innerNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeDtoMap.set(dto.id, dto)
|
||||||
|
}
|
||||||
|
|
||||||
let output: ComfyApiWorkflow = {}
|
let output: ComfyApiWorkflow = {}
|
||||||
// Process nodes in order of execution
|
// Process nodes in order of execution
|
||||||
for (const outerNode of computedNodeDtos) {
|
for (const node of nodeDtoMap.values()) {
|
||||||
// Don't serialize muted nodes
|
// Don't serialize muted nodes
|
||||||
if (
|
if (
|
||||||
outerNode.mode === LGraphEventMode.NEVER ||
|
node.isVirtualNode ||
|
||||||
outerNode.mode === LGraphEventMode.BYPASS
|
node.mode === LGraphEventMode.NEVER ||
|
||||||
|
node.mode === LGraphEventMode.BYPASS
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const node of outerNode.getInnerNodes()) {
|
const inputs: ComfyApiWorkflow[string]['inputs'] = {}
|
||||||
if (
|
const { widgets } = node
|
||||||
node.isVirtualNode ||
|
|
||||||
node.mode === LGraphEventMode.NEVER ||
|
// Store all widget values
|
||||||
node.mode === LGraphEventMode.BYPASS
|
if (widgets) {
|
||||||
) {
|
for (const [i, widget] of widgets.entries()) {
|
||||||
continue
|
if (!widget.name || widget.options?.serialize === false) continue
|
||||||
|
|
||||||
|
const widgetValue = widget.serializeValue
|
||||||
|
? await widget.serializeValue(node, i)
|
||||||
|
: widget.value
|
||||||
|
// By default, Array values are reserved to represent node connections.
|
||||||
|
// We need to wrap the array as an object to avoid the misinterpretation
|
||||||
|
// of the array as a node connection.
|
||||||
|
// The backend automatically unwraps the object to an array during
|
||||||
|
// execution.
|
||||||
|
inputs[widget.name] = Array.isArray(widgetValue)
|
||||||
|
? {
|
||||||
|
__value__: widgetValue
|
||||||
|
}
|
||||||
|
: widgetValue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const inputs: ComfyApiWorkflow[string]['inputs'] = {}
|
// Store all node links
|
||||||
const { widgets } = node
|
for (const [i, input] of node.inputs.entries()) {
|
||||||
|
const resolvedInput = node.resolveInput(i)
|
||||||
|
if (!resolvedInput) continue
|
||||||
|
|
||||||
// Store all widget values
|
inputs[input.name] = [
|
||||||
if (widgets) {
|
String(resolvedInput.origin_id),
|
||||||
for (const [i, widget] of widgets.entries()) {
|
// @ts-expect-error link.origin_slot is already number.
|
||||||
if (!widget.name || widget.options?.serialize === false) continue
|
parseInt(resolvedInput.origin_slot)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
const widgetValue = widget.serializeValue
|
output[String(node.id)] = {
|
||||||
? await widget.serializeValue(node, i)
|
inputs,
|
||||||
: widget.value
|
// TODO(huchenlei): Filter out all nodes that cannot be mapped to a
|
||||||
// By default, Array values are reserved to represent node connections.
|
// comfyClass.
|
||||||
// We need to wrap the array as an object to avoid the misinterpretation
|
class_type: node.comfyClass!,
|
||||||
// of the array as a node connection.
|
// Ignored by the backend.
|
||||||
// The backend automatically unwraps the object to an array during
|
_meta: {
|
||||||
// execution.
|
title: node.title
|
||||||
inputs[widget.name] = Array.isArray(widgetValue)
|
|
||||||
? {
|
|
||||||
__value__: widgetValue
|
|
||||||
}
|
|
||||||
: widgetValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store all node links
|
|
||||||
for (const [i, input] of node.inputs.entries()) {
|
|
||||||
const resolvedInput = node.resolveInput(i)
|
|
||||||
if (!resolvedInput) continue
|
|
||||||
|
|
||||||
inputs[input.name] = [
|
|
||||||
String(resolvedInput.origin_id),
|
|
||||||
// @ts-expect-error link.origin_slot is already number.
|
|
||||||
parseInt(resolvedInput.origin_slot)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
output[String(node.id)] = {
|
|
||||||
inputs,
|
|
||||||
// TODO(huchenlei): Filter out all nodes that cannot be mapped to a
|
|
||||||
// comfyClass.
|
|
||||||
class_type: node.comfyClass!,
|
|
||||||
// Ignored by the backend.
|
|
||||||
_meta: {
|
|
||||||
title: node.title
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user