diff --git a/src/extensions/core/groupNode.ts b/src/extensions/core/groupNode.ts index 0dac84e94..a7af7361a 100644 --- a/src/extensions/core/groupNode.ts +++ b/src/extensions/core/groupNode.ts @@ -1,21 +1,23 @@ import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants' import { t } from '@/i18n' -import { type NodeId } from '@/lib/litegraph/src/LGraphNode' +import type { GroupNodeWorkflowData } from '@/lib/litegraph/src/LGraph' +import type { SerialisedLLinkArray } from '@/lib/litegraph/src/LLink' +import type { NodeId } from '@/lib/litegraph/src/LGraphNode' import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces' import { type ExecutableLGraphNode, type ExecutionId, LGraphNode, + type LGraphNodeConstructor, LiteGraph, SubgraphNode } from '@/lib/litegraph/src/litegraph' import { useToastStore } from '@/platform/updates/common/toastStore' import { - type ComfyLink, type ComfyNode, type ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' -import type { ComfyNodeDef } from '@/schemas/nodeDefSchema' +import type { ComfyNodeDef, InputSpec } from '@/schemas/nodeDefSchema' import { useDialogService } from '@/services/dialogService' import { useExecutionStore } from '@/stores/executionStore' import { useNodeDefStore } from '@/stores/nodeDefStore' @@ -30,10 +32,55 @@ import { app } from '../../scripts/app' import { ManageGroupDialog } from './groupNodeManage' import { mergeIfValid } from './widgetInputs' -type GroupNodeWorkflowData = { - external: ComfyLink[] - links: ComfyLink[] - nodes: ComfyNode[] +type GroupNodeLink = SerialisedLLinkArray +type LinksFromMap = Record> +type LinksToMap = Record> +type ExternalFromMap = Record> + +interface GroupNodeInput { + name?: string + type?: string + label?: string + widget?: { name: string } +} + +interface GroupNodeOutput { + name?: string + type?: string + label?: string + widget?: { name: string } + links?: number[] +} + +interface GroupNodeData extends Omit< + GroupNodeWorkflowData['nodes'][number], + 'inputs' | 'outputs' +> { + title?: string + widgets_values?: unknown[] + inputs?: GroupNodeInput[] + outputs?: GroupNodeOutput[] +} + +interface GroupNodeDef { + input: { + required: Record + optional?: Record + } + output: unknown[] + output_name: string[] + output_is_list: boolean[] +} + +interface NodeConfigEntry { + input?: Record + output?: Record +} + +interface SerializedGroupConfig { + nodes: unknown[] + links: GroupNodeLink[] + external?: (number | string)[][] } const Workflow = { @@ -42,11 +89,9 @@ const Workflow = { Registered: 1, InWorkflow: 2 }, - // @ts-expect-error fixme ts strict error - isInUseGroupNode(name) { + isInUseGroupNode(name: string) { const id = `${PREFIX}${SEPARATOR}${name}` // Check if lready registered/in use in this workflow - // @ts-expect-error fixme ts strict error if (app.rootGraph.extra?.groupNodes?.[name]) { if (app.rootGraph.nodes.find((n) => n.type === id)) { return Workflow.InUse.InWorkflow @@ -61,15 +106,13 @@ const Workflow = { if (!extra) app.rootGraph.extra = extra = {} let groupNodes = extra.groupNodes if (!groupNodes) extra.groupNodes = groupNodes = {} - // @ts-expect-error fixme ts strict error groupNodes[name] = data } } class GroupNodeBuilder { nodes: LGraphNode[] - // @ts-expect-error fixme ts strict error - nodeData: GroupNodeWorkflowData + nodeData!: GroupNodeWorkflowData constructor(nodes: LGraphNode[]) { this.nodes = nodes @@ -121,25 +164,25 @@ class GroupNodeBuilder { const nodesInOrder = app.rootGraph.computeExecutionOrder(false) this.nodes = this.nodes .map((node) => ({ index: nodesInOrder.indexOf(node), node })) - // @ts-expect-error id might be string - .sort((a, b) => a.index - b.index || a.node.id - b.node.id) + .sort( + (a, b) => + a.index - b.index || + String(a.node.id).localeCompare(String(b.node.id)) + ) .map(({ node }) => node) } - getNodeData() { - // @ts-expect-error fixme ts strict error - const storeLinkTypes = (config) => { + getNodeData(): GroupNodeWorkflowData { + const storeLinkTypes = (config: SerializedGroupConfig) => { // Store link types for dynamically typed nodes e.g. reroutes for (const link of config.links) { - const origin = app.rootGraph.getNodeById(link[4]) - // @ts-expect-error fixme ts strict error - const type = origin.outputs[link[1]].type - link.push(type) + const origin = app.rootGraph.getNodeById(link[4] as NodeId) + const type = origin?.outputs?.[Number(link[1])]?.type + if (type !== undefined) link.push(type) } } - // @ts-expect-error fixme ts strict error - const storeExternalLinks = (config) => { + const storeExternalLinks = (config: SerializedGroupConfig) => { // Store any external links to the group in the config so when rebuilding we add extra slots config.external = [] for (let i = 0; i < this.nodes.length; i++) { @@ -161,53 +204,50 @@ class GroupNodeBuilder { } } if (hasExternal) { - config.external.push([i, slot, type]) + config.external.push([i, slot, String(type)]) } } } } // Use the built in copyToClipboard function to generate the node data we need - try { - // @ts-expect-error fixme ts strict error - const serialised = serialise(this.nodes, app.canvas?.graph) - const config = JSON.parse(serialised) + const graph = app.canvas?.graph + if (!graph) return { nodes: [], links: [], external: [] } + const serialised = serialise(this.nodes, graph) + const config = JSON.parse(serialised) as SerializedGroupConfig + config.external = [] - storeLinkTypes(config) - storeExternalLinks(config) + storeLinkTypes(config) + storeExternalLinks(config) - return config - } finally { - } + return config as GroupNodeWorkflowData } } export class GroupNodeConfig { name: string - nodeData: any + nodeData: GroupNodeWorkflowData inputCount: number - oldToNewOutputMap: {} - newToOldOutputMap: {} - oldToNewInputMap: {} - oldToNewWidgetMap: {} - newToOldWidgetMap: {} - primitiveDefs: {} - widgetToPrimitive: {} - primitiveToWidget: {} - nodeInputs: {} - outputVisibility: any[] + oldToNewOutputMap: Record> + newToOldOutputMap: Record + oldToNewInputMap: Record> + oldToNewWidgetMap: Record> + newToOldWidgetMap: Record + primitiveDefs: Record + widgetToPrimitive: Record> + primitiveToWidget: Record< + number, + { nodeId: number | string | null; inputName: string }[] + > + nodeInputs: Record> + outputVisibility: boolean[] nodeDef: (ComfyNodeDef & { [GROUP]: GroupNodeConfig }) | undefined - // @ts-expect-error fixme ts strict error - inputs: any[] - // @ts-expect-error fixme ts strict error - linksFrom: {} - // @ts-expect-error fixme ts strict error - linksTo: {} - // @ts-expect-error fixme ts strict error - externalFrom: {} + inputs!: unknown[] + linksFrom!: LinksFromMap + linksTo!: LinksToMap + externalFrom!: ExternalFromMap - // @ts-expect-error fixme ts strict error - constructor(name, nodeData) { + constructor(name: string, nodeData: GroupNodeWorkflowData) { this.name = name this.nodeData = nodeData this.getLinks() @@ -236,7 +276,6 @@ export class GroupNodeConfig { category: 'group nodes' + (SEPARATOR + source), input: { required: {} }, description: `Group node combining ${this.nodeData.nodes - // @ts-expect-error fixme ts strict error .map((n) => n.type) .join(', ')}`, python_module: 'custom_nodes.' + this.name, @@ -248,17 +287,15 @@ export class GroupNodeConfig { const seenInputs = {} const seenOutputs = {} for (let i = 0; i < this.nodeData.nodes.length; i++) { - const node = this.nodeData.nodes[i] + const node = this.nodeData.nodes[i] as GroupNodeData node.index = i this.processNode(node, seenInputs, seenOutputs) } for (const p of this.#convertedToProcess) { - // @ts-expect-error fixme ts strict error p() } - // @ts-expect-error fixme ts strict error - this.#convertedToProcess = null + this.#convertedToProcess = [] if (!this.nodeDef) return await app.registerNodeDef(`${PREFIX}${SEPARATOR}` + this.name, this.nodeDef) useNodeDefStore().addNodeDef(this.nodeDef) @@ -270,50 +307,57 @@ export class GroupNodeConfig { this.externalFrom = {} // Extract links for easy lookup - for (const l of this.nodeData.links) { - const [sourceNodeId, sourceNodeSlot, targetNodeId, targetNodeSlot] = l + for (const link of this.nodeData.links) { + const [sourceNodeId, sourceNodeSlot, targetNodeId, targetNodeSlot] = link // Skip links outside the copy config - if (sourceNodeId == null) continue + if ( + sourceNodeId == null || + sourceNodeSlot == null || + targetNodeId == null || + targetNodeSlot == null + ) + continue - // @ts-expect-error fixme ts strict error - if (!this.linksFrom[sourceNodeId]) { - // @ts-expect-error fixme ts strict error - this.linksFrom[sourceNodeId] = {} - } - // @ts-expect-error fixme ts strict error - if (!this.linksFrom[sourceNodeId][sourceNodeSlot]) { - // @ts-expect-error fixme ts strict error - this.linksFrom[sourceNodeId][sourceNodeSlot] = [] - } - // @ts-expect-error fixme ts strict error - this.linksFrom[sourceNodeId][sourceNodeSlot].push(l) + const srcId = Number(sourceNodeId) + const srcSlot = Number(sourceNodeSlot) + const tgtId = Number(targetNodeId) + const tgtSlot = Number(targetNodeSlot) - // @ts-expect-error fixme ts strict error - if (!this.linksTo[targetNodeId]) { - // @ts-expect-error fixme ts strict error - this.linksTo[targetNodeId] = {} + if (!this.linksFrom[srcId]) { + this.linksFrom[srcId] = {} } - // @ts-expect-error fixme ts strict error - this.linksTo[targetNodeId][targetNodeSlot] = l + if (!this.linksFrom[srcId][srcSlot]) { + this.linksFrom[srcId][srcSlot] = [] + } + this.linksFrom[srcId][srcSlot].push(link) + + if (!this.linksTo[tgtId]) { + this.linksTo[tgtId] = {} + } + this.linksTo[tgtId][tgtSlot] = link } if (this.nodeData.external) { for (const ext of this.nodeData.external) { - // @ts-expect-error fixme ts strict error - if (!this.externalFrom[ext[0]]) { - // @ts-expect-error fixme ts strict error - this.externalFrom[ext[0]] = { [ext[1]]: ext[2] } + const nodeIdx = Number(ext[0]) + const slotIdx = Number(ext[1]) + const typeVal = ext[2] + if (typeVal == null) continue + if (!this.externalFrom[nodeIdx]) { + this.externalFrom[nodeIdx] = { [slotIdx]: typeVal } } else { - // @ts-expect-error fixme ts strict error - this.externalFrom[ext[0]][ext[1]] = ext[2] + this.externalFrom[nodeIdx][slotIdx] = typeVal } } } } - // @ts-expect-error fixme ts strict error - processNode(node, seenInputs, seenOutputs) { + processNode( + node: GroupNodeData, + seenInputs: Record, + seenOutputs: Record + ) { const def = this.getNodeDef(node) if (!def) return @@ -323,32 +367,44 @@ export class GroupNodeConfig { if (def.output?.length) this.processNodeOutputs(node, seenOutputs, def) } - // @ts-expect-error fixme ts strict error - getNodeDef(node) { - // @ts-expect-error fixme ts strict error - const def = globalDefs[node.type] - if (def) return def + getNodeDef( + node: GroupNodeData + ): GroupNodeDef | ComfyNodeDef | null | undefined { + if (node.type) { + const def = globalDefs[node.type] + if (def) return def + } - // @ts-expect-error fixme ts strict error - const linksFrom = this.linksFrom[node.index] + const nodeIndex = node.index + if (nodeIndex == null) return undefined + + const linksFrom = this.linksFrom[nodeIndex] if (node.type === 'PrimitiveNode') { // Skip as its not linked if (!linksFrom) return - let type = linksFrom['0'][0][5] + let type: string | number | null = linksFrom[0]?.[0]?.[5] ?? null if (type === 'COMBO') { // Use the array items - const source = node.outputs[0].widget.name - const fromTypeName = this.nodeData.nodes[linksFrom['0'][0][2]].type - // @ts-expect-error fixme ts strict error - const fromType = globalDefs[fromTypeName] - const input = - fromType.input.required[source] ?? fromType.input.optional[source] - type = input[0] + const source = node.outputs?.[0]?.widget?.name + const nodeIdx = linksFrom[0]?.[0]?.[2] + if (source && nodeIdx != null) { + const fromTypeName = this.nodeData.nodes[Number(nodeIdx)]?.type + if (fromTypeName) { + const fromType = globalDefs[fromTypeName] + const input = + fromType?.input?.required?.[source] ?? + fromType?.input?.optional?.[source] + const inputType = input?.[0] + type = + typeof inputType === 'string' || typeof inputType === 'number' + ? inputType + : null + } + } } - // @ts-expect-error fixme ts strict error - const def = (this.primitiveDefs[node.index] = { + const def = (this.primitiveDefs[nodeIndex] = { input: { required: { value: [type, {}] @@ -360,66 +416,85 @@ export class GroupNodeConfig { }) return def } else if (node.type === 'Reroute') { - // @ts-expect-error fixme ts strict error - const linksTo = this.linksTo[node.index] - // @ts-expect-error fixme ts strict error - if (linksTo && linksFrom && !this.externalFrom[node.index]?.[0]) { + const linksTo = this.linksTo[nodeIndex] + if (linksTo && linksFrom && !this.externalFrom[nodeIndex]?.[0]) { // Being used internally return null } - let config = {} + let config: Record = {} let rerouteType = '*' if (linksFrom) { - for (const [, , id, slot] of linksFrom['0']) { - const node = this.nodeData.nodes[id] - const input = node.inputs[slot] - if (rerouteType === '*') { + const links = linksFrom[0] ?? [] + for (const link of links) { + const id = link[2] + const slot = link[3] + if (id == null || slot == null) continue + const targetNode = this.nodeData.nodes[Number(id)] + const input = targetNode?.inputs?.[Number(slot)] as + | GroupNodeInput + | undefined + if (input?.type && rerouteType === '*') { rerouteType = input.type } - if (input.widget) { - // @ts-expect-error fixme ts strict error - const targetDef = globalDefs[node.type] + if (input?.widget && targetNode?.type) { + const targetDef = globalDefs[targetNode.type] const targetWidget = - targetDef.input.required[input.widget.name] ?? - targetDef.input.optional[input.widget.name] + targetDef?.input?.required?.[input.widget.name] ?? + targetDef?.input?.optional?.[input.widget.name] - const widget = [targetWidget[0], config] - const res = mergeIfValid( - { - // @ts-expect-error fixme ts strict error - widget - }, - targetWidget, - false, - null, - widget - ) - config = res?.customConfig ?? config + if (targetWidget) { + const widgetSpec = [targetWidget[0], config] as Parameters< + typeof mergeIfValid + >[4] + const res = mergeIfValid( + { widget: widgetSpec } as unknown as Parameters< + typeof mergeIfValid + >[0], + targetWidget, + false, + undefined, + widgetSpec + ) + config = (res?.customConfig as Record) ?? config + } } } } else if (linksTo) { - const [id, slot] = linksTo['0'] - rerouteType = this.nodeData.nodes[id].outputs[slot].type + const link = linksTo[0] + if (link) { + const id = link[0] + const slot = link[1] + if (id != null && slot != null) { + const outputType = + this.nodeData.nodes[Number(id)]?.outputs?.[Number(slot)] + if ( + outputType && + typeof outputType === 'object' && + 'type' in outputType + ) { + rerouteType = String((outputType as GroupNodeOutput).type ?? '*') + } + } + } } else { // Reroute used as a pipe for (const l of this.nodeData.links) { if (l[2] === node.index) { - rerouteType = l[5] + const linkType = l[5] + if (linkType != null) rerouteType = String(linkType) break } } if (rerouteType === '*') { // Check for an external link - // @ts-expect-error fixme ts strict error - const t = this.externalFrom[node.index]?.[0] + const t = this.externalFrom[nodeIndex]?.[0] if (t) { - rerouteType = t + rerouteType = String(t) } } } - // @ts-expect-error config.forceInput = true return { input: { @@ -441,12 +516,19 @@ export class GroupNodeConfig { ) } - // @ts-expect-error fixme ts strict error - getInputConfig(node, inputName, seenInputs, config, extra?) { - const customConfig = this.nodeData.config?.[node.index]?.input?.[inputName] + getInputConfig( + node: GroupNodeData, + inputName: string, + seenInputs: Record, + config: unknown[], + extra?: Record + ) { + const nodeConfig = this.nodeData.config?.[node.index ?? -1] as + | NodeConfigEntry + | undefined + const customConfig = nodeConfig?.input?.[inputName] let name = customConfig?.name ?? - // @ts-expect-error fixme ts strict error node.inputs?.find((inp) => inp.name === inputName)?.label ?? inputName let key = name @@ -467,36 +549,55 @@ export class GroupNodeConfig { } if (config[0] === 'IMAGEUPLOAD') { if (!extra) extra = {} - extra.widget = - // @ts-expect-error fixme ts strict error - this.oldToNewWidgetMap[node.index]?.[config[1]?.widget ?? 'image'] ?? - 'image' + const nodeIndex = node.index ?? -1 + const configOptions = + typeof config[1] === 'object' && config[1] !== null ? config[1] : {} + const widgetKey = + 'widget' in configOptions && typeof configOptions.widget === 'string' + ? configOptions.widget + : 'image' + extra.widget = this.oldToNewWidgetMap[nodeIndex]?.[widgetKey] ?? 'image' } if (extra) { - config = [config[0], { ...config[1], ...extra }] + const configObj = + typeof config[1] === 'object' && config[1] ? config[1] : {} + config = [config[0], { ...configObj, ...extra }] } return { name, config, customConfig } } - // @ts-expect-error fixme ts strict error - processWidgetInputs(inputs, node, inputNames, seenInputs) { - const slots = [] - const converted = new Map() - // @ts-expect-error fixme ts strict error - const widgetMap = (this.oldToNewWidgetMap[node.index] = {}) + processWidgetInputs( + inputs: Record, + node: GroupNodeData, + inputNames: string[], + seenInputs: Record + ) { + const slots: string[] = [] + const converted = new Map() + const nodeIndex = node.index ?? -1 + const widgetMap: Record = (this.oldToNewWidgetMap[ + nodeIndex + ] = {}) for (const inputName of inputNames) { - if (useWidgetStore().inputIsWidget(inputs[inputName])) { - const convertedIndex = node.inputs?.findIndex( - // @ts-expect-error fixme ts strict error - (inp) => inp.name === inputName && inp.widget?.name === inputName - ) + const inputSpec = inputs[inputName] + const isValidSpec = + Array.isArray(inputSpec) && + inputSpec.length >= 1 && + typeof inputSpec[0] === 'string' + if ( + isValidSpec && + useWidgetStore().inputIsWidget(inputSpec as InputSpec) + ) { + const convertedIndex = + node.inputs?.findIndex( + (inp) => inp.name === inputName && inp.widget?.name === inputName + ) ?? -1 if (convertedIndex > -1) { // This widget has been converted to a widget // We need to store this in the correct position so link ids line up converted.set(convertedIndex, inputName) - // @ts-expect-error fixme ts strict error widgetMap[inputName] = null } else { // Normal widget @@ -504,13 +605,13 @@ export class GroupNodeConfig { node, inputName, seenInputs, - inputs[inputName] + inputs[inputName] as unknown[] ) - // @ts-expect-error fixme ts strict error - this.nodeDef.input.required[name] = config - // @ts-expect-error fixme ts strict error + if (this.nodeDef?.input?.required) { + // @ts-expect-error legacy dynamic input assignment + this.nodeDef.input.required[name] = config + } widgetMap[inputName] = name - // @ts-expect-error fixme ts strict error this.newToOldWidgetMap[name] = { node, inputName } } } else { @@ -521,61 +622,78 @@ export class GroupNodeConfig { return { converted, slots } } - // @ts-expect-error fixme ts strict error - checkPrimitiveConnection(link, inputName, inputs) { - const sourceNode = this.nodeData.nodes[link[0]] - if (sourceNode.type === 'PrimitiveNode') { + checkPrimitiveConnection( + link: GroupNodeLink, + inputName: string, + inputs: Record + ) { + const linkSourceIdx = link[0] + if (linkSourceIdx == null) return + const sourceNode = this.nodeData.nodes[Number(linkSourceIdx)] + if (sourceNode?.type === 'PrimitiveNode') { // Merge link configurations - const [sourceNodeId, _, targetNodeId, __] = link - // @ts-expect-error fixme ts strict error + const sourceNodeId = Number(link[0]) + const targetNodeId = Number(link[2]) const primitiveDef = this.primitiveDefs[sourceNodeId] + if (!primitiveDef) return const targetWidget = inputs[inputName] - const primitiveConfig = primitiveDef.input.required.value + const primitiveConfig = primitiveDef.input.required.value as [ + unknown, + Record + ] const output = { widget: primitiveConfig } const config = mergeIfValid( - // @ts-expect-error invalid slot type + // @ts-expect-error slot type mismatch - legacy API output, targetWidget, false, - null, + undefined, primitiveConfig ) + const inputConfig = inputs[inputName]?.[1] primitiveConfig[1] = - (config?.customConfig ?? inputs[inputName][1]) - ? { ...inputs[inputName][1] } + (config?.customConfig ?? inputConfig) + ? { ...(typeof inputConfig === 'object' ? inputConfig : {}) } : {} - // @ts-expect-error fixme ts strict error - let name = this.oldToNewWidgetMap[sourceNodeId]['value'] - name = name.substr(0, name.length - 6) - primitiveConfig[1].control_after_generate = true - primitiveConfig[1].control_prefix = name + const widgetName = this.oldToNewWidgetMap[sourceNodeId]?.['value'] + if (widgetName) { + const name = widgetName.substring(0, widgetName.length - 6) + primitiveConfig[1].control_after_generate = true + primitiveConfig[1].control_prefix = name + } - // @ts-expect-error fixme ts strict error let toPrimitive = this.widgetToPrimitive[targetNodeId] if (!toPrimitive) { - // @ts-expect-error fixme ts strict error toPrimitive = this.widgetToPrimitive[targetNodeId] = {} } - if (toPrimitive[inputName]) { - toPrimitive[inputName].push(sourceNodeId) + const existing = toPrimitive[inputName] + if (Array.isArray(existing)) { + existing.push(sourceNodeId) + } else if (typeof existing === 'number') { + toPrimitive[inputName] = [existing, sourceNodeId] + } else { + toPrimitive[inputName] = sourceNodeId } - toPrimitive[inputName] = sourceNodeId - // @ts-expect-error fixme ts strict error let toWidget = this.primitiveToWidget[sourceNodeId] if (!toWidget) { - // @ts-expect-error fixme ts strict error toWidget = this.primitiveToWidget[sourceNodeId] = [] } toWidget.push({ nodeId: targetNodeId, inputName }) } } - // @ts-expect-error fixme ts strict error - processInputSlots(inputs, node, slots, linksTo, inputMap, seenInputs) { - // @ts-expect-error fixme ts strict error - this.nodeInputs[node.index] = {} + processInputSlots( + inputs: Record, + node: GroupNodeData, + slots: string[], + linksTo: Record, + inputMap: Record, + seenInputs: Record + ) { + const nodeIdx = node.index ?? -1 + this.nodeInputs[nodeIdx] = {} for (let i = 0; i < slots.length; i++) { const inputName = slots[i] if (linksTo[i]) { @@ -591,31 +709,25 @@ export class GroupNodeConfig { inputs[inputName] ) - // @ts-expect-error fixme ts strict error - this.nodeInputs[node.index][inputName] = name + this.nodeInputs[nodeIdx][inputName] = name if (customConfig?.visible === false) continue - // @ts-expect-error fixme ts strict error - this.nodeDef.input.required[name] = config + if (this.nodeDef?.input?.required) { + // @ts-expect-error legacy dynamic input assignment + this.nodeDef.input.required[name] = config + } inputMap[i] = this.inputCount++ } } processConvertedWidgets( - // @ts-expect-error fixme ts strict error - inputs, - // @ts-expect-error fixme ts strict error - node, - // @ts-expect-error fixme ts strict error - slots, - // @ts-expect-error fixme ts strict error - converted, - // @ts-expect-error fixme ts strict error - linksTo, - // @ts-expect-error fixme ts strict error - inputMap, - // @ts-expect-error fixme ts strict error - seenInputs + inputs: Record, + node: GroupNodeData, + slots: string[], + converted: Map, + linksTo: Record, + inputMap: Record, + seenInputs: Record ) { // Add converted widgets sorted into their index order (ordered as they were converted) so link ids match up const convertedSlots = [...converted.keys()] @@ -623,11 +735,12 @@ export class GroupNodeConfig { .map((k) => converted.get(k)) for (let i = 0; i < convertedSlots.length; i++) { const inputName = convertedSlots[i] + if (!inputName) continue if (linksTo[slots.length + i]) { this.checkPrimitiveConnection( linksTo[slots.length + i], inputName, - inputs + inputs as Record ) // This input is linked so we can skip it continue @@ -637,34 +750,35 @@ export class GroupNodeConfig { node, inputName, seenInputs, - inputs[inputName], + inputs[inputName] as unknown[], { defaultInput: true } ) - // @ts-expect-error fixme ts strict error - this.nodeDef.input.required[name] = config - // @ts-expect-error fixme ts strict error + if (this.nodeDef?.input?.required) { + // @ts-expect-error legacy dynamic input assignment + this.nodeDef.input.required[name] = config + } this.newToOldWidgetMap[name] = { node, inputName } - // @ts-expect-error fixme ts strict error - if (!this.oldToNewWidgetMap[node.index]) { - // @ts-expect-error fixme ts strict error - this.oldToNewWidgetMap[node.index] = {} + const nodeIndex = node.index ?? -1 + if (!this.oldToNewWidgetMap[nodeIndex]) { + this.oldToNewWidgetMap[nodeIndex] = {} } - // @ts-expect-error fixme ts strict error - this.oldToNewWidgetMap[node.index][inputName] = name + this.oldToNewWidgetMap[nodeIndex][inputName] = name inputMap[slots.length + i] = this.inputCount++ } } - #convertedToProcess = [] - // @ts-expect-error fixme ts strict error - processNodeInputs(node, seenInputs, inputs) { - // @ts-expect-error fixme ts strict error - const inputMapping = [] + #convertedToProcess: (() => void)[] = [] + processNodeInputs( + node: GroupNodeData, + seenInputs: Record, + inputs: Record + ) { + const inputMapping: unknown[] = [] const inputNames = Object.keys(inputs) if (!inputNames.length) return @@ -675,14 +789,20 @@ export class GroupNodeConfig { inputNames, seenInputs ) - // @ts-expect-error fixme ts strict error - const linksTo = this.linksTo[node.index] ?? {} - // @ts-expect-error fixme ts strict error - const inputMap = (this.oldToNewInputMap[node.index] = {}) - this.processInputSlots(inputs, node, slots, linksTo, inputMap, seenInputs) + const nodeIndex = node.index ?? -1 + const linksTo = this.linksTo[nodeIndex] ?? {} + const inputMap: Record = (this.oldToNewInputMap[nodeIndex] = + {}) + this.processInputSlots( + inputs as unknown as Record, + node, + slots, + linksTo, + inputMap, + seenInputs + ) // Converted inputs have to be processed after all other nodes as they'll be at the end of the list - // @ts-expect-error fixme ts strict error this.#convertedToProcess.push(() => this.processConvertedWidgets( inputs, @@ -695,79 +815,91 @@ export class GroupNodeConfig { ) ) - // @ts-expect-error fixme ts strict error return inputMapping } - // @ts-expect-error fixme ts strict error - processNodeOutputs(node, seenOutputs, def) { - // @ts-expect-error fixme ts strict error - const oldToNew = (this.oldToNewOutputMap[node.index] = {}) + processNodeOutputs( + node: GroupNodeData, + seenOutputs: Record, + def: GroupNodeDef | ComfyNodeDef + ) { + const nodeIndex = node.index ?? -1 + const oldToNew: Record = (this.oldToNewOutputMap[ + nodeIndex + ] = {}) + const defOutput = def.output ?? [] // Add outputs - for (let outputId = 0; outputId < def.output.length; outputId++) { - // @ts-expect-error fixme ts strict error - const linksFrom = this.linksFrom[node.index] + for (let outputId = 0; outputId < defOutput.length; outputId++) { + const linksFrom = this.linksFrom[nodeIndex] // If this output is linked internally we flag it to hide const hasLink = - // @ts-expect-error fixme ts strict error - linksFrom?.[outputId] && !this.externalFrom[node.index]?.[outputId] - const customConfig = - this.nodeData.config?.[node.index]?.output?.[outputId] + linksFrom?.[outputId] && !this.externalFrom[nodeIndex]?.[outputId] + const outputConfig = this.nodeData.config?.[node.index ?? -1] as + | NodeConfigEntry + | undefined + const customConfig = outputConfig?.output?.[outputId] const visible = customConfig?.visible ?? !hasLink this.outputVisibility.push(visible) if (!visible) { continue } - // @ts-expect-error fixme ts strict error - oldToNew[outputId] = this.nodeDef.output.length - // @ts-expect-error fixme ts strict error - this.newToOldOutputMap[this.nodeDef.output.length] = { - node, - slot: outputId + if (this.nodeDef?.output) { + oldToNew[outputId] = this.nodeDef.output.length + this.newToOldOutputMap[this.nodeDef.output.length] = { + node, + slot: outputId + } + // @ts-expect-error legacy dynamic output type assignment + this.nodeDef.output.push(defOutput[outputId]) + this.nodeDef.output_is_list?.push( + def.output_is_list?.[outputId] ?? false + ) } - // @ts-expect-error fixme ts strict error - this.nodeDef.output.push(def.output[outputId]) - // @ts-expect-error fixme ts strict error - this.nodeDef.output_is_list.push(def.output_is_list[outputId]) - let label = customConfig?.name + let label: string | undefined = customConfig?.name if (!label) { - label = def.output_name?.[outputId] ?? def.output[outputId] - // @ts-expect-error fixme ts strict error - const output = node.outputs.find((o) => o.name === label) + const outputVal = defOutput[outputId] + label = + def.output_name?.[outputId] ?? + (typeof outputVal === 'string' ? outputVal : undefined) + const output = node.outputs?.find((o) => o.name === label) if (output?.label) { label = output.label } } - let name = label + let name: string = String(label ?? `output_${outputId}`) if (name in seenOutputs) { const prefix = `${node.title ?? node.type} ` - name = `${prefix}${label}` + name = `${prefix}${label ?? outputId}` if (name in seenOutputs) { - name = `${prefix}${node.index} ${label}` + name = `${prefix}${node.index} ${label ?? outputId}` } } seenOutputs[name] = 1 - // @ts-expect-error fixme ts strict error - this.nodeDef.output_name.push(name) + this.nodeDef?.output_name?.push(name) } } - // @ts-expect-error fixme ts strict error - static async registerFromWorkflow(groupNodes, missingNodeTypes) { + static async registerFromWorkflow( + groupNodes: Record, + missingNodeTypes: ( + | string + | { type: string; hint?: string; action?: unknown } + )[] + ) { for (const g in groupNodes) { const groupData = groupNodes[g] let hasMissing = false for (const n of groupData.nodes) { // Find missing node types - if (!(n.type in LiteGraph.registered_node_types)) { + if (!n.type || !(n.type in LiteGraph.registered_node_types)) { missingNodeTypes.push({ - type: n.type, + type: n.type ?? 'unknown', hint: ` (In group node '${PREFIX}${SEPARATOR}${g}')` }) @@ -775,12 +907,12 @@ export class GroupNodeConfig { type: `${PREFIX}${SEPARATOR}` + g, action: { text: 'Remove from workflow', - // @ts-expect-error fixme ts strict error - callback: (e) => { + callback: (e: MouseEvent) => { delete groupNodes[g] - e.target.textContent = 'Removed' - e.target.style.pointerEvents = 'none' - e.target.style.opacity = 0.7 + const target = e.target as HTMLElement + target.textContent = 'Removed' + target.style.pointerEvents = 'none' + target.style.opacity = '0.7' } } }) @@ -799,12 +931,12 @@ export class GroupNodeConfig { export class GroupNodeHandler { node: LGraphNode - groupData: any - innerNodes: any + groupData: GroupNodeConfig + innerNodes: LGraphNode[] | null = null constructor(node: LGraphNode) { this.node = node - this.groupData = node.constructor?.nodeData?.[GROUP] + this.groupData = node.constructor?.nodeData?.[GROUP] as GroupNodeConfig this.node.setInnerNodes = (innerNodes) => { this.innerNodes = innerNodes @@ -819,60 +951,63 @@ export class GroupNodeHandler { for (const w of innerNode.widgets ?? []) { if (w.type === 'converted-widget') { + // @ts-expect-error legacy widget property for converted widgets w.serializeValue = w.origSerializeValue } } innerNode.index = innerNodeIndex - // @ts-expect-error fixme ts strict error - innerNode.getInputNode = (slot) => { + innerNode.getInputNode = (slot: number) => { // Check if this input is internal or external - const externalSlot = - this.groupData.oldToNewInputMap[innerNode.index]?.[slot] + const nodeIdx = innerNode.index ?? 0 + const externalSlot = this.groupData.oldToNewInputMap[nodeIdx]?.[slot] if (externalSlot != null) { return this.node.getInputNode(externalSlot) } // Internal link - const innerLink = this.groupData.linksTo[innerNode.index]?.[slot] + const innerLink = this.groupData.linksTo[nodeIdx]?.[slot] if (!innerLink) return null - const inputNode = innerNodes[innerLink[0]] + const linkSrcIdx = innerLink[0] + if (linkSrcIdx == null) return null + const inputNode = innerNodes[Number(linkSrcIdx)] // Primitives will already apply their values if (inputNode.type === 'PrimitiveNode') return null return inputNode } - // @ts-expect-error fixme ts strict error - innerNode.getInputLink = (slot) => { - const externalSlot = - this.groupData.oldToNewInputMap[innerNode.index]?.[slot] + // @ts-expect-error returns partial link object, not full LLink + innerNode.getInputLink = (slot: number) => { + const nodeIdx = innerNode.index ?? 0 + const externalSlot = this.groupData.oldToNewInputMap[nodeIdx]?.[slot] if (externalSlot != null) { // The inner node is connected via the group node inputs const linkId = this.node.inputs[externalSlot].link - // @ts-expect-error fixme ts strict error - let link = app.rootGraph.links[linkId] + if (linkId == null) return null + const existingLink = app.rootGraph.links[linkId] + if (!existingLink) return null // Use the outer link, but update the target to the inner node - link = { - ...link, + return { + ...existingLink, target_id: innerNode.id, target_slot: +slot } - return link } - let link = this.groupData.linksTo[innerNode.index]?.[slot] - if (!link) return null + const innerLink = this.groupData.linksTo[nodeIdx]?.[slot] + if (!innerLink) return null + const linkSrcIdx = innerLink[0] + if (linkSrcIdx == null) return null // Use the inner link, but update the origin node to be inner node id - link = { - origin_id: innerNodes[link[0]].id, - origin_slot: link[1], + return { + origin_id: innerNodes[Number(linkSrcIdx)].id, + origin_slot: innerLink[1], target_id: innerNode.id, target_slot: +slot } - return link } } } @@ -882,7 +1017,9 @@ export class GroupNodeHandler { // @ts-expect-error Can this be removed? Or replaced with: LLink.create(link.asSerialisable()) link = { ...link } const output = this.groupData.newToOldOutputMap[link.origin_slot] - let innerNode = this.innerNodes[output.node.index] + if (!output || !this.innerNodes) return null + const nodeIdx = output.node.index ?? 0 + let innerNode: LGraphNode | null = this.innerNodes[nodeIdx] let l while (innerNode?.type === 'Reroute') { l = innerNode.getInputLink(0) @@ -893,7 +1030,11 @@ export class GroupNodeHandler { return null } - if (l && GroupNodeHandler.isGroupNode(innerNode)) { + if ( + l && + GroupNodeHandler.isGroupNode(innerNode) && + innerNode.updateLink + ) { return innerNode.updateLink(l) } @@ -917,20 +1058,19 @@ export class GroupNodeHandler { visited.add(this.node) if (!this.innerNodes) { - // @ts-expect-error fixme ts strict error - this.node.setInnerNodes( - // @ts-expect-error fixme ts strict error - this.groupData.nodeData.nodes.map((n, i) => { + const createdNodes = this.groupData.nodeData.nodes + .map((n, i) => { + if (!n.type) return null const innerNode = LiteGraph.createNode(n.type) - // @ts-expect-error fixme ts strict error + if (!innerNode) return null + // @ts-expect-error legacy node data format used for configure innerNode.configure(n) - // @ts-expect-error fixme ts strict error innerNode.id = `${this.node.id}:${i}` - // @ts-expect-error fixme ts strict error innerNode.graph = this.node.graph return innerNode }) - ) + .filter((n): n is LGraphNode => n !== null) + this.node.setInnerNodes?.(createdNodes) } this.updateInnerWidgets() @@ -942,11 +1082,12 @@ export class GroupNodeHandler { subgraphNodePath.at(-1) ) ?? undefined) as SubgraphNode | undefined - for (const node of this.innerNodes) { + for (const node of this.innerNodes ?? []) { node.graph ??= this.node.graph // Create minimal DTOs rather than cloning the node const currentId = String(node.id) + // @ts-expect-error temporary id reassignment for DTO creation node.id = currentId.split(':').at(-1) const aVeryRealNode = new ExecutableGroupNodeChildDTO( node, @@ -962,95 +1103,87 @@ export class GroupNodeHandler { return nodes } - // @ts-expect-error fixme ts strict error + // @ts-expect-error recreate returns null if creation fails this.node.recreate = async () => { const id = this.node.id const sz = this.node.size - // @ts-expect-error fixme ts strict error - const nodes = this.node.convertToNodes() + const nodes = ( + this.node as LGraphNode & { convertToNodes?: () => LGraphNode[] } + ).convertToNodes?.() + if (!nodes) return null const groupNode = LiteGraph.createNode(this.node.type) - // @ts-expect-error fixme ts strict error + if (!groupNode) return null groupNode.id = id // Reuse the existing nodes for this instance - // @ts-expect-error fixme ts strict error - groupNode.setInnerNodes(nodes) - // @ts-expect-error fixme ts strict error - groupNode[GROUP].populateWidgets() - // @ts-expect-error fixme ts strict error + groupNode.setInnerNodes?.(nodes) + const handler = GroupNodeHandler.getHandler(groupNode) + handler?.populateWidgets() app.rootGraph.add(groupNode) - // @ts-expect-error fixme ts strict error - groupNode.setSize([ - // @ts-expect-error fixme ts strict error + groupNode.setSize?.([ Math.max(groupNode.size[0], sz[0]), - // @ts-expect-error fixme ts strict error Math.max(groupNode.size[1], sz[1]) ]) // Remove all converted nodes and relink them const builder = new GroupNodeBuilder(nodes) const nodeData = builder.getNodeData() - // @ts-expect-error fixme ts strict error - groupNode[GROUP].groupData.nodeData.links = nodeData.links - // @ts-expect-error fixme ts strict error - groupNode[GROUP].replaceNodes(nodes) + if (handler) { + handler.groupData.nodeData.links = nodeData.links + handler.replaceNodes(nodes) + } return groupNode } - - // @ts-expect-error fixme ts strict error - this.node.convertToNodes = () => { + ;( + this.node as LGraphNode & { convertToNodes: () => LGraphNode[] } + ).convertToNodes = () => { const addInnerNodes = () => { // Clone the node data so we dont mutate it for other nodes const c = { ...this.groupData.nodeData } c.nodes = [...c.nodes] - // @ts-expect-error fixme ts strict error - const innerNodes = this.node.getInnerNodes() - let ids = [] + // @ts-expect-error getInnerNodes called without args in legacy conversion code + const innerNodes = this.node.getInnerNodes?.() + const ids: (string | number)[] = [] for (let i = 0; i < c.nodes.length; i++) { - let id = innerNodes?.[i]?.id + let id: string | number | undefined = innerNodes?.[i]?.id // Use existing IDs if they are set on the inner nodes - // @ts-expect-error id can be string or number - if (id == null || isNaN(id)) { - // @ts-expect-error fixme ts strict error + if (id == null || (typeof id === 'number' && isNaN(id))) { id = undefined } else { ids.push(id) } + // @ts-expect-error adding id to node copy for serialization c.nodes[i] = { ...c.nodes[i], id } } deserialiseAndCreate(JSON.stringify(c), app.canvas) const [x, y] = this.node.pos - let top - let left + let top: number | undefined + let left: number | undefined // Configure nodes with current widget data const selectedIds = ids.length ? ids : Object.keys(app.canvas.selected_nodes) - const newNodes = [] + const newNodes: LGraphNode[] = [] for (let i = 0; i < selectedIds.length; i++) { const id = selectedIds[i] const newNode = app.rootGraph.getNodeById(id) - const innerNode = innerNodes[i] + const innerNode = innerNodes?.[i] + if (!newNode) continue newNodes.push(newNode) - // @ts-expect-error fixme ts strict error if (left == null || newNode.pos[0] < left) { - // @ts-expect-error fixme ts strict error left = newNode.pos[0] } - // @ts-expect-error fixme ts strict error if (top == null || newNode.pos[1] < top) { - // @ts-expect-error fixme ts strict error top = newNode.pos[1] } - // @ts-expect-error fixme ts strict error - if (!newNode.widgets) continue + if (!newNode.widgets || !innerNode) continue - // @ts-expect-error fixme ts strict error - const map = this.groupData.oldToNewWidgetMap[innerNode.index] + // @ts-expect-error index property access on ExecutableLGraphNode + const map = this.groupData.oldToNewWidgetMap[innerNode.index ?? 0] if (map) { const widgets = Object.keys(map) @@ -1058,37 +1191,32 @@ export class GroupNodeHandler { const newName = map[oldName] if (!newName) continue - // @ts-expect-error fixme ts strict error - const widgetIndex = this.node.widgets.findIndex( - (w) => w.name === newName - ) + const widgetIndex = + this.node.widgets?.findIndex((w) => w.name === newName) ?? -1 if (widgetIndex === -1) continue // Populate the main and any linked widgets if (innerNode.type === 'PrimitiveNode') { - // @ts-expect-error fixme ts strict error for (let i = 0; i < newNode.widgets.length; i++) { - // @ts-expect-error fixme ts strict error - newNode.widgets[i].value = - // @ts-expect-error fixme ts strict error - this.node.widgets[widgetIndex + i].value + const srcWidget = this.node.widgets?.[widgetIndex + i] + if (srcWidget) { + newNode.widgets[i].value = srcWidget.value + } } } else { - // @ts-expect-error fixme ts strict error - const outerWidget = this.node.widgets[widgetIndex] - // @ts-expect-error fixme ts strict error + const outerWidget = this.node.widgets?.[widgetIndex] const newWidget = newNode.widgets.find( (w) => w.name === oldName ) - if (!newWidget) continue + if (!newWidget || !outerWidget) continue newWidget.value = outerWidget.value - // @ts-expect-error fixme ts strict error - for (let w = 0; w < outerWidget.linkedWidgets?.length; w++) { - // @ts-expect-error fixme ts strict error - newWidget.linkedWidgets[w].value = - // @ts-expect-error fixme ts strict error - outerWidget.linkedWidgets[w].value + const linkedWidgets = outerWidget.linkedWidgets ?? [] + for (let w = 0; w < linkedWidgets.length; w++) { + const newLinked = newWidget.linkedWidgets?.[w] + if (newLinked && linkedWidgets[w]) { + newLinked.value = linkedWidgets[w].value + } } } } @@ -1097,23 +1225,21 @@ export class GroupNodeHandler { // Shift each node for (const newNode of newNodes) { - // @ts-expect-error fixme ts strict error - newNode.pos[0] -= left - x - // @ts-expect-error fixme ts strict error - newNode.pos[1] -= top - y + newNode.pos[0] -= (left ?? 0) - x + newNode.pos[1] -= (top ?? 0) - y } return { newNodes, selectedIds } } - // @ts-expect-error fixme ts strict error - const reconnectInputs = (selectedIds) => { + const reconnectInputs = (selectedIds: (string | number)[]) => { for (const innerNodeIndex in this.groupData.oldToNewInputMap) { - const id = selectedIds[innerNodeIndex] + const id = selectedIds[Number(innerNodeIndex)] const newNode = app.rootGraph.getNodeById(id) - const map = this.groupData.oldToNewInputMap[innerNodeIndex] + if (!newNode) continue + const map = this.groupData.oldToNewInputMap[Number(innerNodeIndex)] for (const innerInputId in map) { - const groupSlotId = map[innerInputId] + const groupSlotId = map[Number(innerInputId)] if (groupSlotId == null) continue const slot = node.inputs[groupSlotId] if (slot.link == null) continue @@ -1121,14 +1247,12 @@ export class GroupNodeHandler { if (!link) continue // connect this node output to the input of another node const originNode = app.rootGraph.getNodeById(link.origin_id) - // @ts-expect-error fixme ts strict error - originNode.connect(link.origin_slot, newNode, +innerInputId) + originNode?.connect(link.origin_slot, newNode, +innerInputId) } } } - // @ts-expect-error fixme ts strict error - const reconnectOutputs = (selectedIds) => { + const reconnectOutputs = (selectedIds: (string | number)[]) => { for ( let groupOutputId = 0; groupOutputId < node.outputs?.length; @@ -1139,13 +1263,16 @@ export class GroupNodeHandler { const links = [...output.links] for (const l of links) { const slot = this.groupData.newToOldOutputMap[groupOutputId] + if (!slot) continue const link = app.rootGraph.links[l] + if (!link) continue const targetNode = app.rootGraph.getNodeById(link.target_id) const newNode = app.rootGraph.getNodeById( - selectedIds[slot.node.index] + selectedIds[slot.node.index ?? 0] ) - // @ts-expect-error fixme ts strict error - newNode.connect(slot.slot, targetNode, link.target_slot) + if (targetNode) { + newNode?.connect(slot.slot, targetNode, link.target_slot) + } } } } @@ -1165,10 +1292,9 @@ export class GroupNodeHandler { } const getExtraMenuOptions = this.node.getExtraMenuOptions - // @ts-expect-error Should pass patched return value getExtraMenuOptions - this.node.getExtraMenuOptions = function (_, options) { - // @ts-expect-error fixme ts strict error - getExtraMenuOptions?.apply(this, arguments) + const handlerNode = this.node + this.node.getExtraMenuOptions = function (_canvas, options) { + getExtraMenuOptions?.call(this, _canvas, options) let optionIndex = options.findIndex((o) => o?.content === 'Outputs') if (optionIndex === -1) optionIndex = options.length @@ -1179,10 +1305,14 @@ export class GroupNodeHandler { null, { content: 'Convert to nodes', - // @ts-expect-error - callback: () => { - // @ts-expect-error fixme ts strict error - return this.convertToNodes() + // @ts-expect-error async callback not expected by legacy menu API + callback: async () => { + const convertFn = ( + handlerNode as LGraphNode & { + convertToNodes?: () => LGraphNode[] + } + ).convertToNodes + return convertFn?.() } }, { @@ -1190,13 +1320,15 @@ export class GroupNodeHandler { callback: () => manageGroupNodes(this.type) } ) + // Return empty array to satisfy type signature without triggering + // LGraphCanvas concatenation (which only happens when length > 0) + return [] } // Draw custom collapse icon to identity this as a group const onDrawTitleBox = this.node.onDrawTitleBox - this.node.onDrawTitleBox = function (ctx, height) { - // @ts-expect-error fixme ts strict error - onDrawTitleBox?.apply(this, arguments) + this.node.onDrawTitleBox = function (ctx, height, size, scale) { + onDrawTitleBox?.call(this, ctx, height, size, scale) const fill = ctx.fillStyle ctx.beginPath() @@ -1218,17 +1350,19 @@ export class GroupNodeHandler { // Draw progress label const onDrawForeground = node.onDrawForeground const groupData = this.groupData.nodeData - node.onDrawForeground = function (ctx) { - // @ts-expect-error fixme ts strict error - onDrawForeground?.apply?.(this, arguments) + node.onDrawForeground = function (ctx, canvas, canvasElement) { + onDrawForeground?.call(this, ctx, canvas, canvasElement) const progressState = useExecutionStore().nodeProgressStates[this.id] if ( progressState && progressState.state === 'running' && this.runningInternalNodeId !== null ) { - // @ts-expect-error fixme ts strict error - const n = groupData.nodes[this.runningInternalNodeId] + const nodeIdx = + typeof this.runningInternalNodeId === 'number' + ? this.runningInternalNodeId + : parseInt(String(this.runningInternalNodeId), 10) + const n = groupData.nodes[nodeIdx] as { title?: string; type?: string } if (!n) return const message = `Running ${n.title || n.type} (${this.runningInternalNodeId}/${groupData.nodes.length})` ctx.save() @@ -1254,26 +1388,28 @@ export class GroupNodeHandler { // Flag this node as needing to be reset const onExecutionStart = this.node.onExecutionStart this.node.onExecutionStart = function () { - // @ts-expect-error fixme ts strict error - this.resetExecution = true - // @ts-expect-error fixme ts strict error - return onExecutionStart?.apply(this, arguments) + ;(this as LGraphNode & { resetExecution?: boolean }).resetExecution = true + return onExecutionStart?.call(this) } - const self = this const onNodeCreated = this.node.onNodeCreated + const handlerGroupData = this.groupData this.node.onNodeCreated = function () { if (!this.widgets) { return } - const config = self.groupData.nodeData.config + const config = handlerGroupData.nodeData.config as + | Record + | undefined if (config) { for (const n in config) { const inputs = config[n]?.input + if (!inputs) continue for (const w in inputs) { - if (inputs[w].visible !== false) continue - const widgetName = self.groupData.oldToNewWidgetMap[n][w] - const widget = this.widgets.find((w) => w.name === widgetName) + if (inputs[w]?.visible !== false) continue + const widgetName = + handlerGroupData.oldToNewWidgetMap[Number(n)]?.[w] + const widget = this.widgets.find((wg) => wg.name === widgetName) if (widget) { widget.type = 'hidden' widget.computeSize = () => [0, -4] @@ -1282,91 +1418,88 @@ export class GroupNodeHandler { } } - // @ts-expect-error fixme ts strict error - return onNodeCreated?.apply(this, arguments) + return onNodeCreated?.call(this) } - // @ts-expect-error fixme ts strict error - function handleEvent(type, getId, getEvent) { - // @ts-expect-error fixme ts strict error - const handler = ({ detail }) => { + type EventDetail = { display_node?: string; node?: string } | string + const handleEvent = ( + type: string, + getId: (detail: EventDetail) => string | undefined, + getEvent: ( + detail: EventDetail, + id: string, + node: LGraphNode + ) => EventDetail + ) => { + const handler = ({ detail }: CustomEvent) => { const id = getId(detail) if (!id) return - const node = app.rootGraph.getNodeById(id) - if (node) return + const existingNode = app.rootGraph.getNodeById(id) + if (existingNode) return - // @ts-expect-error fixme ts strict error - const innerNodeIndex = this.innerNodes?.findIndex((n) => n.id == id) + const innerNodeIndex = + this.innerNodes?.findIndex((n) => n.id == id) ?? -1 if (innerNodeIndex > -1) { - // @ts-expect-error fixme ts strict error - this.node.runningInternalNodeId = innerNodeIndex + ;( + this.node as LGraphNode & { runningInternalNodeId?: number } + ).runningInternalNodeId = innerNodeIndex api.dispatchCustomEvent( - type, - // @ts-expect-error fixme ts strict error - getEvent(detail, `${this.node.id}`, this.node) + type as 'executing', + getEvent(detail, `${this.node.id}`, this.node) as string ) } } - api.addEventListener(type, handler) + api.addEventListener( + type as 'executing' | 'executed', + handler as EventListener + ) return handler } - const executing = handleEvent.call( - this, + const executing = handleEvent( 'executing', - // @ts-expect-error fixme ts strict error - (d) => d, - // @ts-expect-error fixme ts strict error - (_, id) => id + (d) => (typeof d === 'string' ? d : undefined), + (_d, id) => id ) - const executed = handleEvent.call( - this, + const executed = handleEvent( 'executed', - // @ts-expect-error fixme ts strict error - (d) => d?.display_node || d?.node, - // @ts-expect-error fixme ts strict error + (d) => (typeof d === 'object' ? d?.display_node || d?.node : undefined), (d, id, node) => ({ - ...d, + ...(typeof d === 'object' ? d : {}), node: id, display_node: id, - merge: !node.resetExecution + merge: !(node as LGraphNode & { resetExecution?: boolean }) + .resetExecution }) ) const onRemoved = node.onRemoved this.node.onRemoved = function () { - // @ts-expect-error fixme ts strict error - onRemoved?.apply(this, arguments) - // api.removeEventListener('progress_state', progress_state) - api.removeEventListener('executing', executing) - api.removeEventListener('executed', executed) + onRemoved?.call(this) + api.removeEventListener('executing', executing as EventListener) + api.removeEventListener('executed', executed as EventListener) } this.node.refreshComboInNode = (defs) => { // Update combo widget options for (const widgetName in this.groupData.newToOldWidgetMap) { - // @ts-expect-error fixme ts strict error - const widget = this.node.widgets.find((w) => w.name === widgetName) + const widget = this.node.widgets?.find((w) => w.name === widgetName) if (widget?.type === 'combo') { const old = this.groupData.newToOldWidgetMap[widgetName] + if (!old.node.type) continue const def = defs[old.node.type] const input = def?.input?.required?.[old.inputName] ?? def?.input?.optional?.[old.inputName] if (!input) continue - widget.options.values = input[0] + widget.options.values = input[0] as unknown[] - if ( - old.inputName !== 'image' && - // @ts-expect-error Widget values - !widget.options.values.includes(widget.value) - ) { - // @ts-expect-error fixme ts strict error - widget.value = widget.options.values[0] - // @ts-expect-error fixme ts strict error - widget.callback(widget.value) + const values = widget.options.values as unknown[] + if (old.inputName !== 'image' && !values.includes(widget.value)) { + widget.value = values[0] as typeof widget.value + widget.callback?.(widget.value) } } } @@ -1374,22 +1507,30 @@ export class GroupNodeHandler { } updateInnerWidgets() { + if (!this.innerNodes) return for (const newWidgetName in this.groupData.newToOldWidgetMap) { - // @ts-expect-error fixme ts strict error - const newWidget = this.node.widgets.find((w) => w.name === newWidgetName) + const newWidget = this.node.widgets?.find((w) => w.name === newWidgetName) if (!newWidget) continue const newValue = newWidget.value const old = this.groupData.newToOldWidgetMap[newWidgetName] - let innerNode = this.innerNodes[old.node.index] + const nodeIdx = old.node.index ?? 0 + const innerNode = this.innerNodes[nodeIdx] + if (!innerNode) continue if (innerNode.type === 'PrimitiveNode') { + // @ts-expect-error primitiveValue is a custom property on PrimitiveNode innerNode.primitiveValue = newValue - const primitiveLinked = this.groupData.primitiveToWidget[old.node.index] + const primitiveLinked = this.groupData.primitiveToWidget[nodeIdx] for (const linked of primitiveLinked ?? []) { - const node = this.innerNodes[linked.nodeId] - // @ts-expect-error fixme ts strict error - const widget = node.widgets.find((w) => w.name === linked.inputName) + const linkedNodeId = + typeof linked.nodeId === 'number' + ? linked.nodeId + : Number(linked.nodeId) + const linkedNode = this.innerNodes[linkedNodeId] + const widget = linkedNode?.widgets?.find( + (w) => w.name === linked.inputName + ) if (widget) { widget.value = newValue @@ -1397,15 +1538,17 @@ export class GroupNodeHandler { } continue } else if (innerNode.type === 'Reroute') { - const rerouteLinks = this.groupData.linksFrom[old.node.index] + const rerouteLinks = this.groupData.linksFrom[nodeIdx] if (rerouteLinks) { - for (const [_, , targetNodeId, targetSlot] of rerouteLinks['0']) { - const node = this.innerNodes[targetNodeId] - const input = node.inputs[targetSlot] - if (input.widget) { - const widget = node.widgets?.find( - // @ts-expect-error fixme ts strict error - (w) => w.name === input.widget.name + for (const [, , targetNodeId, targetSlot] of rerouteLinks[0] ?? []) { + if (targetNodeId == null || targetSlot == null) continue + const targetNode = this.innerNodes[Number(targetNodeId)] + if (!targetNode) continue + const input = targetNode.inputs?.[Number(targetSlot)] + if (input?.widget) { + const widgetName = input.widget.name + const widget = targetNode.widgets?.find( + (w) => w.name === widgetName ) if (widget) { widget.value = newValue @@ -1415,7 +1558,6 @@ export class GroupNodeHandler { } } - // @ts-expect-error fixme ts strict error const widget = innerNode.widgets?.find((w) => w.name === old.inputName) if (widget) { widget.value = newValue @@ -1423,57 +1565,73 @@ export class GroupNodeHandler { } } - // @ts-expect-error fixme ts strict error - populatePrimitive(_node, nodeId, oldName) { + populatePrimitive( + _node: GroupNodeData, + nodeId: number, + oldName: string + ): boolean { // Converted widget, populate primitive if linked const primitiveId = this.groupData.widgetToPrimitive[nodeId]?.[oldName] - if (primitiveId == null) return + if (primitiveId == null) return false const targetWidgetName = - this.groupData.oldToNewWidgetMap[primitiveId]['value'] - // @ts-expect-error fixme ts strict error - const targetWidgetIndex = this.node.widgets.findIndex( - (w) => w.name === targetWidgetName - ) - if (targetWidgetIndex > -1) { - const primitiveNode = this.innerNodes[primitiveId] + this.groupData.oldToNewWidgetMap[ + Array.isArray(primitiveId) ? primitiveId[0] : primitiveId + ]?.['value'] + if (!targetWidgetName) return false + const targetWidgetIndex = + this.node.widgets?.findIndex((w) => w.name === targetWidgetName) ?? -1 + if (targetWidgetIndex > -1 && this.innerNodes) { + const primIdx = Array.isArray(primitiveId) ? primitiveId[0] : primitiveId + const primitiveNode = this.innerNodes[primIdx] + if (!primitiveNode?.widgets) return true let len = primitiveNode.widgets.length if ( len - 1 !== - // @ts-expect-error fixme ts strict error - this.node.widgets[targetWidgetIndex].linkedWidgets?.length + (this.node.widgets?.[targetWidgetIndex]?.linkedWidgets?.length ?? 0) ) { // Fallback handling for if some reason the primitive has a different number of widgets // we dont want to overwrite random widgets, better to leave blank len = 1 } for (let i = 0; i < len; i++) { - // @ts-expect-error fixme ts strict error - this.node.widgets[targetWidgetIndex + i].value = - primitiveNode.widgets[i].value + const targetWidget = this.node.widgets?.[targetWidgetIndex + i] + const srcWidget = primitiveNode.widgets[i] + if (targetWidget && srcWidget) { + targetWidget.value = srcWidget.value + } } } return true } - // @ts-expect-error fixme ts strict error - populateReroute(node, nodeId, map) { + populateReroute( + node: GroupNodeData, + nodeId: number, + map: Record + ) { if (node.type !== 'Reroute') return const link = this.groupData.linksFrom[nodeId]?.[0]?.[0] if (!link) return - const [, , targetNodeId, targetNodeSlot] = link - const targetNode = this.groupData.nodeData.nodes[targetNodeId] - const inputs = targetNode.inputs - const targetWidget = inputs?.[targetNodeSlot]?.widget + const targetNodeId = link[2] + const targetNodeSlot = link[3] + if (targetNodeId == null || targetNodeSlot == null) return + const targetNode = this.groupData.nodeData.nodes[Number(targetNodeId)] as + | GroupNodeData + | undefined + const inputs = targetNode?.inputs + const targetWidget = (inputs as GroupNodeInput[] | undefined)?.[ + Number(targetNodeSlot) + ]?.widget if (!targetWidget) return - const offset = inputs.length - (targetNode.widgets_values?.length ?? 0) - const v = targetNode.widgets_values?.[targetNodeSlot - offset] + const offset = + (inputs?.length ?? 0) - (targetNode?.widgets_values?.length ?? 0) + const v = targetNode?.widgets_values?.[Number(targetNodeSlot) - offset] if (v == null) return const widgetName = Object.values(map)[0] - // @ts-expect-error fixme ts strict error - const widget = this.node.widgets.find((w) => w.name === widgetName) + const widget = this.node.widgets?.find((w) => w.name === widgetName) if (widget) { widget.value = v } @@ -1487,7 +1645,7 @@ export class GroupNodeHandler { nodeId < this.groupData.nodeData.nodes.length; nodeId++ ) { - const node = this.groupData.nodeData.nodes[nodeId] + const node = this.groupData.nodeData.nodes[nodeId] as GroupNodeData const map = this.groupData.oldToNewWidgetMap[nodeId] ?? {} const widgets = Object.keys(map) @@ -1511,8 +1669,7 @@ export class GroupNodeHandler { widgetIndex === -1 ) { // Find the inner widget and shift by the number of linked widgets as they will have been removed too - const innerWidget = this.innerNodes[nodeId].widgets?.find( - // @ts-expect-error fixme ts strict error + const innerWidget = this.innerNodes?.[nodeId]?.widgets?.find( (w) => w.name === oldName ) linkedShift += innerWidget?.linkedWidgets?.length ?? 0 @@ -1522,20 +1679,22 @@ export class GroupNodeHandler { } // Populate the main and any linked widget - mainWidget.value = node.widgets_values[i + linkedShift] - // @ts-expect-error fixme ts strict error - for (let w = 0; w < mainWidget.linkedWidgets?.length; w++) { - this.node.widgets[widgetIndex + w + 1].value = - node.widgets_values[i + ++linkedShift] + mainWidget.value = node.widgets_values?.[ + i + linkedShift + ] as typeof mainWidget.value + const linkedWidgets = mainWidget.linkedWidgets ?? [] + for (let w = 0; w < linkedWidgets.length; w++) { + this.node.widgets[widgetIndex + w + 1].value = node.widgets_values?.[ + i + ++linkedShift + ] as typeof mainWidget.value } } } } - // @ts-expect-error fixme ts strict error - replaceNodes(nodes) { - let top - let left + replaceNodes(nodes: LGraphNode[]) { + let top: number | undefined + let left: number | undefined for (let i = 0; i < nodes.length; i++) { const node = nodes[i] @@ -1554,11 +1713,10 @@ export class GroupNodeHandler { } this.linkInputs() - this.node.pos = [left, top] + this.node.pos = [left ?? 0, top ?? 0] } - // @ts-expect-error fixme ts strict error - linkOutputs(originalNode, nodeId) { + linkOutputs(originalNode: LGraphNode, nodeId: number) { if (!originalNode.outputs) return for (const output of originalNode.outputs) { @@ -1572,8 +1730,7 @@ export class GroupNodeHandler { const targetNode = app.rootGraph.getNodeById(link.target_id) const newSlot = this.groupData.oldToNewOutputMap[nodeId]?.[link.origin_slot] - if (newSlot != null) { - // @ts-expect-error fixme ts strict error + if (newSlot != null && targetNode) { this.node.connect(newSlot, targetNode, link.target_slot) } } @@ -1583,20 +1740,58 @@ export class GroupNodeHandler { linkInputs() { for (const link of this.groupData.nodeData.links ?? []) { const [, originSlot, targetId, targetSlot, actualOriginId] = link + if (actualOriginId == null || typeof actualOriginId === 'object') continue const originNode = app.rootGraph.getNodeById(actualOriginId) if (!originNode) continue // this node is in the group - originNode.connect( - originSlot, - // @ts-expect-error Valid - uses deprecated interface. Required check: if (graph.getNodeById(this.node.id) !== this.node) report() - this.node.id, - this.groupData.oldToNewInputMap[targetId][targetSlot] - ) + if (targetId == null || targetSlot == null) continue + const mappedSlot = + this.groupData.oldToNewInputMap[Number(targetId)]?.[Number(targetSlot)] + if (mappedSlot == null) continue + if (typeof originSlot === 'number' || typeof originSlot === 'string') { + originNode.connect( + originSlot, + // @ts-expect-error Valid - uses deprecated interface (node ID instead of node reference) + this.node.id, + mappedSlot + ) + } } } - // @ts-expect-error fixme ts strict error - static getGroupData(node) { - return (node.nodeData ?? node.constructor?.nodeData)?.[GROUP] + static getGroupData( + node: LGraphNodeConstructor + ): GroupNodeConfig | undefined + static getGroupData(node: LGraphNode): GroupNodeConfig | undefined + static getGroupData( + node: LGraphNode | LGraphNodeConstructor + ): GroupNodeConfig | undefined { + // Check if this is a constructor (function) or an instance + if (typeof node === 'function') { + // Constructor case - access nodeData directly + const nodeData = (node as LGraphNodeConstructor & { nodeData?: unknown }) + .nodeData as Record | undefined + return nodeData?.[GROUP] + } + // Instance case - check instance property first, then constructor + const instanceData = (node as LGraphNode & { nodeData?: unknown }) + .nodeData as Record | undefined + if (instanceData?.[GROUP]) return instanceData[GROUP] + const ctorData = ( + node.constructor as LGraphNodeConstructor & { nodeData?: unknown } + )?.nodeData as Record | undefined + return ctorData?.[GROUP] + } + + static getHandler(node: LGraphNode): GroupNodeHandler | undefined { + // @ts-expect-error GROUP symbol indexing on LGraphNode + let handler = node[GROUP] as GroupNodeHandler | undefined + // Handler may not be set yet if nodeCreated async hook hasn't run + // Create it synchronously if needed + if (!handler && GroupNodeHandler.isGroupNode(node)) { + handler = new GroupNodeHandler(node) + ;(node as LGraphNode & { [GROUP]: GroupNodeHandler })[GROUP] = handler + } + return handler } static isGroupNode(node: LGraphNode) { @@ -1616,17 +1811,15 @@ export class GroupNodeHandler { await config.registerType() const groupNode = LiteGraph.createNode(`${PREFIX}${SEPARATOR}${name}`) + if (!groupNode) return // Reuse the existing nodes for this instance - // @ts-expect-error fixme ts strict error - groupNode.setInnerNodes(builder.nodes) - // @ts-expect-error fixme ts strict error - groupNode[GROUP].populateWidgets() - // @ts-expect-error fixme ts strict error + groupNode.setInnerNodes?.(builder.nodes) + const handler = GroupNodeHandler.getHandler(groupNode) + handler?.populateWidgets() app.rootGraph.add(groupNode) // Remove all converted nodes and relink them - // @ts-expect-error fixme ts strict error - groupNode[GROUP].replaceNodes(builder.nodes) + handler?.replaceNodes(builder.nodes) return groupNode } } @@ -1685,8 +1878,24 @@ function manageGroupNodes(type?: string) { } const id = 'Comfy.GroupNode' -// @ts-expect-error fixme ts strict error -let globalDefs + +/** + * Global node definitions cache, populated and mutated by extension callbacks. + * + * **Initialization**: Set by `addCustomNodeDefs` during extension initialization. + * This callback runs early in the app lifecycle, before any code that reads + * `globalDefs` is executed. + * + * **Mutation**: `refreshComboInNodes` merges updated definitions into this object + * when combo options are refreshed (e.g., after model files change). + * + * **Usage Notes**: + * - Functions reading `globalDefs` (e.g., `getNodeDef`, `checkPrimitiveConnection`) + * must only be called after `addCustomNodeDefs` has run. + * - Not thread-safe; assumes single-threaded JS execution model. + * - The object reference is stable after initialization; only contents are mutated. + */ +let globalDefs: Record const ext: ComfyExtension = { name: id, commands: [ @@ -1739,8 +1948,8 @@ const ext: ComfyExtension = { items.push({ content: `Convert to Group Node (Deprecated)`, disabled: !convertEnabled, - // @ts-expect-error fixme ts strict error - async callback - callback: () => convertSelectedNodesToGroupNode() + // @ts-expect-error async callback - legacy menu API doesn't expect Promise + callback: async () => convertSelectedNodesToGroupNode() }) const groups = canvas.graph?.extra?.groupNodes @@ -1766,8 +1975,8 @@ const ext: ComfyExtension = { { content: `Convert to Group Node (Deprecated)`, disabled: !convertEnabled, - // @ts-expect-error fixme ts strict error - async callback - callback: () => convertSelectedNodesToGroupNode() + // @ts-expect-error async callback - legacy menu API doesn't expect Promise + callback: async () => convertSelectedNodesToGroupNode() } ] }, @@ -1775,7 +1984,9 @@ const ext: ComfyExtension = { graphData: ComfyWorkflowJSON, missingNodeTypes: string[] ) { - const nodes = graphData?.extra?.groupNodes + const nodes = graphData?.extra?.groupNodes as + | Record + | undefined if (nodes) { replaceLegacySeparators(graphData.nodes) await GroupNodeConfig.registerFromWorkflow(nodes, missingNodeTypes) @@ -1787,25 +1998,24 @@ const ext: ComfyExtension = { }, nodeCreated(node) { if (GroupNodeHandler.isGroupNode(node)) { - // @ts-expect-error fixme ts strict error - node[GROUP] = new GroupNodeHandler(node) + ;(node as LGraphNode & { [GROUP]: GroupNodeHandler })[GROUP] = + new GroupNodeHandler(node) // Ensure group nodes pasted from other workflows are stored - // @ts-expect-error fixme ts strict error - if (node.title && node[GROUP]?.groupData?.nodeData) { - // @ts-expect-error fixme ts strict error - Workflow.storeGroupNode(node.title, node[GROUP].groupData.nodeData) + const handler = GroupNodeHandler.getHandler(node) + if (node.title && handler?.groupData?.nodeData) { + Workflow.storeGroupNode(node.title, handler.groupData.nodeData) } } }, - // @ts-expect-error fixme ts strict error - async refreshComboInNodes(defs) { + async refreshComboInNodes(defs: Record) { // Re-register group nodes so new ones are created with the correct options - // @ts-expect-error fixme ts strict error Object.assign(globalDefs, defs) - const nodes = app.rootGraph.extra?.groupNodes + const nodes = app.rootGraph.extra?.groupNodes as + | Record + | undefined if (nodes) { - await GroupNodeConfig.registerFromWorkflow(nodes, {}) + await GroupNodeConfig.registerFromWorkflow(nodes, []) } } } diff --git a/src/extensions/core/groupNodeManage.ts b/src/extensions/core/groupNodeManage.ts index 7ea480614..0e91af317 100644 --- a/src/extensions/core/groupNodeManage.ts +++ b/src/extensions/core/groupNodeManage.ts @@ -483,23 +483,30 @@ export class ManageGroupDialog extends ComfyDialog { // Rewrite links for (const l of type.links) { + // @ts-expect-error l[0]/l[2] used as node index if (l[0] != null) l[0] = type.nodes[l[0]].index + // @ts-expect-error l[0]/l[2] used as node index if (l[2] != null) l[2] = type.nodes[l[2]].index } // Rewrite externals if (type.external) { for (const ext of type.external) { - ext[0] = type.nodes[ext[0]] + if (ext[0] != null) { + // @ts-expect-error ext[0] used as node index + ext[0] = type.nodes[ext[0]].index + } } } // Rewrite modifications for (const id of keys) { + // @ts-expect-error id used as node index if (config[id]) { // @ts-expect-error fixme ts strict error orderedConfig[type.nodes[id].index] = config[id] } + // @ts-expect-error id used as config key delete config[id] } @@ -529,7 +536,7 @@ export class ManageGroupDialog extends ComfyDialog { if (nodes) recreateNodes.push(...nodes) } - await GroupNodeConfig.registerFromWorkflow(types, {}) + await GroupNodeConfig.registerFromWorkflow(types, []) for (const node of recreateNodes) { node.recreate() diff --git a/src/extensions/core/nodeTemplates.ts b/src/extensions/core/nodeTemplates.ts index 457f54c48..040b54526 100644 --- a/src/extensions/core/nodeTemplates.ts +++ b/src/extensions/core/nodeTemplates.ts @@ -370,9 +370,10 @@ const ext: ComfyExtension = { const node = app.canvas.graph?.getNodeById(nodeIds[i]) const nodeData = node?.constructor.nodeData - let groupData = GroupNodeHandler.getGroupData(node) - if (groupData) { - groupData = groupData.nodeData + if (!node) continue + const groupConfig = GroupNodeHandler.getGroupData(node) + if (groupConfig) { + const groupData = groupConfig.nodeData // @ts-expect-error if (!data.groupNodes) { // @ts-expect-error @@ -402,7 +403,10 @@ const ext: ComfyExtension = { callback: () => { clipboardAction(async () => { const data = JSON.parse(t.data) - await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {}) + await GroupNodeConfig.registerFromWorkflow( + data.groupNodes ?? {}, + [] + ) // Check for old clipboard format if (!data.reroutes) { diff --git a/src/lib/litegraph/src/LGraph.ts b/src/lib/litegraph/src/LGraph.ts index 478a2a784..b3ad35c99 100644 --- a/src/lib/litegraph/src/LGraph.ts +++ b/src/lib/litegraph/src/LGraph.ts @@ -15,7 +15,7 @@ import { LGraphGroup } from './LGraphGroup' import { LGraphNode } from './LGraphNode' import type { NodeId } from './LGraphNode' import { LLink } from './LLink' -import type { LinkId } from './LLink' +import type { LinkId, SerialisedLLinkArray } from './LLink' import { MapProxyHandler } from './MapProxyHandler' import { Reroute } from './Reroute' import type { RerouteId } from './Reroute' @@ -102,11 +102,24 @@ export interface LGraphConfig { links_ontop?: any } +export interface GroupNodeWorkflowData { + external: (number | string)[][] + links: SerialisedLLinkArray[] + nodes: { + index?: number + type?: string + inputs?: unknown[] + outputs?: unknown[] + }[] + config?: Record +} + export interface LGraphExtra extends Dictionary { reroutes?: SerialisableReroute[] linkExtensions?: { id: number; parentId: number | undefined }[] ds?: DragAndScaleState workflowRendererVersion?: RendererType + groupNodes?: Record } export interface BaseLGraph { diff --git a/src/platform/workflow/validation/schemas/workflowSchema.ts b/src/platform/workflow/validation/schemas/workflowSchema.ts index 558ef819d..9517cc226 100644 --- a/src/platform/workflow/validation/schemas/workflowSchema.ts +++ b/src/platform/workflow/validation/schemas/workflowSchema.ts @@ -452,7 +452,6 @@ const zSubgraphDefinition = zComfyWorkflow1 .passthrough() export type ModelFile = z.infer -export type ComfyLink = z.infer export type ComfyLinkObject = z.infer export type ComfyNode = z.infer export type Reroute = z.infer diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 0701f694d..edcc64517 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -34,6 +34,7 @@ import { import type { ExecutionErrorWsMessage, NodeError, + NodeExecutionOutput, ResultItem } from '@/schemas/apiSchema' import { @@ -153,10 +154,8 @@ export class ComfyApp { vueAppReady: boolean api: ComfyApi ui: ComfyUI - // @ts-expect-error fixme ts strict error - extensionManager: ExtensionManager - // @ts-expect-error fixme ts strict error - _nodeOutputs: Record + extensionManager!: ExtensionManager + private _nodeOutputs!: Record nodePreviewImages: Record private rootGraphInternal: LGraph | undefined @@ -174,8 +173,7 @@ export class ComfyApp { return this.rootGraphInternal! } - // @ts-expect-error fixme ts strict error - canvas: LGraphCanvas + canvas!: LGraphCanvas dragOverNode: LGraphNode | null = null readonly canvasElRef = shallowRef() get canvasEl() { @@ -187,8 +185,7 @@ export class ComfyApp { get configuringGraph() { return this.configuringGraphLevel > 0 } - // @ts-expect-error fixme ts strict error - ctx: CanvasRenderingContext2D + ctx!: CanvasRenderingContext2D bodyTop: HTMLElement bodyLeft: HTMLElement bodyRight: HTMLElement @@ -491,18 +488,17 @@ export class ComfyApp { } } } - if (ComfyApp.clipspace.widgets) { + if (ComfyApp.clipspace.widgets && node.widgets) { ComfyApp.clipspace.widgets.forEach(({ type, name, value }) => { - // @ts-expect-error fixme ts strict error - const prop = Object.values(node.widgets).find( + const prop = node.widgets?.find( (obj) => obj.type === type && obj.name === name ) if (prop && prop.type != 'button') { + const valueObj = value as Record | undefined if ( prop.type != 'image' && typeof prop.value == 'string' && - // @ts-expect-error Custom widget value - value.filename + valueObj?.filename ) { const resultItem = value as ResultItem prop.value = @@ -752,16 +748,11 @@ export class ComfyApp { * Set up the app on the page */ async setup(canvasEl: HTMLCanvasElement) { - // @ts-expect-error fixme ts strict error - this.bodyTop = document.getElementById('comfyui-body-top') - // @ts-expect-error fixme ts strict error - this.bodyLeft = document.getElementById('comfyui-body-left') - // @ts-expect-error fixme ts strict error - this.bodyRight = document.getElementById('comfyui-body-right') - // @ts-expect-error fixme ts strict error - this.bodyBottom = document.getElementById('comfyui-body-bottom') - // @ts-expect-error fixme ts strict error - this.canvasContainer = document.getElementById('graph-canvas-container') + this.bodyTop = document.getElementById('comfyui-body-top')! + this.bodyLeft = document.getElementById('comfyui-body-left')! + this.bodyRight = document.getElementById('comfyui-body-right')! + this.bodyBottom = document.getElementById('comfyui-body-bottom')! + this.canvasContainer = document.getElementById('graph-canvas-container')! this.canvasElRef.value = canvasEl @@ -798,8 +789,7 @@ export class ComfyApp { // Make canvas states reactive so we can observe changes on them. this.canvas.state = reactive(this.canvas.state) - // @ts-expect-error fixme ts strict error - this.ctx = canvasEl.getContext('2d') + this.ctx = canvasEl.getContext('2d')! LiteGraph.alt_drag_do_clone_nodes = true LiteGraph.macGesturesRequireMac = false @@ -887,8 +877,7 @@ export class ComfyApp { const { width, height } = canvas.getBoundingClientRect() canvas.width = Math.round(width * scale) canvas.height = Math.round(height * scale) - // @ts-expect-error fixme ts strict error - canvas.getContext('2d').scale(scale, scale) + canvas.getContext('2d')?.scale(scale, scale) this.canvas?.draw(true, true) } @@ -981,16 +970,15 @@ export class ComfyApp { } } - // @ts-expect-error fixme ts strict error - loadTemplateData(templateData) { + loadTemplateData(templateData: { + templates?: { name?: string; data?: string }[] + }): void { if (!templateData?.templates) { return } const old = localStorage.getItem('litegrapheditor_clipboard') - var maxY, nodeBottom, node - for (const template of templateData.templates) { if (!template?.data) { continue @@ -1006,26 +994,24 @@ export class ComfyApp { } // Move mouse position down to paste the next template below - - maxY = false + let maxY: number | undefined for (const i in app.canvas.selected_nodes) { - node = app.canvas.selected_nodes[i] - - nodeBottom = node.pos[1] + node.size[1] - - // @ts-expect-error fixme ts strict error - if (maxY === false || nodeBottom > maxY) { + const node = app.canvas.selected_nodes[i] + const nodeBottom = node.pos[1] + node.size[1] + if (maxY === undefined || nodeBottom > maxY) { maxY = nodeBottom } } - // @ts-expect-error fixme ts strict error - app.canvas.graph_mouse[1] = maxY + 50 + if (maxY !== undefined) { + app.canvas.graph_mouse[1] = maxY + 50 + } } - // @ts-expect-error fixme ts strict error - localStorage.setItem('litegrapheditor_clipboard', old) + if (old !== null) { + localStorage.setItem('litegrapheditor_clipboard', old) + } } private showMissingNodesError(missingNodeTypes: MissingNodeType[]) { @@ -1034,8 +1020,10 @@ export class ComfyApp { } } - // @ts-expect-error fixme ts strict error - private showMissingModelsError(missingModels, paths) { + private showMissingModelsError( + missingModels: ModelFile[], + paths: Record + ): void { if (useSettingStore().get('Comfy.Workflow.ShowMissingModelsWarning')) { useDialogService().showMissingModelsWarning({ missingModels, @@ -1191,8 +1179,9 @@ export class ComfyApp { await modelStore.loadModelFolders() for (const m of uniqueModels) { const modelFolder = await modelStore.getLoadedModelFolder(m.directory) - // @ts-expect-error - if (!modelFolder) m.directory_invalid = true + if (!modelFolder) + (m as ModelFile & { directory_invalid?: boolean }).directory_invalid = + true const modelsAvailable = modelFolder?.models const modelExists = @@ -1288,14 +1277,15 @@ export class ComfyApp { } if (reset_invalid_values) { if (widget.type == 'combo') { + const values = widget.options.values as + | (string | number | boolean)[] + | undefined if ( - // @ts-expect-error fixme ts strict error - !widget.options.values.includes(widget.value as string) && - // @ts-expect-error fixme ts strict error - widget.options.values.length > 0 + values && + values.length > 0 && + !values.includes(widget.value as string | number | boolean) ) { - // @ts-expect-error fixme ts strict error - widget.value = widget.options.values[0] + widget.value = values[0] } } } @@ -1455,8 +1445,14 @@ export class ComfyApp { const { workflow, prompt, parameters, templates } = workflowData - if (templates) { - this.loadTemplateData({ templates }) + if ( + templates && + typeof templates === 'object' && + Array.isArray(templates) + ) { + this.loadTemplateData({ + templates: templates as { name?: string; data?: string }[] + }) } // Check workflow first - it should take priority over parameters @@ -1505,11 +1501,9 @@ export class ComfyApp { } // Use parameters strictly as the final fallback - if (parameters) { - // Note: Not putting this in `importA1111` as it is mostly not used - // by external callers, and `importA1111` has no access to `app`. + if (parameters && typeof parameters === 'string') { useWorkflowService().beforeLoadNewGraph() - importA1111(this.graph, parameters) + importA1111(this.rootGraph, parameters) useWorkflowService().afterLoadNewGraph( fileName, this.rootGraph.serialize() as unknown as ComfyWorkflowJSON @@ -1560,35 +1554,40 @@ export class ComfyApp { app.rootGraph.add(node) } - //TODO: Investigate repeat of for loop. Can compress? - for (const id of ids) { + const processNodeInputs = (id: string) => { const data = apiData[id] const node = app.rootGraph.getNodeById(id) + if (!node) return + for (const input in data.inputs ?? {}) { const value = data.inputs[input] if (value instanceof Array) { const [fromId, fromSlot] = value const fromNode = app.rootGraph.getNodeById(fromId) - // @ts-expect-error fixme ts strict error - let toSlot = node.inputs?.findIndex((inp) => inp.name === input) - if (toSlot == null || toSlot === -1) { + if (!fromNode) continue + + let toSlot = node.inputs?.findIndex((inp) => inp.name === input) ?? -1 + if (toSlot === -1) { try { - // Target has no matching input, most likely a converted widget - // @ts-expect-error fixme ts strict error const widget = node.widgets?.find((w) => w.name === input) - // @ts-expect-error - if (widget && node.convertWidgetToInput?.(widget)) { - // @ts-expect-error fixme ts strict error - toSlot = node.inputs?.length - 1 + const convertFn = ( + node as LGraphNode & { + convertWidgetToInput?: (w: IBaseWidget) => boolean + } + ).convertWidgetToInput + if (widget && convertFn?.(widget)) { + // Re-find the target slot by name after conversion + toSlot = + node.inputs?.findIndex((inp) => inp.name === input) ?? -1 } - } catch (error) {} + } catch (_error) { + // Ignore conversion errors + } } - if (toSlot != null || toSlot !== -1) { - // @ts-expect-error fixme ts strict error + if (toSlot !== -1) { fromNode.connect(fromSlot, node, toSlot) } } else { - // @ts-expect-error fixme ts strict error const widget = node.widgets?.find((w) => w.name === input) if (widget) { widget.value = value @@ -1597,45 +1596,10 @@ export class ComfyApp { } } } + + for (const id of ids) processNodeInputs(id) app.rootGraph.arrange() - - for (const id of ids) { - const data = apiData[id] - const node = app.rootGraph.getNodeById(id) - for (const input in data.inputs ?? {}) { - const value = data.inputs[input] - if (value instanceof Array) { - const [fromId, fromSlot] = value - const fromNode = app.rootGraph.getNodeById(fromId) - // @ts-expect-error fixme ts strict error - let toSlot = node.inputs?.findIndex((inp) => inp.name === input) - if (toSlot == null || toSlot === -1) { - try { - // Target has no matching input, most likely a converted widget - // @ts-expect-error fixme ts strict error - const widget = node.widgets?.find((w) => w.name === input) - // @ts-expect-error - if (widget && node.convertWidgetToInput?.(widget)) { - // @ts-expect-error fixme ts strict error - toSlot = node.inputs?.length - 1 - } - } catch (error) {} - } - if (toSlot != null || toSlot !== -1) { - // @ts-expect-error fixme ts strict error - fromNode.connect(fromSlot, node, toSlot) - } - } else { - // @ts-expect-error fixme ts strict error - const widget = node.widgets?.find((w) => w.name === input) - if (widget) { - widget.value = value - widget.callback?.(value) - } - } - } - } - + for (const id of ids) processNodeInputs(id) app.rootGraph.arrange() useWorkflowService().afterLoadNewGraph( diff --git a/src/scripts/pnginfo.ts b/src/scripts/pnginfo.ts index 97c6a9320..58fe2da0f 100644 --- a/src/scripts/pnginfo.ts +++ b/src/scripts/pnginfo.ts @@ -1,4 +1,5 @@ -import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import { LGraph, LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' +import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import { api } from './api' import { getFromAvifFile } from './metadata/avif' @@ -18,14 +19,16 @@ export function getAvifMetadata(file: File): Promise> { return getFromAvifFile(file) } -// @ts-expect-error fixme ts strict error -function parseExifData(exifData) { +function parseExifData(exifData: Uint8Array) { // Check for the correct TIFF header (0x4949 for little-endian or 0x4D4D for big-endian) const isLittleEndian = String.fromCharCode(...exifData.slice(0, 2)) === 'II' // Function to read 16-bit and 32-bit integers from binary data - // @ts-expect-error fixme ts strict error - function readInt(offset, isLittleEndian, length) { + function readInt( + offset: number, + isLittleEndian: boolean, + length: 2 | 4 + ): number { let arr = exifData.slice(offset, offset + length) if (length === 2) { return new DataView(arr.buffer, arr.byteOffset, arr.byteLength).getUint16( @@ -38,17 +41,16 @@ function parseExifData(exifData) { isLittleEndian ) } + return 0 } // Read the offset to the first IFD (Image File Directory) const ifdOffset = readInt(4, isLittleEndian, 4) - // @ts-expect-error fixme ts strict error - function parseIFD(offset) { + function parseIFD(offset: number): Record { const numEntries = readInt(offset, isLittleEndian, 2) - const result = {} + const result: Record = {} - // @ts-expect-error fixme ts strict error for (let i = 0; i < numEntries; i++) { const entryOffset = offset + 2 + i * 12 const tag = readInt(entryOffset, isLittleEndian, 2) @@ -61,12 +63,10 @@ function parseExifData(exifData) { if (type === 2) { // ASCII string value = new TextDecoder('utf-8').decode( - // @ts-expect-error fixme ts strict error exifData.subarray(valueOffset, valueOffset + numValues - 1) ) } - // @ts-expect-error fixme ts strict error result[tag] = value } @@ -78,13 +78,11 @@ function parseExifData(exifData) { return ifdData } -// @ts-expect-error fixme ts strict error -export function getWebpMetadata(file) { +export function getWebpMetadata(file: File) { return new Promise>((r) => { const reader = new FileReader() reader.onload = (event) => { - // @ts-expect-error fixme ts strict error - const webp = new Uint8Array(event.target.result as ArrayBuffer) + const webp = new Uint8Array(event.target?.result as ArrayBuffer) const dataView = new DataView(webp.buffer) // Check that the WEBP signature is present @@ -99,7 +97,7 @@ export function getWebpMetadata(file) { // Start searching for chunks after the WEBP signature let offset = 12 - let txt_chunks = {} + const txt_chunks: Record = {} // Loop through the chunks in the WEBP file while (offset < webp.length) { const chunk_length = dataView.getUint32(offset + 4, true) @@ -116,12 +114,10 @@ export function getWebpMetadata(file) { let data = parseExifData( webp.slice(offset + 8, offset + 8 + chunk_length) ) - for (var key in data) { - // @ts-expect-error fixme ts strict error - const value = data[key] as string + for (const key in data) { + const value = data[Number(key)] if (typeof value === 'string') { const index = value.indexOf(':') - // @ts-expect-error fixme ts strict error txt_chunks[value.slice(0, index)] = value.slice(index + 1) } } @@ -161,26 +157,42 @@ export function getLatentMetadata(file: File): Promise> { }) } -// @ts-expect-error fixme ts strict error -export async function importA1111(graph, parameters) { +interface NodeConnection { + node: LGraphNode + index: number +} + +interface LoraEntry { + name: string + weight: number +} + +export async function importA1111( + graph: LGraph, + parameters: string +): Promise { const p = parameters.lastIndexOf('\nSteps:') if (p > -1) { const embeddings = await api.getEmbeddings() - const opts = parameters + const matchResult = parameters .substr(p) .split('\n')[1] .match( new RegExp('\\s*([^:]+:\\s*([^"\\{].*?|".*?"|\\{.*?\\}))\\s*(,|$)', 'g') ) - // @ts-expect-error fixme ts strict error - .reduce((p, n) => { + if (!matchResult) return + + const opts: Record = matchResult.reduce( + (acc: Record, n: string) => { const s = n.split(':') if (s[1].endsWith(',')) { s[1] = s[1].substr(0, s[1].length - 1) } - p[s[0].trim().toLowerCase()] = s[1].trim() - return p - }, {}) + acc[s[0].trim().toLowerCase()] = s[1].trim() + return acc + }, + {} + ) const p2 = parameters.lastIndexOf('\nNegative prompt:', p) if (p2 > -1) { let positive = parameters.substr(0, p2).trim() @@ -194,25 +206,45 @@ export async function importA1111(graph, parameters) { const imageNode = LiteGraph.createNode('EmptyLatentImage') const vaeNode = LiteGraph.createNode('VAEDecode') const saveNode = LiteGraph.createNode('SaveImage') - // @ts-expect-error fixme ts strict error - let hrSamplerNode = null - let hrSteps = null - // @ts-expect-error fixme ts strict error - const ceil64 = (v) => Math.ceil(v / 64) * 64 - - // @ts-expect-error fixme ts strict error - const getWidget = (node, name) => { - // @ts-expect-error fixme ts strict error - return node.widgets.find((w) => w.name === name) + if ( + !ckptNode || + !clipSkipNode || + !positiveNode || + !negativeNode || + !samplerNode || + !imageNode || + !vaeNode || + !saveNode + ) { + console.error('Failed to create required nodes for A1111 import') + return } - // @ts-expect-error fixme ts strict error - const setWidgetValue = (node, name, value, isOptionPrefix?) => { + let hrSamplerNode: LGraphNode | null = null + let hrSteps: string | null = null + + const ceil64 = (v: number) => Math.ceil(v / 64) * 64 + + function getWidget( + node: LGraphNode | null, + name: string + ): IBaseWidget | undefined { + return node?.widgets?.find((w) => w.name === name) + } + + function setWidgetValue( + node: LGraphNode | null, + name: string, + value: string | number, + isOptionPrefix?: boolean + ): void { const w = getWidget(node, name) + if (!w) return + if (isOptionPrefix) { - // @ts-expect-error fixme ts strict error - const o = w.options.values.find((w) => w.startsWith(value)) + const values = w.options.values as string[] | undefined + const o = values?.find((v) => v.startsWith(String(value))) if (o) { w.value = o } else { @@ -224,25 +256,28 @@ export async function importA1111(graph, parameters) { } } - // @ts-expect-error fixme ts strict error - const createLoraNodes = (clipNode, text, prevClip, prevModel) => { - // @ts-expect-error fixme ts strict error - const loras = [] - // @ts-expect-error fixme ts strict error - text = text.replace(/]+)>/g, function (m, c) { + function createLoraNodes( + clipNode: LGraphNode, + text: string, + prevClip: NodeConnection, + prevModel: NodeConnection, + targetSamplerNode: LGraphNode + ): { text: string; prevModel: NodeConnection; prevClip: NodeConnection } { + const loras: LoraEntry[] = [] + text = text.replace(/]+)>/g, (_m, c: string) => { const s = c.split(':') const weight = parseFloat(s[1]) if (isNaN(weight)) { - console.warn('Invalid LORA', m) + console.warn('Invalid LORA', _m) } else { loras.push({ name: s[0], weight }) } return '' }) - // @ts-expect-error fixme ts strict error for (const l of loras) { const loraNode = LiteGraph.createNode('LoraLoader') + if (!loraNode) continue graph.add(loraNode) setWidgetValue(loraNode, 'lora_name', l.name, true) setWidgetValue(loraNode, 'strength_model', l.weight) @@ -254,8 +289,7 @@ export async function importA1111(graph, parameters) { } prevClip.node.connect(1, clipNode, 0) - prevModel.node.connect(0, samplerNode, 0) - // @ts-expect-error fixme ts strict error + prevModel.node.connect(0, targetSamplerNode, 0) if (hrSamplerNode) { prevModel.node.connect(0, hrSamplerNode, 0) } @@ -263,8 +297,7 @@ export async function importA1111(graph, parameters) { return { text, prevModel, prevClip } } - // @ts-expect-error fixme ts strict error - const replaceEmbeddings = (text) => { + function replaceEmbeddings(text: string): string { if (!embeddings.length) return text return text.replaceAll( new RegExp( @@ -279,8 +312,7 @@ export async function importA1111(graph, parameters) { ) } - // @ts-expect-error fixme ts strict error - const popOpt = (name) => { + function popOpt(name: string): string | undefined { const v = opts[name] delete opts[name] return v @@ -296,43 +328,29 @@ export async function importA1111(graph, parameters) { graph.add(vaeNode) graph.add(saveNode) - // @ts-expect-error fixme ts strict error ckptNode.connect(1, clipSkipNode, 0) - // @ts-expect-error fixme ts strict error clipSkipNode.connect(0, positiveNode, 0) - // @ts-expect-error fixme ts strict error clipSkipNode.connect(0, negativeNode, 0) - // @ts-expect-error fixme ts strict error ckptNode.connect(0, samplerNode, 0) - // @ts-expect-error fixme ts strict error positiveNode.connect(0, samplerNode, 1) - // @ts-expect-error fixme ts strict error negativeNode.connect(0, samplerNode, 2) - // @ts-expect-error fixme ts strict error imageNode.connect(0, samplerNode, 3) - // @ts-expect-error fixme ts strict error vaeNode.connect(0, saveNode, 0) - // @ts-expect-error fixme ts strict error samplerNode.connect(0, vaeNode, 0) - // @ts-expect-error fixme ts strict error ckptNode.connect(2, vaeNode, 1) - const handlers = { - // @ts-expect-error fixme ts strict error - model(v) { + const handlers: Record void> = { + model(v: string) { setWidgetValue(ckptNode, 'ckpt_name', v, true) }, vae() {}, - // @ts-expect-error fixme ts strict error - 'cfg scale'(v) { + 'cfg scale'(v: string) { setWidgetValue(samplerNode, 'cfg', +v) }, - // @ts-expect-error fixme ts strict error - 'clip skip'(v) { - setWidgetValue(clipSkipNode, 'stop_at_clip_layer', -v) + 'clip skip'(v: string) { + setWidgetValue(clipSkipNode, 'stop_at_clip_layer', -Number(v)) }, - // @ts-expect-error fixme ts strict error - sampler(v) { + sampler(v: string) { let name = v.toLowerCase().replace('++', 'pp').replaceAll(' ', '_') if (name.includes('karras')) { name = name.replace('karras', '').replace(/_+$/, '') @@ -341,45 +359,44 @@ export async function importA1111(graph, parameters) { setWidgetValue(samplerNode, 'scheduler', 'normal') } const w = getWidget(samplerNode, 'sampler_name') - const o = w.options.values.find( - // @ts-expect-error fixme ts strict error - (w) => w === name || w === 'sample_' + name - ) + const values = w?.options.values as string[] | undefined + const o = values?.find((v) => v === name || v === 'sample_' + name) if (o) { setWidgetValue(samplerNode, 'sampler_name', o) } }, - // @ts-expect-error fixme ts strict error - size(v) { + size(v: string) { const wxh = v.split('x') const w = ceil64(+wxh[0]) const h = ceil64(+wxh[1]) const hrUp = popOpt('hires upscale') const hrSz = popOpt('hires resize') - hrSteps = popOpt('hires steps') + hrSteps = popOpt('hires steps') ?? null let hrMethod = popOpt('hires upscaler') setWidgetValue(imageNode, 'width', w) setWidgetValue(imageNode, 'height', h) if (hrUp || hrSz) { - let uw, uh + let uw: number, uh: number if (hrUp) { - uw = w * hrUp - uh = h * hrUp - } else { + uw = w * Number(hrUp) + uh = h * Number(hrUp) + } else if (hrSz) { const s = hrSz.split('x') uw = +s[0] uh = +s[1] + } else { + return } - let upscaleNode - let latentNode + let upscaleNode: LGraphNode | null + let latentNode: LGraphNode | null - if (hrMethod.startsWith('Latent')) { + if (hrMethod?.startsWith('Latent')) { latentNode = upscaleNode = LiteGraph.createNode('LatentUpscale') + if (!upscaleNode) return graph.add(upscaleNode) - // @ts-expect-error fixme ts strict error samplerNode.connect(0, upscaleNode, 0) switch (hrMethod) { @@ -390,37 +407,40 @@ export async function importA1111(graph, parameters) { setWidgetValue(upscaleNode, 'upscale_method', hrMethod, true) } else { const decode = LiteGraph.createNode('VAEDecodeTiled') + if (!decode) return graph.add(decode) - // @ts-expect-error fixme ts strict error samplerNode.connect(0, decode, 0) - // @ts-expect-error fixme ts strict error ckptNode.connect(2, decode, 1) const upscaleLoaderNode = LiteGraph.createNode('UpscaleModelLoader') + if (!upscaleLoaderNode) return graph.add(upscaleLoaderNode) - setWidgetValue(upscaleLoaderNode, 'model_name', hrMethod, true) + setWidgetValue( + upscaleLoaderNode, + 'model_name', + hrMethod ?? '', + true + ) const modelUpscaleNode = LiteGraph.createNode( 'ImageUpscaleWithModel' ) + if (!modelUpscaleNode) return graph.add(modelUpscaleNode) - // @ts-expect-error fixme ts strict error decode.connect(0, modelUpscaleNode, 1) - // @ts-expect-error fixme ts strict error upscaleLoaderNode.connect(0, modelUpscaleNode, 0) upscaleNode = LiteGraph.createNode('ImageScale') + if (!upscaleNode) return graph.add(upscaleNode) - // @ts-expect-error fixme ts strict error modelUpscaleNode.connect(0, upscaleNode, 0) - const vaeEncodeNode = (latentNode = - LiteGraph.createNode('VAEEncodeTiled')) + const vaeEncodeNode = LiteGraph.createNode('VAEEncodeTiled') + if (!vaeEncodeNode) return + latentNode = vaeEncodeNode graph.add(vaeEncodeNode) - // @ts-expect-error fixme ts strict error upscaleNode.connect(0, vaeEncodeNode, 0) - // @ts-expect-error fixme ts strict error ckptNode.connect(2, vaeEncodeNode, 1) } @@ -428,33 +448,28 @@ export async function importA1111(graph, parameters) { setWidgetValue(upscaleNode, 'height', ceil64(uh)) hrSamplerNode = LiteGraph.createNode('KSampler') + if (!hrSamplerNode || !latentNode) return graph.add(hrSamplerNode) - // @ts-expect-error fixme ts strict error ckptNode.connect(0, hrSamplerNode, 0) - // @ts-expect-error fixme ts strict error positiveNode.connect(0, hrSamplerNode, 1) - // @ts-expect-error fixme ts strict error negativeNode.connect(0, hrSamplerNode, 2) - // @ts-expect-error fixme ts strict error latentNode.connect(0, hrSamplerNode, 3) - // @ts-expect-error fixme ts strict error hrSamplerNode.connect(0, vaeNode, 0) } }, - // @ts-expect-error fixme ts strict error - steps(v) { + steps(v: string) { setWidgetValue(samplerNode, 'steps', +v) }, - // @ts-expect-error fixme ts strict error - seed(v) { + seed(v: string) { setWidgetValue(samplerNode, 'seed', +v) } } for (const opt in opts) { - if (opt in handlers) { - // @ts-expect-error fixme ts strict error - handlers[opt](popOpt(opt)) + const handler = handlers[opt] + if (handler) { + const value = popOpt(opt) + if (value !== undefined) handler(value) } } @@ -462,27 +477,29 @@ export async function importA1111(graph, parameters) { setWidgetValue( hrSamplerNode, 'steps', - hrSteps ? +hrSteps : getWidget(samplerNode, 'steps').value + hrSteps + ? +hrSteps + : (getWidget(samplerNode, 'steps')?.value as number) ) setWidgetValue( hrSamplerNode, 'cfg', - getWidget(samplerNode, 'cfg').value + getWidget(samplerNode, 'cfg')?.value as number ) setWidgetValue( hrSamplerNode, 'scheduler', - getWidget(samplerNode, 'scheduler').value + getWidget(samplerNode, 'scheduler')?.value as string ) setWidgetValue( hrSamplerNode, 'sampler_name', - getWidget(samplerNode, 'sampler_name').value + getWidget(samplerNode, 'sampler_name')?.value as string ) setWidgetValue( hrSamplerNode, 'denoise', - +(popOpt('denoising strength') || '1') + +(popOpt('denoising strength') ?? '1') ) } @@ -490,10 +507,17 @@ export async function importA1111(graph, parameters) { positiveNode, positive, { node: clipSkipNode, index: 0 }, - { node: ckptNode, index: 0 } + { node: ckptNode, index: 0 }, + samplerNode ) positive = n.text - n = createLoraNodes(negativeNode, negative, n.prevClip, n.prevModel) + n = createLoraNodes( + negativeNode, + negative, + n.prevClip, + n.prevModel, + samplerNode + ) negative = n.text setWidgetValue(positiveNode, 'text', replaceEmbeddings(positive))