[refactor] Remove obsolete Kontext Edit Button (#5108)

* feat: Remove obsolete Kontext Edit Button

Removes the 'Kontext Edit Button' and its associated code, as it has been made obsolete by the new 'Subgraphs + Partial Execution' feature.

Fixes #5093

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-08-19 18:36:00 -07:00
committed by GitHub
parent 97e5291b05
commit 8d0a523ffd
22 changed files with 0 additions and 780 deletions

View File

@@ -12,7 +12,6 @@
<ColorPickerButton />
<BypassButton />
<PinButton />
<EditModelButton />
<Load3DViewerButton />
<MaskEditorButton />
<ConvertToSubgraphButton />
@@ -35,7 +34,6 @@ import BypassButton from '@/components/graph/selectionToolbox/BypassButton.vue'
import ColorPickerButton from '@/components/graph/selectionToolbox/ColorPickerButton.vue'
import ConvertToSubgraphButton from '@/components/graph/selectionToolbox/ConvertToSubgraphButton.vue'
import DeleteButton from '@/components/graph/selectionToolbox/DeleteButton.vue'
import EditModelButton from '@/components/graph/selectionToolbox/EditModelButton.vue'
import ExecuteButton from '@/components/graph/selectionToolbox/ExecuteButton.vue'
import ExtensionCommandButton from '@/components/graph/selectionToolbox/ExtensionCommandButton.vue'
import HelpButton from '@/components/graph/selectionToolbox/HelpButton.vue'

View File

@@ -1,37 +0,0 @@
<template>
<Button
v-show="isImageOutputSelected"
v-tooltip.top="{
value: t('commands.Comfy_Canvas_AddEditModelStep.label'),
showDelay: 1000
}"
severity="secondary"
text
icon="pi pi-pen-to-square"
@click="() => commandStore.execute('Comfy.Canvas.AddEditModelStep')"
/>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useCommandStore } from '@/stores/commandStore'
import { useCanvasStore } from '@/stores/graphStore'
import { isImageNode, isLGraphNode } from '@/utils/litegraphUtil'
const { t } = useI18n()
const commandStore = useCommandStore()
const canvasStore = useCanvasStore()
const isImageOutputOrEditModelNode = (node: unknown) =>
isLGraphNode(node) &&
(isImageNode(node) || node.type === 'workflow>FLUX.1 Kontext Image Edit')
const isImageOutputSelected = computed(
() =>
canvasStore.selectedItems.length === 1 &&
isImageOutputOrEditModelNode(canvasStore.selectedItems[0])
)
</script>

View File

