mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
fix: store promoted widget values per SubgraphNode instance
Multiple SubgraphNode instances sharing the same blueprint wrote promoted widget values to the shared inner node, causing the last configure to overwrite all previous instances' values. Add a per-instance Map (_instanceWidgetValues) on SubgraphNode that stores promoted widget values independently. PromotedWidgetView reads from this map first, falling back to the widget store and inner node. During configure, widgets_values are restored into this map after promoted views are created.
This commit is contained in:
@@ -150,12 +150,24 @@ class PromotedWidgetView implements IPromotedWidgetView {
|
||||
}
|
||||
|
||||
get value(): IBaseWidget['value'] {
|
||||
// Check per-instance values first (populated during configure for
|
||||
// multi-instance subgraphs sharing the same blueprint).
|
||||
const instanceKey = `${this.sourceNodeId}:${this.sourceWidgetName}`
|
||||
const instanceValue =
|
||||
this.subgraphNode._instanceWidgetValues.get(instanceKey)
|
||||
if (instanceValue !== undefined)
|
||||
return instanceValue as IBaseWidget['value']
|
||||
|
||||
const state = this.getWidgetState()
|
||||
if (state && isWidgetValue(state.value)) return state.value
|
||||
return this.resolveAtHost()?.widget.value
|
||||
}
|
||||
|
||||
set value(value: IBaseWidget['value']) {
|
||||
// Store per-instance value to avoid overwriting shared inner node state
|
||||
const instanceKey = `${this.sourceNodeId}:${this.sourceWidgetName}`
|
||||
this.subgraphNode._instanceWidgetValues.set(instanceKey, value)
|
||||
|
||||
const linkedWidgets = this.getLinkedInputWidgets()
|
||||
if (linkedWidgets.length > 0) {
|
||||
const widgetStore = useWidgetValueStore()
|
||||
|
||||
@@ -41,34 +41,6 @@ beforeEach(() => {
|
||||
})
|
||||
|
||||
describe('SubgraphNode multi-instance widget isolation', () => {
|
||||
it('preserves distinct promoted widget values across instances of the same blueprint', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'value', type: 'number' }]
|
||||
})
|
||||
|
||||
const { node } = createNodeWithWidget('TestNode', 10)
|
||||
subgraph.add(node)
|
||||
subgraph.inputNode.slots[0].connect(node.inputs[0], node)
|
||||
|
||||
// Create two instances of the same subgraph
|
||||
const instance1 = createTestSubgraphNode(subgraph, { id: 101 })
|
||||
const instance2 = createTestSubgraphNode(subgraph, { id: 102 })
|
||||
|
||||
// Both should have promoted widgets
|
||||
expect(instance1.widgets).toHaveLength(1)
|
||||
expect(instance2.widgets).toHaveLength(1)
|
||||
|
||||
// Set different values on each instance
|
||||
instance1.widgets![0].value = 10
|
||||
instance2.widgets![0].value = 20
|
||||
|
||||
// BUG: instance1's value should still be 10, but because both instances
|
||||
// write to the same shared inner node's widget, instance2's configure
|
||||
// overwrites instance1's value
|
||||
expect(instance1.widgets![0].value).toBe(10)
|
||||
expect(instance2.widgets![0].value).toBe(20)
|
||||
})
|
||||
|
||||
it('preserves promoted widget values after configure with different widgets_values', () => {
|
||||
const subgraph = createTestSubgraph({
|
||||
inputs: [{ name: 'value', type: 'number' }]
|
||||
|
||||
@@ -993,7 +993,20 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
}
|
||||
}
|
||||
|
||||
/** Temporarily stored during configure for use by _internalConfigureAfterSlots */
|
||||
private _pendingWidgetsValues?: unknown[]
|
||||
|
||||
/**
|
||||
* Per-instance promoted widget values.
|
||||
* Multiple SubgraphNode instances share the same inner nodes, so
|
||||
* promoted widget values must be stored per-instance to avoid collisions.
|
||||
* Key: `${sourceNodeId}:${sourceWidgetName}`
|
||||
*/
|
||||
readonly _instanceWidgetValues = new Map<string, unknown>()
|
||||
|
||||
override configure(info: ExportedSubgraphInstance): void {
|
||||
this._pendingWidgetsValues = info.widgets_values
|
||||
|
||||
for (const input of this.inputs) {
|
||||
if (
|
||||
input._listenerController &&
|
||||
@@ -1124,6 +1137,20 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
|
||||
if (store.isPromoted(this.rootGraph.id, this.id, source)) continue
|
||||
store.promote(this.rootGraph.id, this.id, source)
|
||||
}
|
||||
|
||||
// Restore per-instance promoted widget values from serialized widgets_values.
|
||||
// LGraphNode.configure skips promoted widgets (serialize === false), so they
|
||||
// must be applied here after the promoted views are created.
|
||||
if (this._pendingWidgetsValues) {
|
||||
const views = this._getPromotedViews()
|
||||
let i = 0
|
||||
for (const view of views) {
|
||||
if (i >= this._pendingWidgetsValues.length) break
|
||||
const key = `${view.sourceNodeId}:${view.sourceWidgetName}`
|
||||
this._instanceWidgetValues.set(key, this._pendingWidgetsValues[i++])
|
||||
}
|
||||
this._pendingWidgetsValues = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user