Skip to content

Commit 2717d59

Browse files
authored
Fix reactivity of vue subgraph price badges (#12029)
When a subgraph contains partner nodes with price badges, those badges are also displayed on the subgraphNode. The reactivity here was spotty: The price badges would fail to display unless the user had navigated into the subgraph on the current page load. Fixing this is performed in 2 steps: - Firing a `node:property:changed` event when the badges contained in a subgraph are updated - Extending the reactivity updates so that badges update in vue mode despite using the litegraph badge getter. This PR also includes a minor styling tweak to fix text alignment on price badges | Before | After | | ------ | ----- | | <img width="360" alt="before" src="https://github.com/user-attachments/assets/56a95cbe-12c9-43b0-8664-34e52b6415ac" /> | <img width="360" alt="after" src="https://github.com/user-attachments/assets/bf4a0d81-21e4-4afc-946e-eba5967f1715" />| Resolves FE-346 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12029-Fix-reactivity-of-vue-subgraph-price-badges-3586d73d3650813cb12fe265090940e4) by [Unito](https://www.unito.io)
1 parent d63b0f0 commit 2717d59

8 files changed

Lines changed: 91 additions & 7 deletions

File tree

-307 Bytes
Loading
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {
2+
comfyPageFixture as test,
3+
comfyExpect as expect
4+
} from '@e2e/fixtures/ComfyPage'
5+
6+
test('Price badge displays on subgraphs @vue-nodes', async ({ comfyPage }) => {
7+
const apiNodeName = 'Node With Price Badge'
8+
await comfyPage.settings.setSetting('Comfy.NodeSearchBoxImpl', 'v1 (legacy)')
9+
10+
const priceBadge = comfyPage.page.locator('.lg-node-header i + span')
11+
const apiNode = comfyPage.vueNodes.getNodeByTitle(apiNodeName)
12+
13+
await comfyPage.menu.topbar.newWorkflowButton.click()
14+
await comfyPage.nextFrame()
15+
16+
await comfyPage.page.mouse.dblclick(500, 500, { delay: 5 })
17+
await comfyPage.searchBox.fillAndSelectFirstNode(apiNodeName)
18+
await expect(comfyPage.searchBox.input).toBeHidden()
19+
await expect(apiNode, 'Add partner node').toBeVisible()
20+
await expect(apiNode.locator(priceBadge), 'Has price badge').toBeVisible()
21+
22+
await comfyPage.contextMenu
23+
.openForVueNode(apiNode)
24+
.then((m) => m.clickMenuItemExact('Convert to Subgraph'))
25+
const subgraphNode = comfyPage.vueNodes.getNodeByTitle('New Subgraph')
26+
await expect(subgraphNode, 'Convert to Subgraph').toBeVisible()
27+
28+
const nodePrice = subgraphNode.locator(priceBadge)
29+
await expect(nodePrice, 'subgraphNode has price badge').toBeVisible()
30+
const initialPrice = Number(await nodePrice.innerText())
31+
32+
await comfyPage.subgraph.editor.togglePromotion(subgraphNode, {
33+
nodeName: apiNodeName,
34+
widgetName: 'price',
35+
toState: true
36+
})
37+
await comfyPage.vueNodes.selectComboOption('New Subgraph', 'price', '2x')
38+
await expect(nodePrice, 'Price is reactive').toHaveText(
39+
String(initialPrice * 2)
40+
)
41+
})

src/components/node/CreditBadge.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</span>
1313
<span
1414
v-if="rest"
15-
class="-ml-2.5 max-w-max min-w-0 grow basis-0 truncate rounded-r-full bg-component-node-widget-background"
15+
class="-ml-2.5 flex h-5 max-w-max min-w-0 grow basis-0 items-center truncate rounded-r-full bg-component-node-widget-background text-xs"
1616
>
1717
<span class="pr-2" v-text="rest" />
1818
</span>

src/composables/graph/useGraphNodeManager.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export interface WidgetSlotMetadata {
4848
type: string
4949
}
5050

51+
type Badges = (LGraphBadge | (() => LGraphBadge))[]
52+
5153
/**
5254
* Minimal render-specific widget data extracted from LiteGraph widgets.
5355
* Value and metadata (label, hidden, disabled, etc.) are accessed via widgetValueStore.
@@ -107,7 +109,7 @@ export interface VueNodeData {
107109
title: string
108110
type: string
109111
apiNode?: boolean
110-
badges?: (LGraphBadge | (() => LGraphBadge))[]
112+
badges?: Badges
111113
bgcolor?: string
112114
color?: string
113115
flags?: {
@@ -786,6 +788,12 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
786788
showAdvanced: Boolean(propertyEvent.newValue)
787789
})
788790
break
791+
case 'badges':
792+
vueNodeData.set(nodeId, {
793+
...currentData,
794+
badges: propertyEvent.newValue as Badges
795+
})
796+
break
789797
}
790798
}
791799
},

src/composables/node/useNodePricing.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,9 +625,9 @@ describe('useNodePricing', () => {
625625
getNodeDisplayPrice(node)
626626
await new Promise((r) => setTimeout(r, 50))
627627

628-
// VueNodes path bumps per-node ref instead of the global tick.
628+
// VueNodes path bumps per-node ref and the global tick.
629629
expect(getNodeRevisionRef(node.id).value).toBeGreaterThan(revBefore)
630-
expect(pricingRevision.value).toBe(tickBefore)
630+
expect(pricingRevision.value).toBeGreaterThan(tickBefore)
631631
} finally {
632632
LiteGraph.vueNodesMode = false
633633
}

src/composables/node/useNodePricing.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -509,10 +509,8 @@ const scheduleEvaluation = (
509509
if (LiteGraph.vueNodesMode) {
510510
// VueNodes mode: bump per-node revision (only this node re-renders)
511511
getNodeRevisionRef(node.id).value++
512-
} else {
513-
// Nodes 1.0 mode: bump global tick to trigger setDirtyCanvas
514-
pricingTick.value++
515512
}
513+
pricingTick.value++
516514
})
517515

518516
inflight.set(node, { sig, promise })

src/composables/node/usePriceBadge.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ export const usePriceBadge = () => {
1818
} else {
1919
node.badges.push(...newBadges)
2020
}
21+
const graph = node.graph
22+
if (!graph) return
23+
graph.trigger('node:property:changed', {
24+
type: 'node:property:changed',
25+
nodeId: node.id,
26+
property: 'badges',
27+
oldValue: node.badges,
28+
newValue: node.badges
29+
})
2130
}
2231
function collectCreditsBadges(
2332
graph: LGraph,

tools/devtools/nodes/inputs.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import time
44

5+
from comfy_api.v0_0_2 import IO
6+
57

68
class LongComboDropdown:
79
@classmethod
@@ -317,6 +319,30 @@ def INPUT_TYPES(cls):
317319
def node_with_legacy_widget(self):
318320
return ()
319321

322+
class NodeWithPriceBadge(IO.ComfyNode):
323+
@classmethod
324+
def define_schema(cls):
325+
return IO.Schema(
326+
node_id="DevToolsNodeWithPriceBadge",
327+
display_name="Node With Price Badge",
328+
description="An API node with a price badge",
329+
inputs=[IO.Combo.Input("price", options=["1x", "2x", "3x"])],
330+
is_api_node=True,
331+
price_badge=IO.PriceBadge(
332+
depends_on=IO.PriceBadgeDepends(widgets=["price"]),
333+
expr="""
334+
(
335+
$p := widgets.price;
336+
{"type":"usd","usd": $contains($p, "2x") ? 2 : $contains($p, "3x") ? 3 : 1}
337+
)
338+
""",
339+
),
340+
)
341+
342+
@classmethod
343+
async def execute(cls, price):
344+
return IO.NodeOutput()
345+
320346

321347
NODE_CLASS_MAPPINGS = {
322348
"DevToolsLongComboDropdown": LongComboDropdown,
@@ -334,6 +360,7 @@ def node_with_legacy_widget(self):
334360
"DevToolsNodeWithValidation": NodeWithValidation,
335361
"DevToolsNodeWithV2ComboInput": NodeWithV2ComboInput,
336362
"DevToolsNodeWithLegacyWidget": NodeWithLegacyWidget,
363+
"DevToolsNodeWithPriceBadge": NodeWithPriceBadge,
337364
}
338365

339366
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -352,6 +379,7 @@ def node_with_legacy_widget(self):
352379
"DevToolsNodeWithValidation": "Node With Validation",
353380
"DevToolsNodeWithV2ComboInput": "Node With V2 Combo Input",
354381
"DevToolsNodeWithLegacyWidget": "Node With Legacy Widget",
382+
"DevToolsNodeWithPriceBadge": "Node With Price Badge",
355383
}
356384

357385
__all__ = [

0 commit comments

Comments
 (0)