Skip to content

Commit 9dd6da3

Browse files
authored
Support node deprecated/experimental flag (#563)
* Add deprecated field * Hide deprecated nodes * Add experimental node show/hide * Add setting tooltips * nit * nit * nit
1 parent 269e468 commit 9dd6da3

File tree

7 files changed

+120
-9
lines changed

7 files changed

+120
-9
lines changed

src/components/graph/GraphCanvas.vue

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import SideToolbar from '@/components/sidebar/SideToolbar.vue'
1616
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
1717
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
1818
import NodeTooltip from '@/components/graph/NodeTooltip.vue'
19-
import { ref, computed, onUnmounted, watch, onMounted } from 'vue'
19+
import { ref, computed, onUnmounted, watch, onMounted, watchEffect } from 'vue'
2020
import { app as comfyApp } from '@/scripts/app'
2121
import { useSettingStore } from '@/stores/settingStore'
2222
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
@@ -36,6 +36,7 @@ import {
3636
const emit = defineEmits(['ready'])
3737
const canvasRef = ref<HTMLCanvasElement | null>(null)
3838
const settingStore = useSettingStore()
39+
const nodeDefStore = useNodeDefStore()
3940
const workspaceStore = useWorkspaceStore()
4041
4142
const betaMenuEnabled = computed(
@@ -63,6 +64,16 @@ watch(
6364
{ immediate: true }
6465
)
6566
67+
watchEffect(() => {
68+
nodeDefStore.showDeprecated = settingStore.get('Comfy.Node.ShowDeprecated')
69+
})
70+
71+
watchEffect(() => {
72+
nodeDefStore.showExperimental = settingStore.get(
73+
'Comfy.Node.ShowExperimental'
74+
)
75+
})
76+
6677
let dropTargetCleanup = () => {}
6778
6879
onMounted(async () => {
@@ -98,7 +109,7 @@ onMounted(async () => {
98109
const comfyNodeName = event.source.element.getAttribute(
99110
'data-comfy-node-name'
100111
)
101-
const nodeDef = useNodeDefStore().nodeDefsByName[comfyNodeName]
112+
const nodeDef = nodeDefStore.nodeDefsByName[comfyNodeName]
102113
comfyApp.addNodeOnGraph(nodeDef, { pos })
103114
}
104115
})

src/components/sidebar/tabs/NodeLibrarySidebarTab.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ const settingStore = useSettingStore()
112112
const sidebarLocation = computed<'left' | 'right'>(() =>
113113
settingStore.get('Comfy.Sidebar.Location')
114114
)
115+
115116
const nodePreviewStyle = ref<Record<string, string>>({
116117
position: 'absolute',
117118
top: '0px',

src/stores/nodeDefStore.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NodeSearchService } from '@/services/nodeSearchService'
22
import { ComfyNodeDef } from '@/types/apiTypes'
33
import { defineStore } from 'pinia'
4-
import { Type, Transform, plainToClass } from 'class-transformer'
4+
import { Type, Transform, plainToClass, Expose } from 'class-transformer'
55
import { ComfyWidgetConstructor } from '@/scripts/widgets'
66
import { TreeNode } from 'primevue/treenode'
77
import { buildTree } from '@/utils/treeUtil'
@@ -166,6 +166,23 @@ export class ComfyNodeDefImpl {
166166
python_module: string
167167
description: string
168168

169+
@Transform(({ value, obj }) => value ?? obj.category === '', {
170+
toClassOnly: true
171+
})
172+
@Type(() => Boolean)
173+
@Expose()
174+
deprecated: boolean
175+
176+
@Transform(
177+
({ value, obj }) => value ?? obj.category.startsWith('_for_testing'),
178+
{
179+
toClassOnly: true
180+
}
181+
)
182+
@Type(() => Boolean)
183+
@Expose()
184+
experimental: boolean
185+
169186
@Type(() => ComfyInputsSpec)
170187
input: ComfyInputsSpec
171188

@@ -229,22 +246,34 @@ export const SYSTEM_NODE_DEFS: Record<string, ComfyNodeDef> = {
229246
interface State {
230247
nodeDefsByName: Record<string, ComfyNodeDefImpl>
231248
widgets: Record<string, ComfyWidgetConstructor>
249+
showDeprecated: boolean
250+
showExperimental: boolean
232251
}
233252

234253
export const useNodeDefStore = defineStore('nodeDef', {
235254
state: (): State => ({
236255
nodeDefsByName: {},
237-
widgets: {}
256+
widgets: {},
257+
showDeprecated: false,
258+
showExperimental: false
238259
}),
239260
getters: {
240261
nodeDefs(state) {
241262
return Object.values(state.nodeDefsByName)
242263
},
243-
nodeSearchService(state) {
244-
return new NodeSearchService(Object.values(state.nodeDefsByName))
264+
// Node defs that are not deprecated
265+
visibleNodeDefs(state) {
266+
return this.nodeDefs.filter(
267+
(nodeDef: ComfyNodeDefImpl) =>
268+
(state.showDeprecated || !nodeDef.deprecated) &&
269+
(state.showExperimental || !nodeDef.experimental)
270+
)
271+
},
272+
nodeSearchService() {
273+
return new NodeSearchService(this.visibleNodeDefs)
245274
},
246275
nodeTree(): TreeNode {
247-
return buildTree(this.nodeDefs, (nodeDef: ComfyNodeDefImpl) => [
276+
return buildTree(this.visibleNodeDefs, (nodeDef: ComfyNodeDefImpl) => [
248277
...nodeDef.category.split('/'),
249278
nodeDef.display_name
250279
])

src/stores/settingStore.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,24 @@ export const useSettingStore = defineStore('setting', {
147147
type: 'boolean',
148148
defaultValue: true
149149
})
150+
151+
app.ui.settings.addSetting({
152+
id: 'Comfy.Node.ShowDeprecated',
153+
name: 'Show deprecated nodes in search',
154+
tooltip:
155+
'Deprecated nodes are hidden by default in the UI, but remain functional in existing workflows that use them.',
156+
type: 'boolean',
157+
defaultValue: false
158+
})
159+
160+
app.ui.settings.addSetting({
161+
id: 'Comfy.Node.ShowExperimental',
162+
name: 'Show experimental nodes in search',
163+
tooltip:
164+
'Experimental nodes are marked as such in the UI and may be subject to significant changes or removal in future versions. Use with caution in production workflows',
165+
type: 'boolean',
166+
defaultValue: true
167+
})
150168
},
151169

152170
set<K extends keyof Settings>(key: K, value: Settings[K]) {

src/types/apiTypes.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,9 @@ const zComfyNodeDef = z.object({
335335
description: z.string(),
336336
category: z.string(),
337337
output_node: z.boolean(),
338-
python_module: z.string()
338+
python_module: z.string(),
339+
deprecated: z.boolean().optional(),
340+
experimental: z.boolean().optional()
339341
})
340342

341343
// `/object_info`
@@ -419,6 +421,8 @@ const zSettings = z.record(z.any()).and(
419421
'Comfy.NodeSearchBoxImpl': z.enum(['default', 'simple']),
420422
'Comfy.NodeSearchBoxImpl.ShowCategory': z.boolean(),
421423
'Comfy.NodeSuggestions.number': z.number(),
424+
'Comfy.Node.ShowDeprecated': z.boolean(),
425+
'Comfy.Node.ShowExperimental': z.boolean(),
422426
'Comfy.PreviewFormat': z.string(),
423427
'Comfy.PromptFilename': z.boolean(),
424428
'Comfy.Sidebar.Location': z.enum(['left', 'right']),

tests-ui/tests/apiTypes.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ const EXAMPLE_NODE_DEF: ComfyNodeDef = {
1616
description: '',
1717
python_module: 'nodes',
1818
category: 'loaders',
19-
output_node: false
19+
output_node: false,
20+
experimental: false,
21+
deprecated: false
2022
}
2123

2224
describe('validateNodeDef', () => {

tests-ui/tests/nodeDef.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,52 @@ describe('ComfyNodeDefImpl', () => {
194194
is_list: false
195195
}
196196
])
197+
expect(result.deprecated).toBe(false)
198+
})
199+
200+
it('should transform a deprecated basic node definition', () => {
201+
const plainObject = {
202+
name: 'TestNode',
203+
display_name: 'Test Node',
204+
category: 'Testing',
205+
python_module: 'test_module',
206+
description: 'A test node',
207+
input: {
208+
required: {
209+
intInput: ['INT', { min: 0, max: 100, default: 50 }]
210+
}
211+
},
212+
output: ['INT'],
213+
output_is_list: [false],
214+
output_name: ['intOutput'],
215+
deprecated: true
216+
}
217+
218+
const result = plainToClass(ComfyNodeDefImpl, plainObject)
219+
expect(result.deprecated).toBe(true)
220+
})
221+
222+
// Legacy way of marking a node as deprecated
223+
it('should mark deprecated with empty category', () => {
224+
const plainObject = {
225+
name: 'TestNode',
226+
display_name: 'Test Node',
227+
// Empty category should be treated as deprecated
228+
category: '',
229+
python_module: 'test_module',
230+
description: 'A test node',
231+
input: {
232+
required: {
233+
intInput: ['INT', { min: 0, max: 100, default: 50 }]
234+
}
235+
},
236+
output: ['INT'],
237+
output_is_list: [false],
238+
output_name: ['intOutput']
239+
}
240+
241+
const result = plainToClass(ComfyNodeDefImpl, plainObject)
242+
expect(result.deprecated).toBe(true)
197243
})
198244

199245
it('should handle multiple outputs including COMBO type', () => {

0 commit comments

Comments
 (0)