mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-22 23:39:45 +00:00
Migrate forceInput widgets_values (#3337)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
@@ -296,6 +296,17 @@
|
||||
"Comfy_Window_UnloadConfirmation": {
|
||||
"name": "Show confirmation when closing window"
|
||||
},
|
||||
"Comfy_Workflow_AutoSave": {
|
||||
"name": "Auto Save",
|
||||
"options": {
|
||||
"off": "off",
|
||||
"after delay": "after delay"
|
||||
}
|
||||
},
|
||||
"Comfy_Workflow_AutoSaveDelay": {
|
||||
"name": "Auto Save Delay (ms)",
|
||||
"tooltip": "Only applies if Auto Save is set to \"after delay\"."
|
||||
},
|
||||
"Comfy_Workflow_ConfirmDelete": {
|
||||
"name": "Show confirmation when deleting workflows"
|
||||
},
|
||||
|
||||
@@ -296,6 +296,17 @@
|
||||
"Comfy_Window_UnloadConfirmation": {
|
||||
"name": "Mostrar confirmación al cerrar la ventana"
|
||||
},
|
||||
"Comfy_Workflow_AutoSave": {
|
||||
"name": "Auto Guardar",
|
||||
"options": {
|
||||
"after delay": "después de retraso",
|
||||
"off": "desactivado"
|
||||
}
|
||||
},
|
||||
"Comfy_Workflow_AutoSaveDelay": {
|
||||
"name": "Retraso de Auto Guardar (ms)",
|
||||
"tooltip": "Solo se aplica si Auto Guardar está configurado en \"después de retraso\"."
|
||||
},
|
||||
"Comfy_Workflow_ConfirmDelete": {
|
||||
"name": "Mostrar confirmación al eliminar flujos de trabajo"
|
||||
},
|
||||
|
||||
@@ -296,6 +296,17 @@
|
||||
"Comfy_Window_UnloadConfirmation": {
|
||||
"name": "Afficher une confirmation lors de la fermeture de la fenêtre"
|
||||
},
|
||||
"Comfy_Workflow_AutoSave": {
|
||||
"name": "Auto Sauvegarde",
|
||||
"options": {
|
||||
"after delay": "après délai",
|
||||
"off": "désactivé"
|
||||
}
|
||||
},
|
||||
"Comfy_Workflow_AutoSaveDelay": {
|
||||
"name": "Délai de l'Auto Sauvegarde (ms)",
|
||||
"tooltip": "S'applique uniquement si l'Auto Sauvegarde est réglée sur \"après délai\"."
|
||||
},
|
||||
"Comfy_Workflow_ConfirmDelete": {
|
||||
"name": "Afficher une confirmation lors de la suppression des flux de travail"
|
||||
},
|
||||
|
||||
@@ -296,6 +296,17 @@
|
||||
"Comfy_Window_UnloadConfirmation": {
|
||||
"name": "ウィンドウを閉じるときに確認を表示"
|
||||
},
|
||||
"Comfy_Workflow_AutoSave": {
|
||||
"name": "自動保存",
|
||||
"options": {
|
||||
"after delay": "遅延後",
|
||||
"off": "オフ"
|
||||
}
|
||||
},
|
||||
"Comfy_Workflow_AutoSaveDelay": {
|
||||
"name": "自動保存遅延(ms)",
|
||||
"tooltip": "自動保存が「遅延後」に設定されている場合のみ適用されます。"
|
||||
},
|
||||
"Comfy_Workflow_ConfirmDelete": {
|
||||
"name": "ワークフローを削除する際に確認を表示"
|
||||
},
|
||||
|
||||
@@ -296,6 +296,17 @@
|
||||
"Comfy_Window_UnloadConfirmation": {
|
||||
"name": "창 닫을 때 확인 표시"
|
||||
},
|
||||
"Comfy_Workflow_AutoSave": {
|
||||
"name": "자동 저장",
|
||||
"options": {
|
||||
"after delay": "지연 후",
|
||||
"off": "끄기"
|
||||
}
|
||||
},
|
||||
"Comfy_Workflow_AutoSaveDelay": {
|
||||
"name": "자동 저장 지연 시간 (ms)",
|
||||
"tooltip": "자동 저장이 \"지연 후\"로 설정된 경우에만 적용됩니다."
|
||||
},
|
||||
"Comfy_Workflow_ConfirmDelete": {
|
||||
"name": "워크플로 삭제 시 확인 표시"
|
||||
},
|
||||
|
||||
@@ -296,6 +296,17 @@
|
||||
"Comfy_Window_UnloadConfirmation": {
|
||||
"name": "Показать подтверждение при закрытии окна"
|
||||
},
|
||||
"Comfy_Workflow_AutoSave": {
|
||||
"name": "Автосохранение",
|
||||
"options": {
|
||||
"after delay": "после задержки",
|
||||
"off": "выключено"
|
||||
}
|
||||
},
|
||||
"Comfy_Workflow_AutoSaveDelay": {
|
||||
"name": "Задержка автосохранения (мс)",
|
||||
"tooltip": "Применяется только если автосохранение установлено на \"после задержки\"."
|
||||
},
|
||||
"Comfy_Workflow_ConfirmDelete": {
|
||||
"name": "Показать подтверждение при удалении рабочих процессов"
|
||||
},
|
||||
|
||||
@@ -296,6 +296,17 @@
|
||||
"Comfy_Window_UnloadConfirmation": {
|
||||
"name": "关闭窗口时显示确认"
|
||||
},
|
||||
"Comfy_Workflow_AutoSave": {
|
||||
"name": "自动保存",
|
||||
"options": {
|
||||
"after delay": "延迟后",
|
||||
"off": "关闭"
|
||||
}
|
||||
},
|
||||
"Comfy_Workflow_AutoSaveDelay": {
|
||||
"name": "自动保存延迟(毫秒)",
|
||||
"tooltip": "仅在自动保存设置为“延迟后”时适用。"
|
||||
},
|
||||
"Comfy_Workflow_ConfirmDelete": {
|
||||
"name": "删除工作流时显示确认"
|
||||
},
|
||||
|
||||
@@ -33,7 +33,11 @@ import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { useWidgetStore } from '@/stores/widgetStore'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { isImageNode, isVideoNode } from '@/utils/litegraphUtil'
|
||||
import {
|
||||
isImageNode,
|
||||
isVideoNode,
|
||||
migrateWidgetsValues
|
||||
} from '@/utils/litegraphUtil'
|
||||
|
||||
import { useExtensionService } from './extensionService'
|
||||
|
||||
@@ -262,6 +266,12 @@ export const useLitegraphService = () => {
|
||||
}
|
||||
)
|
||||
|
||||
data.widgets_values = migrateWidgetsValues(
|
||||
ComfyNode.nodeData.inputs,
|
||||
this.widgets ?? [],
|
||||
data.widgets_values ?? []
|
||||
)
|
||||
|
||||
super.configure(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import type { ColorOption } from '@comfyorg/litegraph'
|
||||
import { LGraphGroup, LGraphNode, isColorable } from '@comfyorg/litegraph'
|
||||
import type { IComboWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import type {
|
||||
IComboWidget,
|
||||
IWidget
|
||||
} from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import _ from 'lodash'
|
||||
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
|
||||
type ImageNode = LGraphNode & { imgs: HTMLImageElement[] | undefined }
|
||||
type VideoNode = LGraphNode & {
|
||||
videoContainer: HTMLElement | undefined
|
||||
@@ -70,3 +75,32 @@ export function executeWidgetsCallback(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Since frontend version 1.16, forceInput input is no longer treated
|
||||
* as widget. So we need to remove the dummy widget value serialized
|
||||
* from workflows prior to v1.16.
|
||||
* Ref: https://github.com/Comfy-Org/ComfyUI_frontend/pull/3326
|
||||
*
|
||||
* @param nodeDef the node definition
|
||||
* @param widgets the widgets on the node instance (from node definition)
|
||||
* @param widgetsValues the widgets values to populate the node during configuration
|
||||
* @returns the widgets values without the dummy widget values
|
||||
*/
|
||||
export function migrateWidgetsValues<TWidgetValue>(
|
||||
inputDefs: Record<string, InputSpec>,
|
||||
widgets: IWidget[],
|
||||
widgetsValues: TWidgetValue[]
|
||||
): TWidgetValue[] {
|
||||
const widgetNames = new Set(widgets.map((w) => w.name))
|
||||
const originalWidgetsInputs = Object.values(inputDefs).filter(
|
||||
(input) => widgetNames.has(input.name) || input.forceInput
|
||||
)
|
||||
|
||||
if (originalWidgetsInputs.length === widgetsValues?.length) {
|
||||
return _.zip(originalWidgetsInputs, widgetsValues)
|
||||
.filter(([input]) => !input?.forceInput)
|
||||
.map(([_, value]) => value as TWidgetValue)
|
||||
}
|
||||
return widgetsValues
|
||||
}
|
||||
|
||||
88
tests-ui/tests/utils/litegraphUtil.test.ts
Normal file
88
tests-ui/tests/utils/litegraphUtil.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { IWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { migrateWidgetsValues } from '@/utils/litegraphUtil'
|
||||
|
||||
describe('migrateWidgetsValues', () => {
|
||||
it('should remove widget values for forceInput inputs', () => {
|
||||
const inputDefs: Record<string, InputSpec> = {
|
||||
normalInput: {
|
||||
type: 'INT',
|
||||
name: 'normalInput'
|
||||
},
|
||||
forceInputField: {
|
||||
type: 'STRING',
|
||||
name: 'forceInputField',
|
||||
forceInput: true
|
||||
},
|
||||
anotherNormal: {
|
||||
type: 'FLOAT',
|
||||
name: 'anotherNormal'
|
||||
}
|
||||
}
|
||||
|
||||
const widgets: IWidget[] = [
|
||||
{ name: 'normalInput', type: 'number' },
|
||||
{ name: 'anotherNormal', type: 'number' }
|
||||
] as unknown as IWidget[]
|
||||
|
||||
const widgetValues = [42, 'dummy value', 3.14]
|
||||
|
||||
const result = migrateWidgetsValues(inputDefs, widgets, widgetValues)
|
||||
expect(result).toEqual([42, 3.14])
|
||||
})
|
||||
|
||||
it('should return original values if lengths do not match', () => {
|
||||
const inputDefs: Record<string, InputSpec> = {
|
||||
input1: {
|
||||
type: 'INT',
|
||||
name: 'input1',
|
||||
forceInput: true
|
||||
}
|
||||
}
|
||||
|
||||
const widgets: IWidget[] = []
|
||||
const widgetValues = [42, 'extra value']
|
||||
|
||||
const result = migrateWidgetsValues(inputDefs, widgets, widgetValues)
|
||||
expect(result).toEqual(widgetValues)
|
||||
})
|
||||
|
||||
it('should handle empty widgets and values', () => {
|
||||
const inputDefs: Record<string, InputSpec> = {}
|
||||
const widgets: IWidget[] = []
|
||||
const widgetValues: any[] = []
|
||||
|
||||
const result = migrateWidgetsValues(inputDefs, widgets, widgetValues)
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it('should preserve order of non-forceInput widget values', () => {
|
||||
const inputDefs: Record<string, InputSpec> = {
|
||||
first: {
|
||||
type: 'INT',
|
||||
name: 'first'
|
||||
},
|
||||
forced: {
|
||||
type: 'STRING',
|
||||
name: 'forced',
|
||||
forceInput: true
|
||||
},
|
||||
last: {
|
||||
type: 'FLOAT',
|
||||
name: 'last'
|
||||
}
|
||||
}
|
||||
|
||||
const widgets: IWidget[] = [
|
||||
{ name: 'first', type: 'number' },
|
||||
{ name: 'last', type: 'number' }
|
||||
] as unknown as IWidget[]
|
||||
|
||||
const widgetValues = ['first value', 'dummy', 'last value']
|
||||
|
||||
const result = migrateWidgetsValues(inputDefs, widgets, widgetValues)
|
||||
expect(result).toEqual(['first value', 'last value'])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user