Support node deprecated/experimental flag (#563)

* Add deprecated field

* Hide deprecated nodes

* Add experimental node show/hide

* Add setting tooltips

* nit

* nit

* nit
This commit is contained in:
Chenlei Hu
2024-08-20 17:00:47 -04:00
committed by GitHub
parent 269e468425
commit 9dd6da3dc2
7 changed files with 120 additions and 9 deletions

View File

@@ -16,7 +16,7 @@ import SideToolbar from '@/components/sidebar/SideToolbar.vue'
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
import NodeTooltip from '@/components/graph/NodeTooltip.vue'
import { ref, computed, onUnmounted, watch, onMounted } from 'vue'
import { ref, computed, onUnmounted, watch, onMounted, watchEffect } from 'vue'
import { app as comfyApp } from '@/scripts/app'
import { useSettingStore } from '@/stores/settingStore'
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
@@ -36,6 +36,7 @@ import {
const emit = defineEmits(['ready'])
const canvasRef = ref<HTMLCanvasElement | null>(null)
const settingStore = useSettingStore()
const nodeDefStore = useNodeDefStore()
const workspaceStore = useWorkspaceStore()
const betaMenuEnabled = computed(
@@ -63,6 +64,16 @@ watch(
{ immediate: true }
)
watchEffect(() => {
nodeDefStore.showDeprecated = settingStore.get('Comfy.Node.ShowDeprecated')
})
watchEffect(() => {
nodeDefStore.showExperimental = settingStore.get(
'Comfy.Node.ShowExperimental'
)
})
let dropTargetCleanup = () => {}
onMounted(async () => {
@@ -98,7 +109,7 @@ onMounted(async () => {
const comfyNodeName = event.source.element.getAttribute(
'data-comfy-node-name'
)
const nodeDef = useNodeDefStore().nodeDefsByName[comfyNodeName]
const nodeDef = nodeDefStore.nodeDefsByName[comfyNodeName]
comfyApp.addNodeOnGraph(nodeDef, { pos })
}
})

View File

@@ -112,6 +112,7 @@ const settingStore = useSettingStore()
const sidebarLocation = computed<'left' | 'right'>(() =>
settingStore.get('Comfy.Sidebar.Location')
)
const nodePreviewStyle = ref<Record<string, string>>({
position: 'absolute',
top: '0px',

View File

@@ -1,7 +1,7 @@
import { NodeSearchService } from '@/services/nodeSearchService'
import { ComfyNodeDef } from '@/types/apiTypes'
import { defineStore } from 'pinia'
import { Type, Transform, plainToClass } from 'class-transformer'
import { Type, Transform, plainToClass, Expose } from 'class-transformer'
import { ComfyWidgetConstructor } from '@/scripts/widgets'
import { TreeNode } from 'primevue/treenode'
import { buildTree } from '@/utils/treeUtil'
@@ -166,6 +166,23 @@ export class ComfyNodeDefImpl {
python_module: string
description: string
@Transform(({ value, obj }) => value ?? obj.category === '', {
toClassOnly: true
})
@Type(() => Boolean)
@Expose()
deprecated: boolean
@Transform(
({ value, obj }) => value ?? obj.category.startsWith('_for_testing'),
{
toClassOnly: true
}
)
@Type(() => Boolean)
@Expose()
experimental: boolean
@Type(() => ComfyInputsSpec)
input: ComfyInputsSpec
@@ -229,22 +246,34 @@ export const SYSTEM_NODE_DEFS: Record<string, ComfyNodeDef> = {
interface State {
nodeDefsByName: Record<string, ComfyNodeDefImpl>
widgets: Record<string, ComfyWidgetConstructor>
showDeprecated: boolean
showExperimental: boolean
}
export const useNodeDefStore = defineStore('nodeDef', {
state: (): State => ({
nodeDefsByName: {},
widgets: {}
widgets: {},
showDeprecated: false,
showExperimental: false
}),
getters: {
nodeDefs(state) {
return Object.values(state.nodeDefsByName)
},
nodeSearchService(state) {
return new NodeSearchService(Object.values(state.nodeDefsByName))
// Node defs that are not deprecated
visibleNodeDefs(state) {
return this.nodeDefs.filter(
(nodeDef: ComfyNodeDefImpl) =>
(state.showDeprecated || !nodeDef.deprecated) &&
(state.showExperimental || !nodeDef.experimental)
)
},
nodeSearchService() {
return new NodeSearchService(this.visibleNodeDefs)
},
nodeTree(): TreeNode {
return buildTree(this.nodeDefs, (nodeDef: ComfyNodeDefImpl) => [
return buildTree(this.visibleNodeDefs, (nodeDef: ComfyNodeDefImpl) => [
...nodeDef.category.split('/'),
nodeDef.display_name
])

View File

@@ -147,6 +147,24 @@ export const useSettingStore = defineStore('setting', {
type: 'boolean',
defaultValue: true
})
app.ui.settings.addSetting({
id: 'Comfy.Node.ShowDeprecated',
name: 'Show deprecated nodes in search',
tooltip:
'Deprecated nodes are hidden by default in the UI, but remain functional in existing workflows that use them.',
type: 'boolean',
defaultValue: false
})
app.ui.settings.addSetting({
id: 'Comfy.Node.ShowExperimental',
name: 'Show experimental nodes in search',
tooltip:
'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',
type: 'boolean',
defaultValue: true
})
},
set<K extends keyof Settings>(key: K, value: Settings[K]) {

View File

@@ -335,7 +335,9 @@ const zComfyNodeDef = z.object({
description: z.string(),
category: z.string(),
output_node: z.boolean(),
python_module: z.string()
python_module: z.string(),
deprecated: z.boolean().optional(),
experimental: z.boolean().optional()
})
// `/object_info`
@@ -419,6 +421,8 @@ const zSettings = z.record(z.any()).and(
'Comfy.NodeSearchBoxImpl': z.enum(['default', 'simple']),
'Comfy.NodeSearchBoxImpl.ShowCategory': z.boolean(),
'Comfy.NodeSuggestions.number': z.number(),
'Comfy.Node.ShowDeprecated': z.boolean(),
'Comfy.Node.ShowExperimental': z.boolean(),
'Comfy.PreviewFormat': z.string(),
'Comfy.PromptFilename': z.boolean(),
'Comfy.Sidebar.Location': z.enum(['left', 'right']),

View File

@@ -16,7 +16,9 @@ const EXAMPLE_NODE_DEF: ComfyNodeDef = {
description: '',
python_module: 'nodes',
category: 'loaders',
output_node: false
output_node: false,
experimental: false,
deprecated: false
}
describe('validateNodeDef', () => {

View File

@@ -194,6 +194,52 @@ describe('ComfyNodeDefImpl', () => {
is_list: false
}
])
expect(result.deprecated).toBe(false)
})
it('should transform a deprecated basic node definition', () => {
const plainObject = {
name: 'TestNode',
display_name: 'Test Node',
category: 'Testing',
python_module: 'test_module',
description: 'A test node',
input: {
required: {
intInput: ['INT', { min: 0, max: 100, default: 50 }]
}
},
output: ['INT'],
output_is_list: [false],
output_name: ['intOutput'],
deprecated: true
}
const result = plainToClass(ComfyNodeDefImpl, plainObject)
expect(result.deprecated).toBe(true)
})
// Legacy way of marking a node as deprecated
it('should mark deprecated with empty category', () => {
const plainObject = {
name: 'TestNode',
display_name: 'Test Node',
// Empty category should be treated as deprecated
category: '',
python_module: 'test_module',
description: 'A test node',
input: {
required: {
intInput: ['INT', { min: 0, max: 100, default: 50 }]
}
},
output: ['INT'],
output_is_list: [false],
output_name: ['intOutput']
}
const result = plainToClass(ComfyNodeDefImpl, plainObject)
expect(result.deprecated).toBe(true)
})
it('should handle multiple outputs including COMBO type', () => {