mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
Implement a CustomCombo node (#7142)
Adds a frontend support for a "Custom Combo" node which contains a Combo Widget, and a growing list of string inputs that determine the options available to the combo.  By promoting the combo widget on this node, a list of options determined by workflow builders can be exposed to end users. CC: @Kosinkadink ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7142-Implement-a-CustomCombo-node-2bf6d73d36508161951df01009a7262f) by [Unito](https://www.unito.io)
This commit is contained in:
113
src/extensions/core/customCombo.ts
Normal file
113
src/extensions/core/customCombo.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { shallowReactive } from 'vue'
|
||||||
|
|
||||||
|
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||||
|
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
||||||
|
import { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||||
|
import { LLink } from '@/lib/litegraph/src/litegraph'
|
||||||
|
import { app } from '@/scripts/app'
|
||||||
|
|
||||||
|
function applyToGraph(this: LGraphNode, extraLinks: LLink[] = []) {
|
||||||
|
if (!this.outputs[0].links?.length || !this.graph) return
|
||||||
|
|
||||||
|
const links = [
|
||||||
|
...this.outputs[0].links.map((l) => this.graph!.links[l]),
|
||||||
|
...extraLinks
|
||||||
|
]
|
||||||
|
let v = this.widgets?.[0].value
|
||||||
|
// For each output link copy our value over the original widget value
|
||||||
|
for (const linkInfo of links) {
|
||||||
|
const node = this.graph?.getNodeById(linkInfo.target_id)
|
||||||
|
const input = node?.inputs[linkInfo.target_slot]
|
||||||
|
if (!input) {
|
||||||
|
console.warn('Unable to resolve node or input for link', linkInfo)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetName = input.widget?.name
|
||||||
|
if (!widgetName) {
|
||||||
|
console.warn('Invalid widget or widget name', input.widget)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = node.widgets?.find((w) => w.name === widgetName)
|
||||||
|
if (!widget) {
|
||||||
|
console.warn(`Unable to find widget "${widgetName}" on node [${node.id}]`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.value = v
|
||||||
|
widget.callback?.(
|
||||||
|
widget.value,
|
||||||
|
app.canvas,
|
||||||
|
node,
|
||||||
|
app.canvas.graph_mouse,
|
||||||
|
{} as CanvasPointerEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onNodeCreated(this: LGraphNode) {
|
||||||
|
this.applyToGraph = useChainCallback(this.applyToGraph, applyToGraph)
|
||||||
|
|
||||||
|
const comboWidget = this.widgets![0]
|
||||||
|
const values = shallowReactive<string[]>([])
|
||||||
|
comboWidget.options.values = values
|
||||||
|
|
||||||
|
const updateCombo = () => {
|
||||||
|
values.splice(
|
||||||
|
0,
|
||||||
|
values.length,
|
||||||
|
...this.widgets!.filter(
|
||||||
|
(w) => w.name.startsWith('option') && w.value
|
||||||
|
).map((w) => `${w.value}`)
|
||||||
|
)
|
||||||
|
if (app.configuringGraph) return
|
||||||
|
if (values.includes(`${comboWidget.value}`)) return
|
||||||
|
comboWidget.value = values[0] ?? ''
|
||||||
|
comboWidget.callback?.(comboWidget.value)
|
||||||
|
}
|
||||||
|
comboWidget.callback = useChainCallback(comboWidget.callback, () =>
|
||||||
|
this.applyToGraph!()
|
||||||
|
)
|
||||||
|
|
||||||
|
function addOption(node: LGraphNode) {
|
||||||
|
if (!node.widgets) return
|
||||||
|
const newCount = node.widgets.length - 1
|
||||||
|
node.addWidget('string', `option${newCount}`, '', () => {})
|
||||||
|
const widget = node.widgets.at(-1)
|
||||||
|
if (!widget) return
|
||||||
|
|
||||||
|
let value = ''
|
||||||
|
Object.defineProperty(widget, 'value', {
|
||||||
|
get() {
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
value = v
|
||||||
|
updateCombo()
|
||||||
|
if (!node.widgets) return
|
||||||
|
const lastWidget = node.widgets.at(-1)
|
||||||
|
if (lastWidget === this) {
|
||||||
|
if (v) addOption(node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (v || node.widgets.at(-2) !== this || lastWidget?.value) return
|
||||||
|
node.widgets.pop()
|
||||||
|
node.computeSize(node.size)
|
||||||
|
this.callback(v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addOption(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.registerExtension({
|
||||||
|
name: 'Comfy.CustomCombo',
|
||||||
|
beforeRegisterNodeDef(nodeType, nodeData) {
|
||||||
|
if (nodeData?.name !== 'CustomCombo') return
|
||||||
|
nodeType.prototype.onNodeCreated = useChainCallback(
|
||||||
|
nodeType.prototype.onNodeCreated,
|
||||||
|
onNodeCreated
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -2,6 +2,7 @@ import { isCloud } from '@/platform/distribution/types'
|
|||||||
|
|
||||||
import './clipspace'
|
import './clipspace'
|
||||||
import './contextMenuFilter'
|
import './contextMenuFilter'
|
||||||
|
import './customCombo'
|
||||||
import './dynamicPrompts'
|
import './dynamicPrompts'
|
||||||
import './editAttention'
|
import './editAttention'
|
||||||
import './electronAdapter'
|
import './electronAdapter'
|
||||||
|
|||||||
Reference in New Issue
Block a user