Compare commits

...

5 Commits

Author SHA1 Message Date
Austin
52197934af Add test 2026-05-19 18:55:16 -07:00
Austin
57aa435ae2 Implement resolveVirtualValue for primitives 2026-05-19 18:55:13 -07:00
Austin
aaa9498fb4 Add resolveVirtualValue method 2026-05-19 18:55:10 -07:00
Austin
960cf20cbe Use original spec verbatim for widgetconfig 2026-05-19 18:55:07 -07:00
Austin
8f4081bb75 Remove special case for curve widgets
This will never be used now that interpolation information is included
and should have been applied as a `serializeValue` function
2026-05-19 18:55:01 -07:00
6 changed files with 67 additions and 4 deletions

View File

@@ -75,4 +75,46 @@ test.describe('Primitive Node', { tag: ['@screenshot', '@node'] }, () => {
)
await expect(errorOverlay).toBeVisible()
})
test('Can serialize to forced inputs @vue-nodes', async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.settings.setSetting(
'Comfy.NodeSearchBoxImpl',
'v1 (legacy)'
)
await test.step('Add nodes', async () => {
await comfyPage.menu.topbar.newWorkflowButton.click()
await comfyPage.nextFrame()
await comfyPage.page.mouse.dblclick(250, 500, { delay: 5 })
await comfyPage.searchBox.fillAndSelectFirstNode('Primitive')
await expect(comfyPage.searchBox.input).toBeHidden()
await comfyPage.page.mouse.dblclick(600, 500, { delay: 5 })
await comfyPage.searchBox.fillAndSelectFirstNode('Node With Force Input')
})
const primitive = await comfyPage.vueNodes.getFixtureByTitle('Primitive')
const node = await comfyPage.vueNodes.getFixtureByTitle(
'Node With Force Input'
)
await test.step('Link nodes', async () => {
await primitive.getSlot('').dragTo(node.getSlot('int_input').first())
await expect
.poll(() => comfyPage.vueNodes.isSlotConnected(primitive.getSlot('')))
.toBe(true)
})
const { input } = comfyPage.vueNodes.getInputNumberControls(
await comfyPage.vueNodes.getWidgetByName('Primitive', 'value')
)
await input.fill('5')
await input.blur()
const serializedValue = await comfyPage.page.evaluate(async () => {
const { output } = await app!.graphToPrompt()
return output[2].inputs.int_input
})
expect(serializedValue, 'Serialized prompt has primitive value').toBe(5)
})
})

View File

@@ -412,6 +412,14 @@ export class PrimitiveNode extends LGraphNode {
this._removeWidgets()
}
override resolveVirtualValue() {
const w = this.widgets?.[0]
let value = w?.serializeValue ? w.serializeValue(this, 0) : w?.value
return value && this.properties[replacePropertyName] && this.graph
? applyTextReplacements(this.graph, String(value))
: value
}
}
export function getWidgetConfig(

View File

@@ -1222,6 +1222,12 @@ export class LGraphNode
slot: number
): { node: LGraphNode; slot: number } | undefined
/**
* Resolves the value of an to be sent from an output slot
* Ensures virtual nodes can apply state to socketless widgets
*/
resolveVirtualValue?(slot: number): unknown
/**
* Returns the link info in the connection of an input slot
* @returns object or null

View File

@@ -307,6 +307,15 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode {
return inputNodeDto.resolveOutput(virtualSource.slot, type, visited)
}
if (this.node.resolveVirtualValue) {
return {
node: this,
origin_id: this.id,
origin_slot: -1,
widgetInfo: { value: this.node.resolveVirtualValue(slot) }
}
}
const virtualLink = this.node.getInputLink(slot)
if (virtualLink) {
const { inputNode } = virtualLink.resolve(this.graph)

View File

@@ -305,7 +305,7 @@ export const useLitegraphService = () => {
}
if (!widget?.options?.socketless) {
const inputSpecV1 = transformInputSpecV2ToV1(widgetInputSpec)
const inputSpecV1 = transformInputSpecV2ToV1(inputSpec)
node.addInput(inputName, inputSpec.type, {
shape: inputSpec.isOptional ? RenderShape.HollowCircle : undefined,
localized_name: st(nameKey, inputName),

View File

@@ -108,9 +108,7 @@ export const graphToPrompt = async (
// The backend automatically unwraps the object to an array during
// execution.
inputs[widget.name] = Array.isArray(widgetValue)
? widget.type === 'curve'
? { __type__: 'CURVE', __value__: widgetValue }
: { __value__: widgetValue }
? { __value__: widgetValue }
: widgetValue
}
}