@@ -16,7 +16,6 @@ import {
import { Point } from '@/lib/litegraph/src/litegraph'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { addFluxKontextGroupNode } from '@/scripts/fluxKontextEditNode'
import { useDialogService } from '@/services/dialogService'
import { useLitegraphService } from '@/services/litegraphService'
import { useWorkflowService } from '@/services/workflowService'
@@ -775,17 +774,6 @@ export function useCoreCommands(): ComfyCommand[] {
versionAdded: moveSelectedNodesVersionAdded,
function: () => moveSelectedNodes(([x, y], gridSize) => [x + gridSize, y])
},
{
id: 'Comfy.Canvas.AddEditModelStep',
icon: 'pi pi-pen-to-square',
label: 'Add Edit Model Step',
versionAdded: '1.23.3',
function: async () => {
const node = app.canvas.selectedItems.values().next().value
if (!(node instanceof LGraphNode)) return
await addFluxKontextGroupNode(node)
}
},
{
id: 'Comfy.Graph.ConvertToSubgraph',
icon: 'pi pi-sitemap',

View File

@@ -41,9 +41,6 @@
"Comfy_BrowseTemplates": {
"label": "تصفح القوالب"
},
"Comfy_Canvas_AddEditModelStep": {
"label": "إضافة خطوة تحرير النموذج"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "حذف العناصر المحددة"
},

View File

@@ -762,7 +762,6 @@
},
"menuLabels": {
"About ComfyUI": "حول ComfyUI",
"Add Edit Model Step": "إضافة خطوة تعديل النموذج",
"Bottom Panel": "لوحة سفلية",
"Browse Templates": "تصفح القوالب",
"Bypass/Unbypass Selected Nodes": "تجاوز/إلغاء تجاوز العقد المحددة",

View File

@@ -41,9 +41,6 @@
"Comfy_BrowseTemplates": {
"label": "Browse Templates"
},
"Comfy_Canvas_AddEditModelStep": {
"label": "Add Edit Model Step"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "Delete Selected Items"
},

View File

@@ -958,7 +958,6 @@
"Restart": "Restart",
"Open 3D Viewer (Beta) for Selected Node": "Open 3D Viewer (Beta) for Selected Node",
"Browse Templates": "Browse Templates",
"Add Edit Model Step": "Add Edit Model Step",
"Delete Selected Items": "Delete Selected Items",
"Zoom to fit": "Zoom to fit",
"Move Selected Nodes Down": "Move Selected Nodes Down",

View File

@@ -41,9 +41,6 @@
"Comfy_BrowseTemplates": {
"label": "Explorar plantillas"
},
"Comfy_Canvas_AddEditModelStep": {
"label": "Agregar paso de edición de modelo"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "Eliminar elementos seleccionados"
},

View File

@@ -762,7 +762,6 @@
},
"menuLabels": {
"About ComfyUI": "Acerca de ComfyUI",
"Add Edit Model Step": "Agregar paso de edición de modelo",
"Bottom Panel": "Panel inferior",
"Browse Templates": "Explorar plantillas",
"Bypass/Unbypass Selected Nodes": "Evitar/No evitar nodos seleccionados",

View File

@@ -41,9 +41,6 @@
"Comfy_BrowseTemplates": {
"label": "Parcourir les modèles"
},
"Comfy_Canvas_AddEditModelStep": {
"label": "Ajouter/Modifier une étape de modèle"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "Supprimer les éléments sélectionnés"
},

View File

@@ -762,7 +762,6 @@
},
"menuLabels": {
"About ComfyUI": "À propos de ComfyUI",
"Add Edit Model Step": "Ajouter une étape dédition de modèle",
"Bottom Panel": "Panneau inférieur",
"Browse Templates": "Parcourir les modèles",
"Bypass/Unbypass Selected Nodes": "Contourner/Ne pas contourner les nœuds sélectionnés",

View File

@@ -41,9 +41,6 @@
"Comfy_BrowseTemplates": {
"label": "テンプレートを参照"
},
"Comfy_Canvas_AddEditModelStep": {
"label": "編集モデルステップを追加"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "選択したアイテムを削除"
},

View File

@@ -762,7 +762,6 @@
},
"menuLabels": {
"About ComfyUI": "ComfyUIについて",
"Add Edit Model Step": "モデル編集ステップを追加",
"Bottom Panel": "下部パネル",
"Browse Templates": "テンプレートを参照",
"Bypass/Unbypass Selected Nodes": "選択したノードのバイパス/バイパス解除",

View File

@@ -41,9 +41,6 @@
"Comfy_BrowseTemplates": {
"label": "템플릿 탐색"
},
"Comfy_Canvas_AddEditModelStep": {
"label": "모델 편집 단계 추가"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "선택한 항목 삭제"
},

View File

@@ -762,7 +762,6 @@
},
"menuLabels": {
"About ComfyUI": "ComfyUI에 대하여",
"Add Edit Model Step": "모델 편집 단계 추가",
"Bottom Panel": "하단 패널",
"Browse Templates": "템플릿 탐색",
"Bypass/Unbypass Selected Nodes": "선택한 노드 우회/우회 해제",

View File

@@ -41,9 +41,6 @@
"Comfy_BrowseTemplates": {
"label": "Просмотр шаблонов"
},
"Comfy_Canvas_AddEditModelStep": {
"label": "Добавить или изменить шаг модели"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "Удалить выбранные элементы"
},

View File

@@ -762,7 +762,6 @@
},
"menuLabels": {
"About ComfyUI": "О ComfyUI",
"Add Edit Model Step": "Добавить или изменить шаг модели",
"Bottom Panel": "Нижняя панель",
"Browse Templates": "Просмотреть шаблоны",
"Bypass/Unbypass Selected Nodes": "Обойти/восстановить выбранные ноды",

View File

@@ -41,9 +41,6 @@
"Comfy_BrowseTemplates": {
"label": "瀏覽範本"
},
"Comfy_Canvas_AddEditModelStep": {
"label": "新增編輯模型步驟"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "刪除選取項目"
},

View File

@@ -762,7 +762,6 @@
},
"menuLabels": {
"About ComfyUI": "關於 ComfyUI",
"Add Edit Model Step": "新增編輯模型步驟",
"Bottom Panel": "底部面板",
"Browse Templates": "瀏覽範本",
"Bypass/Unbypass Selected Nodes": "繞過/取消繞過選取節點",

View File

@@ -41,9 +41,6 @@
"Comfy_BrowseTemplates": {
"label": "浏览模板"
},
"Comfy_Canvas_AddEditModelStep": {
"label": "添加编辑模型步骤"
},
"Comfy_Canvas_DeleteSelectedItems": {
"label": "删除选定的项目"
},

View File

@@ -762,7 +762,6 @@
},
"menuLabels": {
"About ComfyUI": "关于ComfyUI",
"Add Edit Model Step": "添加编辑模型步骤",
"Bottom Panel": "底部面板",
"Browse Templates": "浏览模板",
"Bypass/Unbypass Selected Nodes": "忽略/取消忽略选定节点",

View File

@@ -1,693 +0,0 @@
import _ from 'es-toolkit/compat'
import {
type INodeOutputSlot,
type LGraph,
type LGraphNode,
LLink,
LiteGraph,
type Point
} from '@/lib/litegraph/src/litegraph'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import { parseFilePath } from '@/utils/formatUtil'
import { app } from './app'
const fluxKontextGroupNode = {
nodes: [
{
id: -1,
type: 'Reroute',
pos: [2354.87890625, -127.23468780517578],
size: [75, 26],
flags: {},
order: 20,
mode: 0,
inputs: [{ name: '', type: '*', link: null }],
outputs: [{ name: '', type: '*', links: null }],
properties: { showOutputText: false, horizontal: false },
index: 0
},
{
id: -1,
type: 'ReferenceLatent',
pos: [2730, -220],
size: [197.712890625, 46],
flags: {},
order: 22,
mode: 0,
inputs: [
{
localized_name: 'conditioning',
name: 'conditioning',
type: 'CONDITIONING',
link: null
},
{
localized_name: 'latent',
name: 'latent',
shape: 7,
type: 'LATENT',
link: null
}
],
outputs: [
{
localized_name: 'CONDITIONING',
name: 'CONDITIONING',
type: 'CONDITIONING',
links: []
}
],
properties: {
'Node name for S&R': 'ReferenceLatent',
cnr_id: 'comfy-core',
ver: '0.3.38'
},
index: 1
},
{
id: -1,
type: 'VAEDecode',
pos: [3270, -110],
size: [210, 46],
flags: {},
order: 25,
mode: 0,
inputs: [
{
localized_name: 'samples',
name: 'samples',
type: 'LATENT',
link: null
},
{
localized_name: 'vae',
name: 'vae',
type: 'VAE',
link: null
}
],
outputs: [
{
localized_name: 'IMAGE',
name: 'IMAGE',
type: 'IMAGE',
slot_index: 0,
links: []
}
],
properties: {
'Node name for S&R': 'VAEDecode',
cnr_id: 'comfy-core',
ver: '0.3.38'
},
index: 2
},
{
id: -1,
type: 'KSampler',
pos: [2930, -110],
size: [315, 262],
flags: {},
order: 24,
mode: 0,
inputs: [
{
localized_name: 'model',
name: 'model',
type: 'MODEL',
link: null
},
{
localized_name: 'positive',
name: 'positive',
type: 'CONDITIONING',
link: null
},
{
localized_name: 'negative',
name: 'negative',
type: 'CONDITIONING',
link: null
},
{
localized_name: 'latent_image',
name: 'latent_image',
type: 'LATENT',
link: null
},
{
localized_name: 'seed',
name: 'seed',
type: 'INT',
widget: { name: 'seed' },
link: null
},
{
localized_name: 'steps',
name: 'steps',
type: 'INT',
widget: { name: 'steps' },
link: null
},
{
localized_name: 'cfg',
name: 'cfg',
type: 'FLOAT',
widget: { name: 'cfg' },
link: null
},
{
localized_name: 'sampler_name',
name: 'sampler_name',
type: 'COMBO',
widget: { name: 'sampler_name' },
link: null
},
{
localized_name: 'scheduler',
name: 'scheduler',
type: 'COMBO',
widget: { name: 'scheduler' },
link: null
},
{
localized_name: 'denoise',
name: 'denoise',
type: 'FLOAT',
widget: { name: 'denoise' },
link: null
}
],
outputs: [
{
localized_name: 'LATENT',
name: 'LATENT',
type: 'LATENT',
slot_index: 0,
links: []
}
],
properties: {
'Node name for S&R': 'KSampler',
cnr_id: 'comfy-core',
ver: '0.3.38'
},
widgets_values: [972054013131369, 'fixed', 20, 1, 'euler', 'simple', 1],
index: 3
},
{
id: -1,
type: 'FluxGuidance',
pos: [2940, -220],
size: [211.60000610351562, 58],
flags: {},
order: 23,
mode: 0,
inputs: [
{
localized_name: 'conditioning',
name: 'conditioning',
type: 'CONDITIONING',
link: null
},
{
localized_name: 'guidance',
name: 'guidance',
type: 'FLOAT',
widget: { name: 'guidance' },
link: null
}
],
outputs: [
{
localized_name: 'CONDITIONING',
name: 'CONDITIONING',
type: 'CONDITIONING',
slot_index: 0,
links: []
}
],
properties: {
'Node name for S&R': 'FluxGuidance',
cnr_id: 'comfy-core',
ver: '0.3.38'
},
widgets_values: [2.5],
index: 4
},
{
id: -1,
type: 'SaveImage',
pos: [3490, -110],
size: [985.3012084960938, 1060.3828125],
flags: {},
order: 26,
mode: 0,
inputs: [
{
localized_name: 'images',
name: 'images',
type: 'IMAGE',
link: null
},
{
localized_name: 'filename_prefix',
name: 'filename_prefix',
type: 'STRING',
widget: { name: 'filename_prefix' },
link: null
}
],
outputs: [],
properties: { cnr_id: 'comfy-core', ver: '0.3.38' },
widgets_values: ['ComfyUI'],
index: 5
},
{
id: -1,
type: 'CLIPTextEncode',
pos: [2500, -110],
size: [422.84503173828125, 164.31304931640625],
flags: {},
order: 12,
mode: 0,
inputs: [
{
localized_name: 'clip',
name: 'clip',
type: 'CLIP',
link: null
},
{
localized_name: 'text',
name: 'text',
type: 'STRING',
widget: { name: 'text' },
link: null
}
],
outputs: [
{
localized_name: 'CONDITIONING',
name: 'CONDITIONING',
type: 'CONDITIONING',
slot_index: 0,
links: []
}
],
title: 'CLIP Text Encode (Positive Prompt)',
properties: {
'Node name for S&R': 'CLIPTextEncode',
cnr_id: 'comfy-core',
ver: '0.3.38'
},
widgets_values: ['there is a bright light'],
color: '#232',
bgcolor: '#353',
index: 6
},
{
id: -1,
type: 'CLIPTextEncode',
pos: [2504.1435546875, 97.9598617553711],
size: [422.84503173828125, 164.31304931640625],
flags: { collapsed: true },
order: 13,
mode: 0,
inputs: [
{
localized_name: 'clip',
name: 'clip',
type: 'CLIP',
link: null
},
{
localized_name: 'text',
name: 'text',
type: 'STRING',
widget: { name: 'text' },
link: null
}
],
outputs: [
{
localized_name: 'CONDITIONING',
name: 'CONDITIONING',
type: 'CONDITIONING',
slot_index: 0,
links: []
}
],
title: 'CLIP Text Encode (Negative Prompt)',
properties: {
'Node name for S&R': 'CLIPTextEncode',
cnr_id: 'comfy-core',
ver: '0.3.38'
},
widgets_values: [''],
color: '#322',
bgcolor: '#533',
index: 7
},
{
id: -1,
type: 'UNETLoader',
pos: [2630, -370],
size: [270, 82],
flags: {},
order: 6,
mode: 0,
inputs: [
{
localized_name: 'unet_name',
name: 'unet_name',
type: 'COMBO',
widget: { name: 'unet_name' },
link: null
},
{
localized_name: 'weight_dtype',
name: 'weight_dtype',
type: 'COMBO',
widget: { name: 'weight_dtype' },
link: null
}
],
outputs: [
{
localized_name: 'MODEL',
name: 'MODEL',
type: 'MODEL',
links: []
}
],
properties: {
'Node name for S&R': 'UNETLoader',
cnr_id: 'comfy-core',
ver: '0.3.38'
},
widgets_values: ['flux1-kontext-dev.safetensors', 'default'],
color: '#223',
bgcolor: '#335',
index: 8
},
{
id: -1,
type: 'DualCLIPLoader',
pos: [2100, -290],
size: [337.76861572265625, 130],
flags: {},
order: 8,
mode: 0,
inputs: [
{
localized_name: 'clip_name1',
name: 'clip_name1',
type: 'COMBO',
widget: { name: 'clip_name1' },
link: null
},
{
localized_name: 'clip_name2',
name: 'clip_name2',
type: 'COMBO',
widget: { name: 'clip_name2' },
link: null
},
{
localized_name: 'type',
name: 'type',
type: 'COMBO',
widget: { name: 'type' },
link: null
},
{
localized_name: 'device',
name: 'device',
shape: 7,
type: 'COMBO',
widget: { name: 'device' },
link: null
}
],
outputs: [
{
localized_name: 'CLIP',
name: 'CLIP',
type: 'CLIP',
links: []
}
],
properties: {
'Node name for S&R': 'DualCLIPLoader',
cnr_id: 'comfy-core',
ver: '0.3.38'
},
widgets_values: [
'clip_l.safetensors',
't5xxl_fp8_e4m3fn_scaled.safetensors',
'flux',
'default'
],
color: '#223',
bgcolor: '#335',
index: 9
},
{
id: -1,
type: 'VAELoader',
pos: [2960, -370],
size: [270, 58],
flags: {},
order: 7,
mode: 0,
inputs: [
{
localized_name: 'vae_name',
name: 'vae_name',
type: 'COMBO',
widget: { name: 'vae_name' },
link: null
}
],
outputs: [
{
localized_name: 'VAE',
name: 'VAE',
type: 'VAE',
links: []
}
],
properties: {
'Node name for S&R': 'VAELoader',
cnr_id: 'comfy-core',
ver: '0.3.38'
},
widgets_values: ['ae.safetensors'],
color: '#223',
bgcolor: '#335',
index: 10
}
],
links: [
[6, 0, 1, 0, 72, 'CONDITIONING'],
[0, 0, 1, 1, 66, '*'],
[3, 0, 2, 0, 69, 'LATENT'],
[10, 0, 2, 1, 76, 'VAE'],
[8, 0, 3, 0, 74, 'MODEL'],
[4, 0, 3, 1, 70, 'CONDITIONING'],
[7, 0, 3, 2, 73, 'CONDITIONING'],
[0, 0, 3, 3, 66, '*'],
[1, 0, 4, 0, 67, 'CONDITIONING'],
[2, 0, 5, 0, 68, 'IMAGE'],
[9, 0, 6, 0, 75, 'CLIP'],
[9, 0, 7, 0, 75, 'CLIP']
],
external: [],
config: {
'0': {},
'1': {},
'2': { output: { '0': { visible: true } } },
'3': {
output: { '0': { visible: true } },
input: {
denoise: { visible: false },
cfg: { visible: false }
}
},
'4': {},
'5': {},
'6': {},
'7': { input: { text: { visible: false } } },
'8': { input: { weight_dtype: { visible: false } } },
'9': { input: { type: { visible: false }, device: { visible: false } } },
'10': {}
}
}
export async function ensureGraphHasFluxKontextGroupNode(
graph: LGraph & { extra: { groupNodes?: Record<string, any> } }
) {
graph.extra ??= {}
graph.extra.groupNodes ??= {}
if (graph.extra.groupNodes['FLUX.1 Kontext Image Edit']) return
graph.extra.groupNodes['FLUX.1 Kontext Image Edit'] =
structuredClone(fluxKontextGroupNode)
// Lazy import to avoid circular dependency issues
const { GroupNodeConfig } = await import('@/extensions/core/groupNode')
await GroupNodeConfig.registerFromWorkflow(
{
'FLUX.1 Kontext Image Edit':
graph.extra.groupNodes['FLUX.1 Kontext Image Edit']
},
[]
)
}
export async function addFluxKontextGroupNode(fromNode: LGraphNode) {
const { canvas } = app
const { graph } = canvas
if (!graph) throw new TypeError('Graph is not initialized')
await ensureGraphHasFluxKontextGroupNode(graph)
const node = LiteGraph.createNode('workflow>FLUX.1 Kontext Image Edit')
if (!node) throw new TypeError('Failed to create node')
const pos = getPosToRightOfNode(fromNode)
graph.add(node)
node.pos = pos
app.canvas.processSelect(node, undefined)
connectPreviousLatent(fromNode, node)
const symb = Object.getOwnPropertySymbols(node)[0]
// @ts-expect-error It's there -- promise.
node[symb].populateWidgets()
setWidgetValues(node)
}
function setWidgetValues(node: LGraphNode) {
const seedInput = node.widgets?.find((x) => x.name === 'seed')
if (!seedInput) throw new TypeError('Seed input not found')
seedInput.value = Math.floor(Math.random() * 1_125_899_906_842_624)
const firstClip = node.widgets?.find((x) => x.name === 'clip_name1')
setPreferredValue('t5xxl_fp8_e4m3fn_scaled.safetensors', 't5xxl', firstClip)
const secondClip = node.widgets?.find((x) => x.name === 'clip_name2')
setPreferredValue('clip_l.safetensors', 'clip_l', secondClip)
const unet = node.widgets?.find((x) => x.name === 'unet_name')
setPreferredValue('flux1-dev-kontext_fp8_scaled.safetensors', 'kontext', unet)
const vae = node.widgets?.find((x) => x.name === 'vae_name')
setPreferredValue('ae.safetensors', 'ae.s', vae)
}
function setPreferredValue(
preferred: string,
match: string,
widget: IBaseWidget | undefined
): void {
if (!widget) throw new TypeError('Widget not found')
const { values } = widget.options
if (!Array.isArray(values)) return
// Match against filename portion only
const mapped = values.map((x) => parseFilePath(x).filename)
const value =
mapped.find((x) => x === preferred) ??
mapped.find((x) => x.includes?.(match))
widget.value = value ?? preferred
}
function getPosToRightOfNode(fromNode: LGraphNode) {
const nodes = app.canvas.graph?.nodes
if (!nodes) throw new TypeError('Could not get graph nodes')
const pos = [
fromNode.pos[0] + fromNode.size[0] + 100,
fromNode.pos[1]
] satisfies Point
while (nodes.find((x) => isPointTooClose(x.pos, pos))) {
pos[0] += 20
pos[1] += 20
}
return pos
}
function connectPreviousLatent(fromNode: LGraphNode, toEditNode: LGraphNode) {
const { canvas } = app
const { graph } = canvas
if (!graph) throw new TypeError('Graph is not initialized')
const l = findNearestOutputOfType([fromNode], 'LATENT')
if (!l) {
const imageOutput = findNearestOutputOfType([fromNode], 'IMAGE')
if (!imageOutput) throw new TypeError('No image output found')
const vaeEncode = LiteGraph.createNode('VAEEncode')
if (!vaeEncode) throw new TypeError('Failed to create node')
const { node: imageNode, index: imageIndex } = imageOutput
graph.add(vaeEncode)
vaeEncode.pos = getPosToRightOfNode(fromNode)
vaeEncode.pos[1] -= 200
vaeEncode.connect(0, toEditNode, 0)
imageNode.connect(imageIndex, vaeEncode, 0)
return
}
const { node, index } = l
node.connect(index, toEditNode, 0)
}
function getInputNodes(node: LGraphNode): LGraphNode[] {
return node.inputs
.map((x) => LLink.resolve(x.link, app.graph)?.outputNode)
.filter((x) => !!x)
}
function getOutputOfType(
node: LGraphNode,
type: string
): {
output: INodeOutputSlot
index: number
} {
const index = node.outputs.findIndex((x) => x.type === type)
const output = node.outputs[index]
return { output, index }
}
function findNearestOutputOfType(
nodes: Iterable<LGraphNode>,
type: string = 'LATENT',
depth: number = 0
): { node: LGraphNode; index: number } | undefined {
for (const node of nodes) {
const { output, index } = getOutputOfType(node, type)
if (output) return { node, index }
}
if (depth < 3) {
const closestNodes = new Set([...nodes].flatMap((x) => getInputNodes(x)))
const res = findNearestOutputOfType(closestNodes, type, depth + 1)
if (res) return res
}
}
function isPointTooClose(a: Point, b: Point, precision: number = 5) {
return Math.abs(a[0] - b[0]) < precision && Math.abs(a[1] - b[1]) < precision
}