mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-11 02:20:08 +00:00
Road to No Explicit Any Part 7: Scripts and Dialog Cleanup (#8092)
## Summary Continues the TypeScript strict typing improvements by removing `any` types from core scripts and dialog components. ### Changes **api.ts (6 instances)** - Define `V1RawPrompt` and `CloudRawPrompt` tuple types for queue prompt formats - Export `QueueIndex`, `PromptInputs`, `ExtraData`, `OutputsToExecute` from apiSchema - Type `#postItem` body, `storeUserData` data, and `getCustomNodesI18n` return **groupNodeManage.ts (all @ts-expect-error removed)** - Add `GroupNodeConfigEntry` interface to LGraph.ts - Extend `GroupNodeWorkflowData` with `title`, `widgets_values`, and typed `config` - Type all class properties with definite assignment assertions - Type all method parameters and event handlers - Fix save button callback with proper generic types for node ordering **changeTracker.ts (4 instances)** - Type `nodeOutputs` as `Record<string, ExecutedWsMessage['output']>` - Type prompt callback with `CanvasPointerEvent` and proper value types **asyncDialog.ts and dialog.ts** - Make `ComfyAsyncDialog` generic with `DialogAction<T>` type - Type `ComfyDialog` constructor and show method parameters - Update `ManageGroupDialog.show` signature to match base class ## Test plan - [x] `pnpm typecheck` passes - [x] `pnpm lint` passes - [x] Sourcegraph checks for external usage --- Related: Continues from #8083 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8092-Road-to-No-Explicit-Any-Part-7-Scripts-and-Dialog-Cleanup-2ea6d73d365081fbb890e73646a6ad16) by [Unito](https://www.unito.io)
This commit is contained in:
committed by
GitHub
parent
d5f17f7d9f
commit
7ef4ea6f25
@@ -368,7 +368,7 @@ export class GroupNodeConfig {
|
||||
}
|
||||
|
||||
getNodeDef(
|
||||
node: GroupNodeData
|
||||
node: GroupNodeData | GroupNodeWorkflowData['nodes'][number]
|
||||
): GroupNodeDef | ComfyNodeDef | null | undefined {
|
||||
if (node.type) {
|
||||
const def = globalDefs[node.type]
|
||||
@@ -386,7 +386,8 @@ export class GroupNodeConfig {
|
||||
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 output = node.outputs?.[0] as GroupNodeOutput | undefined
|
||||
const source = output?.widget?.name
|
||||
const nodeIdx = linksFrom[0]?.[0]?.[2]
|
||||
if (source && nodeIdx != null) {
|
||||
const fromTypeName = this.nodeData.nodes[Number(nodeIdx)]?.type
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants'
|
||||
import {
|
||||
type LGraphNode,
|
||||
type LGraphNodeConstructor,
|
||||
LiteGraph
|
||||
import type {
|
||||
GroupNodeConfigEntry,
|
||||
GroupNodeWorkflowData,
|
||||
LGraphNode,
|
||||
LGraphNodeConstructor
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
import { type ComfyApp, app } from '../../scripts/app'
|
||||
@@ -15,18 +17,20 @@ import './groupNodeManage.css'
|
||||
|
||||
const ORDER: symbol = Symbol()
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
function merge(target, source) {
|
||||
if (typeof target === 'object' && typeof source === 'object') {
|
||||
for (const key in source) {
|
||||
const sv = source[key]
|
||||
if (typeof sv === 'object') {
|
||||
let tv = target[key]
|
||||
if (!tv) tv = target[key] = {}
|
||||
merge(tv, source[key])
|
||||
} else {
|
||||
target[key] = sv
|
||||
function merge(
|
||||
target: Record<string, unknown>,
|
||||
source: Record<string, unknown>
|
||||
): Record<string, unknown> {
|
||||
for (const key in source) {
|
||||
const sv = source[key]
|
||||
if (typeof sv === 'object' && sv !== null) {
|
||||
let tv = target[key] as Record<string, unknown> | undefined
|
||||
if (!tv) {
|
||||
tv = target[key] = {}
|
||||
}
|
||||
merge(tv, sv as Record<string, unknown>)
|
||||
} else {
|
||||
target[key] = sv
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +38,7 @@ function merge(target, source) {
|
||||
}
|
||||
|
||||
export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
tabs: Record<
|
||||
tabs!: Record<
|
||||
'Inputs' | 'Outputs' | 'Widgets',
|
||||
{ tab: HTMLAnchorElement; page: HTMLElement }
|
||||
>
|
||||
@@ -52,31 +55,26 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
>
|
||||
>
|
||||
> = {}
|
||||
// @ts-expect-error fixme ts strict error
|
||||
nodeItems: any[]
|
||||
nodeItems!: HTMLLIElement[]
|
||||
app: ComfyApp
|
||||
// @ts-expect-error fixme ts strict error
|
||||
groupNodeType: LGraphNodeConstructor<LGraphNode>
|
||||
groupNodeDef: any
|
||||
groupData: any
|
||||
groupNodeType!: LGraphNodeConstructor<LGraphNode>
|
||||
groupData!: GroupNodeConfig
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
innerNodesList: HTMLUListElement
|
||||
// @ts-expect-error fixme ts strict error
|
||||
widgetsPage: HTMLElement
|
||||
// @ts-expect-error fixme ts strict error
|
||||
inputsPage: HTMLElement
|
||||
// @ts-expect-error fixme ts strict error
|
||||
outputsPage: HTMLElement
|
||||
draggable: any
|
||||
innerNodesList!: HTMLUListElement
|
||||
widgetsPage!: HTMLElement
|
||||
inputsPage!: HTMLElement
|
||||
outputsPage!: HTMLElement
|
||||
draggable: DraggableList | undefined
|
||||
|
||||
get selectedNodeInnerIndex() {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
return +this.nodeItems[this.selectedNodeIndex].dataset.nodeindex
|
||||
get selectedNodeInnerIndex(): number {
|
||||
const index = this.selectedNodeIndex
|
||||
if (index == null) throw new Error('No node selected')
|
||||
const item = this.nodeItems[index]
|
||||
if (!item?.dataset.nodeindex) throw new Error('Invalid node item')
|
||||
return +item.dataset.nodeindex
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
constructor(app) {
|
||||
constructor(app: ComfyApp) {
|
||||
super()
|
||||
this.app = app
|
||||
this.element = $el('dialog.comfy-group-manage', {
|
||||
@@ -84,19 +82,15 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
}) as HTMLDialogElement
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
changeTab(tab) {
|
||||
changeTab(tab: keyof ManageGroupDialog['tabs']): void {
|
||||
this.tabs[this.selectedTab].tab.classList.remove('active')
|
||||
this.tabs[this.selectedTab].page.classList.remove('active')
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.tabs[tab].tab.classList.add('active')
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.tabs[tab].page.classList.add('active')
|
||||
this.selectedTab = tab
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
changeNode(index, force?) {
|
||||
changeNode(index: number, force?: boolean): void {
|
||||
if (!force && this.selectedNodeIndex === index) return
|
||||
|
||||
if (this.selectedNodeIndex != null) {
|
||||
@@ -122,43 +116,41 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
this.groupNodeType = LiteGraph.registered_node_types[
|
||||
`${PREFIX}${SEPARATOR}` + this.selectedGroup
|
||||
] as unknown as LGraphNodeConstructor<LGraphNode>
|
||||
this.groupNodeDef = this.groupNodeType.nodeData
|
||||
this.groupData = GroupNodeHandler.getGroupData(this.groupNodeType)
|
||||
this.groupData = GroupNodeHandler.getGroupData(this.groupNodeType)!
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
changeGroup(group, reset = true) {
|
||||
changeGroup(group: string, reset = true): void {
|
||||
this.selectedGroup = group
|
||||
this.getGroupData()
|
||||
|
||||
const nodes = this.groupData.nodeData.nodes
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.nodeItems = nodes.map((n, i) =>
|
||||
$el(
|
||||
'li.draggable-item',
|
||||
{
|
||||
dataset: {
|
||||
nodeindex: n.index + ''
|
||||
},
|
||||
onclick: () => {
|
||||
this.changeNode(i)
|
||||
}
|
||||
},
|
||||
[
|
||||
$el('span.drag-handle'),
|
||||
$el(
|
||||
'div',
|
||||
{
|
||||
textContent: n.title ?? n.type
|
||||
this.nodeItems = nodes.map(
|
||||
(n, i) =>
|
||||
$el(
|
||||
'li.draggable-item',
|
||||
{
|
||||
dataset: {
|
||||
nodeindex: n.index + ''
|
||||
},
|
||||
n.title
|
||||
? $el('span', {
|
||||
textContent: n.type
|
||||
})
|
||||
: []
|
||||
)
|
||||
]
|
||||
)
|
||||
onclick: () => {
|
||||
this.changeNode(i)
|
||||
}
|
||||
},
|
||||
[
|
||||
$el('span.drag-handle'),
|
||||
$el(
|
||||
'div',
|
||||
{
|
||||
textContent: n.title ?? n.type
|
||||
},
|
||||
n.title
|
||||
? $el('span', {
|
||||
textContent: n.type
|
||||
})
|
||||
: []
|
||||
)
|
||||
]
|
||||
) as HTMLLIElement
|
||||
)
|
||||
|
||||
this.innerNodesList.replaceChildren(...this.nodeItems)
|
||||
@@ -167,47 +159,46 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
this.selectedNodeIndex = null
|
||||
this.changeNode(0)
|
||||
} else {
|
||||
const items = this.draggable.getAllItems()
|
||||
// @ts-expect-error fixme ts strict error
|
||||
let index = items.findIndex((item) => item.classList.contains('selected'))
|
||||
if (index === -1) index = this.selectedNodeIndex
|
||||
const items = this.draggable!.getAllItems()
|
||||
let index = items.findIndex((item: Element) =>
|
||||
item.classList.contains('selected')
|
||||
)
|
||||
if (index === -1) index = this.selectedNodeIndex!
|
||||
this.changeNode(index, true)
|
||||
}
|
||||
|
||||
const ordered = [...nodes]
|
||||
this.draggable?.dispose()
|
||||
this.draggable = new DraggableList(this.innerNodesList, 'li')
|
||||
this.draggable.addEventListener(
|
||||
'dragend',
|
||||
// @ts-expect-error fixme ts strict error
|
||||
({ detail: { oldPosition, newPosition } }) => {
|
||||
if (oldPosition === newPosition) return
|
||||
ordered.splice(newPosition, 0, ordered.splice(oldPosition, 1)[0])
|
||||
for (let i = 0; i < ordered.length; i++) {
|
||||
this.storeModification({
|
||||
nodeIndex: ordered[i].index,
|
||||
section: ORDER,
|
||||
prop: 'order',
|
||||
value: i
|
||||
})
|
||||
}
|
||||
this.draggable.addEventListener('dragend', (e: Event) => {
|
||||
const { oldPosition, newPosition } = (e as CustomEvent).detail
|
||||
if (oldPosition === newPosition) return
|
||||
ordered.splice(newPosition, 0, ordered.splice(oldPosition, 1)[0])
|
||||
for (let i = 0; i < ordered.length; i++) {
|
||||
this.storeModification({
|
||||
nodeIndex: ordered[i].index,
|
||||
section: ORDER,
|
||||
prop: 'order',
|
||||
value: i
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
storeModification(props: {
|
||||
nodeIndex?: number
|
||||
section: symbol
|
||||
section: string | symbol
|
||||
prop: string
|
||||
value: any
|
||||
value: unknown
|
||||
}) {
|
||||
const { nodeIndex, section, prop, value } = props
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const groupMod = (this.modifications[this.selectedGroup] ??= {})
|
||||
const nodesMod = (groupMod.nodes ??= {})
|
||||
const groupKey = this.selectedGroup!
|
||||
const groupMod = (this.modifications[groupKey] ??= {})
|
||||
const nodesMod = ((groupMod as Record<string, unknown>).nodes ??=
|
||||
{}) as Record<string, Record<symbol | string, Record<string, unknown>>>
|
||||
const nodeMod = (nodesMod[nodeIndex ?? this.selectedNodeInnerIndex] ??= {})
|
||||
const typeMod = (nodeMod[section] ??= {})
|
||||
if (typeof value === 'object') {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
const objMod = (typeMod[prop] ??= {})
|
||||
Object.assign(objMod, value)
|
||||
} else {
|
||||
@@ -215,35 +206,45 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
getEditElement(section, prop, value, placeholder, checked, checkable = true) {
|
||||
if (value === placeholder) value = ''
|
||||
getEditElement(
|
||||
section: string,
|
||||
prop: string | number,
|
||||
value: unknown,
|
||||
placeholder: string,
|
||||
checked: boolean,
|
||||
checkable = true
|
||||
): HTMLDivElement {
|
||||
let displayValue = value === placeholder ? '' : value
|
||||
|
||||
const mods =
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.modifications[this.selectedGroup]?.nodes?.[
|
||||
this.selectedNodeInnerIndex
|
||||
]?.[section]?.[prop]
|
||||
if (mods) {
|
||||
if (mods.name != null) {
|
||||
value = mods.name
|
||||
const groupKey = this.selectedGroup!
|
||||
const mods = (
|
||||
this.modifications[groupKey] as Record<string, unknown> | undefined
|
||||
)?.nodes as
|
||||
| Record<
|
||||
number,
|
||||
Record<string, Record<string, { name?: string; visible?: boolean }>>
|
||||
>
|
||||
| undefined
|
||||
const modEntry = mods?.[this.selectedNodeInnerIndex]?.[section]?.[prop]
|
||||
if (modEntry) {
|
||||
if (modEntry.name != null) {
|
||||
displayValue = modEntry.name
|
||||
}
|
||||
if (mods.visible != null) {
|
||||
checked = mods.visible
|
||||
if (modEntry.visible != null) {
|
||||
checked = modEntry.visible
|
||||
}
|
||||
}
|
||||
|
||||
return $el('div', [
|
||||
$el('input', {
|
||||
value,
|
||||
value: displayValue as string,
|
||||
placeholder,
|
||||
type: 'text',
|
||||
// @ts-expect-error fixme ts strict error
|
||||
onchange: (e) => {
|
||||
onchange: (e: Event) => {
|
||||
this.storeModification({
|
||||
section,
|
||||
prop,
|
||||
value: { name: e.target.value }
|
||||
prop: String(prop),
|
||||
value: { name: (e.target as HTMLInputElement).value }
|
||||
})
|
||||
}
|
||||
}),
|
||||
@@ -252,25 +253,23 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
type: 'checkbox',
|
||||
checked,
|
||||
disabled: !checkable,
|
||||
// @ts-expect-error fixme ts strict error
|
||||
onchange: (e) => {
|
||||
onchange: (e: Event) => {
|
||||
this.storeModification({
|
||||
section,
|
||||
prop,
|
||||
value: { visible: !!e.target.checked }
|
||||
prop: String(prop),
|
||||
value: { visible: !!(e.target as HTMLInputElement).checked }
|
||||
})
|
||||
}
|
||||
})
|
||||
])
|
||||
])
|
||||
]) as HTMLDivElement
|
||||
}
|
||||
|
||||
buildWidgetsPage() {
|
||||
const widgets =
|
||||
this.groupData.oldToNewWidgetMap[this.selectedNodeInnerIndex]
|
||||
const items = Object.keys(widgets ?? {})
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const type = app.rootGraph.extra.groupNodes[this.selectedGroup]
|
||||
const type = app.rootGraph.extra.groupNodes![this.selectedGroup!]!
|
||||
const config = type.config?.[this.selectedNodeInnerIndex]?.input
|
||||
this.widgetsPage.replaceChildren(
|
||||
...items.map((oldName) => {
|
||||
@@ -289,28 +288,25 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
buildInputsPage() {
|
||||
const inputs = this.groupData.nodeInputs[this.selectedNodeInnerIndex]
|
||||
const items = Object.keys(inputs ?? {})
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const type = app.rootGraph.extra.groupNodes[this.selectedGroup]
|
||||
const type = app.rootGraph.extra.groupNodes![this.selectedGroup!]!
|
||||
const config = type.config?.[this.selectedNodeInnerIndex]?.input
|
||||
this.inputsPage.replaceChildren(
|
||||
// @ts-expect-error fixme ts strict error
|
||||
...items
|
||||
.map((oldName) => {
|
||||
let value = inputs[oldName]
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
const elements = items
|
||||
.map((oldName) => {
|
||||
const value = inputs[oldName]
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.getEditElement(
|
||||
'input',
|
||||
oldName,
|
||||
value,
|
||||
oldName,
|
||||
config?.[oldName]?.visible !== false
|
||||
)
|
||||
})
|
||||
.filter(Boolean)
|
||||
)
|
||||
return this.getEditElement(
|
||||
'input',
|
||||
oldName,
|
||||
value,
|
||||
oldName,
|
||||
config?.[oldName]?.visible !== false
|
||||
)
|
||||
})
|
||||
.filter((el): el is HTMLDivElement => el !== null)
|
||||
this.inputsPage.replaceChildren(...elements)
|
||||
return !!items.length
|
||||
}
|
||||
|
||||
@@ -323,38 +319,35 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
const groupOutputs =
|
||||
this.groupData.oldToNewOutputMap[this.selectedNodeInnerIndex]
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const type = app.rootGraph.extra.groupNodes[this.selectedGroup]
|
||||
const type = app.rootGraph.extra.groupNodes![this.selectedGroup!]!
|
||||
const config = type.config?.[this.selectedNodeInnerIndex]?.output
|
||||
const node = this.groupData.nodeData.nodes[this.selectedNodeInnerIndex]
|
||||
const checkable = node.type !== 'PrimitiveNode'
|
||||
this.outputsPage.replaceChildren(
|
||||
...outputs
|
||||
// @ts-expect-error fixme ts strict error
|
||||
.map((type, slot) => {
|
||||
const groupOutputIndex = groupOutputs?.[slot]
|
||||
const oldName = innerNodeDef.output_name?.[slot] ?? type
|
||||
let value = config?.[slot]?.name
|
||||
const visible = config?.[slot]?.visible || groupOutputIndex != null
|
||||
if (!value || value === oldName) {
|
||||
value = ''
|
||||
}
|
||||
return this.getEditElement(
|
||||
'output',
|
||||
slot,
|
||||
value,
|
||||
oldName,
|
||||
visible,
|
||||
checkable
|
||||
)
|
||||
})
|
||||
.filter(Boolean)
|
||||
)
|
||||
const elements = outputs.map((outputType: unknown, slot: number) => {
|
||||
const groupOutputIndex = groupOutputs?.[slot]
|
||||
const oldName = innerNodeDef?.output_name?.[slot] ?? String(outputType)
|
||||
let value = config?.[slot]?.name
|
||||
const visible = config?.[slot]?.visible || groupOutputIndex != null
|
||||
if (!value || value === oldName) {
|
||||
value = ''
|
||||
}
|
||||
return this.getEditElement(
|
||||
'output',
|
||||
slot,
|
||||
value,
|
||||
oldName,
|
||||
visible,
|
||||
checkable
|
||||
)
|
||||
})
|
||||
this.outputsPage.replaceChildren(...elements)
|
||||
return !!outputs.length
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
show(type?) {
|
||||
override show(groupNodeType?: string | HTMLElement | HTMLElement[]): void {
|
||||
// Extract string type - this method repurposes the show signature
|
||||
const nodeType =
|
||||
typeof groupNodeType === 'string' ? groupNodeType : undefined
|
||||
const groupNodes = Object.keys(app.rootGraph.extra?.groupNodes ?? {}).sort(
|
||||
(a, b) => a.localeCompare(b)
|
||||
)
|
||||
@@ -371,24 +364,27 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
this.outputsPage
|
||||
])
|
||||
|
||||
this.tabs = [
|
||||
type TabName = 'Inputs' | 'Widgets' | 'Outputs'
|
||||
const tabEntries: [TabName, HTMLElement][] = [
|
||||
['Inputs', this.inputsPage],
|
||||
['Widgets', this.widgetsPage],
|
||||
['Outputs', this.outputsPage]
|
||||
// @ts-expect-error fixme ts strict error
|
||||
].reduce((p, [name, page]: [string, HTMLElement]) => {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
p[name] = {
|
||||
tab: $el('a', {
|
||||
onclick: () => {
|
||||
this.changeTab(name)
|
||||
},
|
||||
textContent: name
|
||||
}),
|
||||
page
|
||||
}
|
||||
return p
|
||||
}, {}) as any
|
||||
]
|
||||
this.tabs = tabEntries.reduce(
|
||||
(p, [name, page]) => {
|
||||
p[name] = {
|
||||
tab: $el('a', {
|
||||
onclick: () => {
|
||||
this.changeTab(name)
|
||||
},
|
||||
textContent: name
|
||||
}) as HTMLAnchorElement,
|
||||
page
|
||||
}
|
||||
return p
|
||||
},
|
||||
{} as ManageGroupDialog['tabs']
|
||||
)
|
||||
|
||||
const outer = $el('div.comfy-group-manage-outer', [
|
||||
$el('header', [
|
||||
@@ -396,15 +392,14 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
$el(
|
||||
'select',
|
||||
{
|
||||
// @ts-expect-error fixme ts strict error
|
||||
onchange: (e) => {
|
||||
this.changeGroup(e.target.value)
|
||||
onchange: (e: Event) => {
|
||||
this.changeGroup((e.target as HTMLSelectElement).value)
|
||||
}
|
||||
},
|
||||
groupNodes.map((g) =>
|
||||
$el('option', {
|
||||
textContent: g,
|
||||
selected: `${PREFIX}${SEPARATOR}${g}` === type,
|
||||
selected: `${PREFIX}${SEPARATOR}${g}` === nodeType,
|
||||
value: g
|
||||
})
|
||||
)
|
||||
@@ -439,8 +434,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
`Are you sure you want to remove the node: "${this.selectedGroup}"`
|
||||
)
|
||||
) {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
delete app.rootGraph.extra.groupNodes[this.selectedGroup]
|
||||
delete app.rootGraph.extra.groupNodes![this.selectedGroup!]
|
||||
LiteGraph.unregisterNodeType(
|
||||
`${PREFIX}${SEPARATOR}` + this.selectedGroup
|
||||
)
|
||||
@@ -454,97 +448,106 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
'button.comfy-btn',
|
||||
{
|
||||
onclick: async () => {
|
||||
let nodesByType
|
||||
let recreateNodes = []
|
||||
const types = {}
|
||||
type NodesByType = Record<string, LGraphNode[]>
|
||||
let nodesByType: NodesByType | undefined
|
||||
const recreateNodes: LGraphNode[] = []
|
||||
const types: Record<string, GroupNodeWorkflowData> = {}
|
||||
for (const g in this.modifications) {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const type = app.rootGraph.extra.groupNodes[g]
|
||||
let config = (type.config ??= {})
|
||||
const groupNodeData = app.rootGraph.extra.groupNodes![g]!
|
||||
let config = (groupNodeData.config ??= {})
|
||||
|
||||
let nodeMods = this.modifications[g]?.nodes
|
||||
type NodeMods = Record<
|
||||
string,
|
||||
Record<symbol | string, Record<string, unknown>>
|
||||
>
|
||||
let nodeMods = this.modifications[g]?.nodes as
|
||||
| NodeMods
|
||||
| undefined
|
||||
if (nodeMods) {
|
||||
const keys = Object.keys(nodeMods)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
if (nodeMods[keys[0]][ORDER]) {
|
||||
if (nodeMods[keys[0]]?.[ORDER]) {
|
||||
// If any node is reordered, they will all need sequencing
|
||||
const orderedNodes = []
|
||||
const orderedMods = {}
|
||||
const orderedConfig = {}
|
||||
const orderedNodes: GroupNodeWorkflowData['nodes'] = []
|
||||
const orderedMods: NodeMods = {}
|
||||
const orderedConfig: Record<number, GroupNodeConfigEntry> =
|
||||
{}
|
||||
|
||||
for (const n of keys) {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const order = nodeMods[n][ORDER].order
|
||||
orderedNodes[order] = type.nodes[+n]
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const order = (nodeMods[n][ORDER] as { order: number })
|
||||
.order
|
||||
orderedNodes[order] = groupNodeData.nodes[+n]
|
||||
orderedMods[order] = nodeMods[n]
|
||||
orderedNodes[order].index = order
|
||||
}
|
||||
|
||||
// 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
|
||||
const nodesLen = groupNodeData.nodes.length
|
||||
for (const l of groupNodeData.links) {
|
||||
const srcIdx = l[0] as number
|
||||
const dstIdx = l[2] as number
|
||||
if (srcIdx != null && srcIdx < nodesLen)
|
||||
l[0] = groupNodeData.nodes[srcIdx].index!
|
||||
if (dstIdx != null && dstIdx < nodesLen)
|
||||
l[2] = groupNodeData.nodes[dstIdx].index!
|
||||
}
|
||||
|
||||
// Rewrite externals
|
||||
if (type.external) {
|
||||
for (const ext of type.external) {
|
||||
if (ext[0] != null) {
|
||||
// @ts-expect-error ext[0] used as node index
|
||||
ext[0] = type.nodes[ext[0]].index
|
||||
if (groupNodeData.external) {
|
||||
for (const ext of groupNodeData.external) {
|
||||
const extIdx = ext[0] as number
|
||||
if (extIdx != null && extIdx < nodesLen) {
|
||||
ext[0] = groupNodeData.nodes[extIdx].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]
|
||||
if (config[+id]) {
|
||||
orderedConfig[groupNodeData.nodes[+id].index!] =
|
||||
config[+id]
|
||||
}
|
||||
// @ts-expect-error id used as config key
|
||||
delete config[id]
|
||||
delete config[+id]
|
||||
}
|
||||
|
||||
type.nodes = orderedNodes
|
||||
groupNodeData.nodes = orderedNodes
|
||||
nodeMods = orderedMods
|
||||
type.config = config = orderedConfig
|
||||
groupNodeData.config = config = orderedConfig
|
||||
}
|
||||
|
||||
merge(config, nodeMods)
|
||||
merge(
|
||||
config as Record<string, unknown>,
|
||||
nodeMods as Record<string, unknown>
|
||||
)
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
types[g] = type
|
||||
types[g] = groupNodeData
|
||||
|
||||
if (!nodesByType) {
|
||||
nodesByType = app.rootGraph.nodes.reduce((p, n) => {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
p[n.type] ??= []
|
||||
// @ts-expect-error fixme ts strict error
|
||||
p[n.type].push(n)
|
||||
return p
|
||||
}, {})
|
||||
nodesByType = app.rootGraph.nodes.reduce<NodesByType>(
|
||||
(p, n) => {
|
||||
const nodeType = n.type ?? ''
|
||||
p[nodeType] ??= []
|
||||
p[nodeType].push(n)
|
||||
return p
|
||||
},
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const nodes = nodesByType[`${PREFIX}${SEPARATOR}` + g]
|
||||
if (nodes) recreateNodes.push(...nodes)
|
||||
const groupTypeNodes = nodesByType[`${PREFIX}${SEPARATOR}` + g]
|
||||
if (groupTypeNodes) recreateNodes.push(...groupTypeNodes)
|
||||
}
|
||||
|
||||
await GroupNodeConfig.registerFromWorkflow(types, [])
|
||||
|
||||
for (const node of recreateNodes) {
|
||||
node.recreate()
|
||||
node.recreate?.()
|
||||
}
|
||||
|
||||
this.modifications = {}
|
||||
this.app.canvas.setDirty(true, true)
|
||||
this.changeGroup(this.selectedGroup, false)
|
||||
this.changeGroup(this.selectedGroup!, false)
|
||||
}
|
||||
},
|
||||
'Save'
|
||||
@@ -559,8 +562,8 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
|
||||
this.element.replaceChildren(outer)
|
||||
this.changeGroup(
|
||||
type
|
||||
? (groupNodes.find((g) => `${PREFIX}${SEPARATOR}${g}` === type) ??
|
||||
nodeType
|
||||
? (groupNodes.find((g) => `${PREFIX}${SEPARATOR}${g}` === nodeType) ??
|
||||
groupNodes[0])
|
||||
: groupNodes[0]
|
||||
)
|
||||
|
||||
@@ -102,16 +102,23 @@ export interface LGraphConfig {
|
||||
links_ontop?: boolean
|
||||
}
|
||||
|
||||
export interface GroupNodeConfigEntry {
|
||||
input?: Record<string, { name?: string; visible?: boolean }>
|
||||
output?: Record<number, { name?: string; visible?: boolean }>
|
||||
}
|
||||
|
||||
export interface GroupNodeWorkflowData {
|
||||
external: (number | string)[][]
|
||||
links: SerialisedLLinkArray[]
|
||||
nodes: {
|
||||
index?: number
|
||||
type?: string
|
||||
title?: string
|
||||
inputs?: unknown[]
|
||||
outputs?: unknown[]
|
||||
widgets_values?: unknown[]
|
||||
}[]
|
||||
config?: Record<number, unknown>
|
||||
config?: Record<number, GroupNodeConfigEntry>
|
||||
}
|
||||
|
||||
export interface LGraphExtra extends Dictionary<unknown> {
|
||||
|
||||
@@ -104,6 +104,8 @@ export type {
|
||||
} from './interfaces'
|
||||
export {
|
||||
LGraph,
|
||||
type GroupNodeConfigEntry,
|
||||
type GroupNodeWorkflowData,
|
||||
type LGraphTriggerAction,
|
||||
type LGraphTriggerParam
|
||||
} from './LGraph'
|
||||
|
||||
@@ -13,6 +13,9 @@ export type PromptId = z.infer<typeof zPromptId>
|
||||
export const resultItemType = z.enum(['input', 'output', 'temp'])
|
||||
export type ResultItemType = z.infer<typeof resultItemType>
|
||||
|
||||
const zCustomNodesI18n = z.record(z.string(), z.unknown())
|
||||
export type CustomNodesI18n = z.infer<typeof zCustomNodesI18n>
|
||||
|
||||
const zResultItem = z.object({
|
||||
filename: z.string().optional(),
|
||||
subfolder: z.string().optional(),
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {
|
||||
} from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type {
|
||||
AssetDownloadWsMessage,
|
||||
CustomNodesI18n,
|
||||
EmbeddingsResponse,
|
||||
ExecutedWsMessage,
|
||||
ExecutingWsMessage,
|
||||
@@ -35,6 +36,7 @@ import type {
|
||||
LogsRawResponse,
|
||||
LogsWsMessage,
|
||||
NotificationWsMessage,
|
||||
PreviewMethod,
|
||||
ProgressStateWsMessage,
|
||||
ProgressTextWsMessage,
|
||||
ProgressWsMessage,
|
||||
@@ -44,8 +46,7 @@ import type {
|
||||
StatusWsMessageStatus,
|
||||
SystemStats,
|
||||
User,
|
||||
UserDataFullInfo,
|
||||
PreviewMethod
|
||||
UserDataFullInfo
|
||||
} from '@/schemas/apiSchema'
|
||||
import type {
|
||||
JobDetail,
|
||||
@@ -951,7 +952,7 @@ export class ComfyApi extends EventTarget {
|
||||
* @param {*} type The endpoint to post to
|
||||
* @param {*} body Optional POST data
|
||||
*/
|
||||
async #postItem(type: string, body: any) {
|
||||
async #postItem(type: string, body?: Record<string, unknown>) {
|
||||
try {
|
||||
await this.fetchApi('/' + type, {
|
||||
method: 'POST',
|
||||
@@ -1074,7 +1075,7 @@ export class ComfyApi extends EventTarget {
|
||||
*/
|
||||
async storeUserData(
|
||||
file: string,
|
||||
data: any,
|
||||
data: unknown,
|
||||
options: RequestInit & {
|
||||
overwrite?: boolean
|
||||
stringify?: boolean
|
||||
@@ -1091,7 +1092,7 @@ export class ComfyApi extends EventTarget {
|
||||
`/userdata/${encodeURIComponent(file)}?overwrite=${options.overwrite}&full_info=${options.full_info}`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: options?.stringify ? JSON.stringify(data) : data,
|
||||
body: options?.stringify ? JSON.stringify(data) : (data as BodyInit),
|
||||
...options
|
||||
}
|
||||
)
|
||||
@@ -1251,7 +1252,7 @@ export class ComfyApi extends EventTarget {
|
||||
*
|
||||
* @returns The custom nodes i18n data
|
||||
*/
|
||||
async getCustomNodesI18n(): Promise<Record<string, any>> {
|
||||
async getCustomNodesI18n(): Promise<CustomNodesI18n> {
|
||||
return (await axios.get(this.apiURL('/i18n'))).data
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import _ from 'es-toolkit/compat'
|
||||
import * as jsondiffpatch from 'jsondiffpatch'
|
||||
import log from 'loglevel'
|
||||
|
||||
import type { CanvasPointerEvent } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
ComfyWorkflow,
|
||||
@@ -40,7 +41,7 @@ export class ChangeTracker {
|
||||
_restoringState: boolean = false
|
||||
|
||||
ds?: { scale: number; offset: [number, number] }
|
||||
nodeOutputs?: Record<string, any>
|
||||
nodeOutputs?: Record<string, ExecutedWsMessage['output']>
|
||||
|
||||
private subgraphState?: {
|
||||
navigation: string[]
|
||||
@@ -303,11 +304,11 @@ export class ChangeTracker {
|
||||
const prompt = LGraphCanvas.prototype.prompt
|
||||
LGraphCanvas.prototype.prompt = function (
|
||||
title: string,
|
||||
value: any,
|
||||
callback: (v: any) => void,
|
||||
event: any
|
||||
value: string | number,
|
||||
callback: (v: string) => void,
|
||||
event: CanvasPointerEvent
|
||||
) {
|
||||
const extendedCallback = (v: any) => {
|
||||
const extendedCallback = (v: string) => {
|
||||
callback(v)
|
||||
checkState()
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { $el } from '../../ui'
|
||||
import { ComfyDialog } from '../dialog'
|
||||
|
||||
export class ComfyAsyncDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
#resolve: (value: any) => void
|
||||
type DialogAction<T> = string | { value?: T; text: string }
|
||||
|
||||
constructor(actions?: Array<string | { value?: any; text: string }>) {
|
||||
export class ComfyAsyncDialog<
|
||||
T = string | null
|
||||
> extends ComfyDialog<HTMLDialogElement> {
|
||||
#resolve: (value: T | null) => void = () => {}
|
||||
|
||||
constructor(actions?: Array<DialogAction<T>>) {
|
||||
super(
|
||||
'dialog.comfy-dialog.comfyui-dialog',
|
||||
// @ts-expect-error fixme ts strict error
|
||||
actions?.map((opt) => {
|
||||
if (typeof opt === 'string') {
|
||||
opt = { text: opt }
|
||||
}
|
||||
const action = typeof opt === 'string' ? { text: opt } : opt
|
||||
return $el('button.comfyui-button', {
|
||||
type: 'button',
|
||||
textContent: opt.text,
|
||||
onclick: () => this.close(opt.value ?? opt.text)
|
||||
})
|
||||
textContent: action.text,
|
||||
onclick: () => this.close((action.value ?? action.text) as T)
|
||||
}) as HTMLButtonElement
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
override show(html: string | HTMLElement | HTMLElement[]) {
|
||||
override show(html: string | HTMLElement | HTMLElement[]): Promise<T | null> {
|
||||
this.element.addEventListener('close', () => {
|
||||
this.close()
|
||||
})
|
||||
@@ -34,7 +34,7 @@ export class ComfyAsyncDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
})
|
||||
}
|
||||
|
||||
showModal(html: string | HTMLElement | HTMLElement[]) {
|
||||
showModal(html: string | HTMLElement | HTMLElement[]): Promise<T | null> {
|
||||
this.element.addEventListener('close', () => {
|
||||
this.close()
|
||||
})
|
||||
@@ -47,22 +47,22 @@ export class ComfyAsyncDialog extends ComfyDialog<HTMLDialogElement> {
|
||||
})
|
||||
}
|
||||
|
||||
override close(result = null) {
|
||||
override close(result: T | null = null) {
|
||||
this.#resolve(result)
|
||||
this.element.close()
|
||||
super.close()
|
||||
}
|
||||
|
||||
static async prompt({
|
||||
static async prompt<U = string>({
|
||||
title = null,
|
||||
message,
|
||||
actions
|
||||
}: {
|
||||
title: string | null
|
||||
message: string
|
||||
actions: Array<string | { value?: any; text: string }>
|
||||
}) {
|
||||
const dialog = new ComfyAsyncDialog(actions)
|
||||
actions: Array<DialogAction<U>>
|
||||
}): Promise<U | null> {
|
||||
const dialog = new ComfyAsyncDialog<U>(actions)
|
||||
const content = [$el('span', message)]
|
||||
if (title) {
|
||||
content.unshift($el('h3', title))
|
||||
|
||||
@@ -4,11 +4,10 @@ export class ComfyDialog<
|
||||
T extends HTMLElement = HTMLElement
|
||||
> extends EventTarget {
|
||||
element: T
|
||||
// @ts-expect-error fixme ts strict error
|
||||
textElement: HTMLElement
|
||||
textElement!: HTMLElement
|
||||
#buttons: HTMLButtonElement[] | null
|
||||
|
||||
constructor(type = 'div', buttons = null) {
|
||||
constructor(type = 'div', buttons: HTMLButtonElement[] | null = null) {
|
||||
super()
|
||||
this.#buttons = buttons
|
||||
this.element = $el(type + '.comfy-modal', { parent: document.body }, [
|
||||
@@ -35,8 +34,7 @@ export class ComfyDialog<
|
||||
this.element.style.display = 'none'
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
show(html) {
|
||||
show(html: string | HTMLElement | HTMLElement[]): void {
|
||||
if (typeof html === 'string') {
|
||||
this.textElement.innerHTML = html
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user