mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-01 22:09:55 +00:00
feat: support dev-only nodes (#8359)
## Summary Support `dev_only` property to node definitions that hides nodes from search and menus unless dev mode is enabled. Dev-only nodes display a "DEV" badge when visible. This functionality is primarily intended to support unit-testing nodes on Comfy Cloud, but also has other uses. ## Changes - **What**: Nodes flagged as dev_only in the node schema will only appear in search and menus if Dev Mode is on. ## Screenshots (if applicable) With Dev Mode off: <img width="2189" height="1003" alt="image" src="https://github.com/user-attachments/assets/a08e1fd7-dca9-4ce1-9964-5f4f3b7b95ac" /> With Dev Mode on: <img width="2201" height="1066" alt="image" src="https://github.com/user-attachments/assets/7fe6cd1f-f774-4f48-b604-a528e286b584" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8359-feat-support-dev-only-nodes-2f66d73d36508102839ee7cd66a26129) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -21,16 +21,17 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-badges">
|
||||
<Tag
|
||||
v-if="nodeDef.experimental"
|
||||
:value="$t('g.experimental')"
|
||||
severity="primary"
|
||||
/>
|
||||
<Tag
|
||||
v-if="nodeDef.deprecated"
|
||||
:value="$t('g.deprecated')"
|
||||
severity="danger"
|
||||
/>
|
||||
<Tag
|
||||
v-if="nodeDef.experimental"
|
||||
:value="$t('g.experimental')"
|
||||
severity="primary"
|
||||
/>
|
||||
<Tag v-if="nodeDef.dev_only" :value="$t('g.devOnly')" severity="info" />
|
||||
<Tag
|
||||
v-if="showNodeFrequency && nodeFrequency > 0"
|
||||
:value="formatNumberWithSuffix(nodeFrequency, { roundToInt: true })"
|
||||
|
||||
@@ -233,6 +233,14 @@ export class LGraphNode
|
||||
static description?: string
|
||||
static filter?: string
|
||||
static skip_list?: boolean
|
||||
static nodeData?: {
|
||||
dev_only?: boolean
|
||||
deprecated?: boolean
|
||||
experimental?: boolean
|
||||
output_node?: boolean
|
||||
api_node?: boolean
|
||||
name?: string
|
||||
}
|
||||
|
||||
static resizeHandleSize = 15
|
||||
static resizeEdgeSize = 5
|
||||
|
||||
@@ -121,6 +121,7 @@
|
||||
"customize": "Customize",
|
||||
"experimental": "BETA",
|
||||
"deprecated": "DEPR",
|
||||
"devOnly": "DEV",
|
||||
"loadWorkflow": "Load Workflow",
|
||||
"goToNode": "Go to Node",
|
||||
"setAsBackground": "Set as Background",
|
||||
|
||||
@@ -174,6 +174,7 @@ export const zComfyNodeDef = z.object({
|
||||
python_module: z.string(),
|
||||
deprecated: z.boolean().optional(),
|
||||
experimental: z.boolean().optional(),
|
||||
dev_only: z.boolean().optional(),
|
||||
api_node: z.boolean().optional()
|
||||
})
|
||||
|
||||
|
||||
@@ -257,6 +257,7 @@ export const zComfyNodeDef = z.object({
|
||||
python_module: z.string(),
|
||||
deprecated: z.boolean().optional(),
|
||||
experimental: z.boolean().optional(),
|
||||
dev_only: z.boolean().optional(),
|
||||
/**
|
||||
* Whether the node is an API node. Running API nodes requires login to
|
||||
* Comfy Org account.
|
||||
|
||||
@@ -261,7 +261,7 @@ export const useLitegraphService = () => {
|
||||
static comfyClass: string
|
||||
static override title: string
|
||||
static override category: string
|
||||
static nodeData: ComfyNodeDefV1 & ComfyNodeDefV2
|
||||
static override nodeData: ComfyNodeDefV1 & ComfyNodeDefV2
|
||||
|
||||
_initialMinSize = { width: 1, height: 1 }
|
||||
|
||||
@@ -394,7 +394,7 @@ export const useLitegraphService = () => {
|
||||
static comfyClass: string
|
||||
static override title: string
|
||||
static override category: string
|
||||
static nodeData: ComfyNodeDefV1 & ComfyNodeDefV2
|
||||
static override nodeData: ComfyNodeDefV1 & ComfyNodeDefV2
|
||||
|
||||
_initialMinSize = { width: 1, height: 1 }
|
||||
|
||||
@@ -496,6 +496,13 @@ export const useLitegraphService = () => {
|
||||
// because `registerNodeType` will overwrite the assignments.
|
||||
node.category = nodeDef.category
|
||||
node.title = nodeDef.display_name || nodeDef.name
|
||||
|
||||
// Set skip_list for dev-only nodes based on current DevMode setting
|
||||
// This ensures nodes registered after initial load respect the current setting
|
||||
if (nodeDef.dev_only) {
|
||||
const settingStore = useSettingStore()
|
||||
node.skip_list = !settingStore.get('Comfy.DevMode')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import axios from 'axios'
|
||||
import _ from 'es-toolkit/compat'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watchEffect } from 'vue'
|
||||
|
||||
import { isProxyWidget } from '@/core/graph/subgraph/proxyWidget'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { transformNodeDefV1ToV2 } from '@/schemas/nodeDef/migration'
|
||||
import type {
|
||||
@@ -17,6 +18,7 @@ import type {
|
||||
ComfyOutputTypesSpec as ComfyOutputSpecV1,
|
||||
PriceBadge
|
||||
} from '@/schemas/nodeDefSchema'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { NodeSearchService } from '@/services/nodeSearchService'
|
||||
import { useSubgraphStore } from '@/stores/subgraphStore'
|
||||
import { NodeSourceType, getNodeSource } from '@/types/nodeSource'
|
||||
@@ -41,6 +43,7 @@ export class ComfyNodeDefImpl
|
||||
readonly help: string
|
||||
readonly deprecated: boolean
|
||||
readonly experimental: boolean
|
||||
readonly dev_only: boolean
|
||||
readonly output_node: boolean
|
||||
readonly api_node: boolean
|
||||
/**
|
||||
@@ -133,6 +136,7 @@ export class ComfyNodeDefImpl
|
||||
this.deprecated = obj.deprecated ?? obj.category === ''
|
||||
this.experimental =
|
||||
obj.experimental ?? obj.category.startsWith('_for_testing')
|
||||
this.dev_only = obj.dev_only ?? false
|
||||
this.output_node = obj.output_node
|
||||
this.api_node = !!obj.api_node
|
||||
this.input = obj.input ?? {}
|
||||
@@ -174,6 +178,7 @@ export class ComfyNodeDefImpl
|
||||
get nodeLifeCycleBadgeText(): string {
|
||||
if (this.deprecated) return '[DEPR]'
|
||||
if (this.experimental) return '[BETA]'
|
||||
if (this.dev_only) return '[DEV]'
|
||||
return ''
|
||||
}
|
||||
}
|
||||
@@ -299,12 +304,27 @@ export interface NodeDefFilter {
|
||||
}
|
||||
|
||||
export const useNodeDefStore = defineStore('nodeDef', () => {
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const nodeDefsByName = ref<Record<string, ComfyNodeDefImpl>>({})
|
||||
const nodeDefsByDisplayName = ref<Record<string, ComfyNodeDefImpl>>({})
|
||||
const showDeprecated = ref(false)
|
||||
const showExperimental = ref(false)
|
||||
const showDevOnly = computed(() => settingStore.get('Comfy.DevMode'))
|
||||
const nodeDefFilters = ref<NodeDefFilter[]>([])
|
||||
|
||||
// Update skip_list on all registered node types when dev mode changes
|
||||
// This ensures LiteGraph's getNodeTypesCategories/getNodeTypesInCategory
|
||||
// correctly filter dev-only nodes from the right-click context menu
|
||||
watchEffect(() => {
|
||||
const devModeEnabled = showDevOnly.value
|
||||
for (const nodeType of Object.values(LiteGraph.registered_node_types)) {
|
||||
if (nodeType.nodeData?.dev_only) {
|
||||
nodeType.skip_list = !devModeEnabled
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const nodeDefs = computed(() => {
|
||||
const subgraphStore = useSubgraphStore()
|
||||
// Blueprints first for discoverability in the node library sidebar
|
||||
@@ -422,6 +442,14 @@ export const useNodeDefStore = defineStore('nodeDef', () => {
|
||||
predicate: (nodeDef) => showExperimental.value || !nodeDef.experimental
|
||||
})
|
||||
|
||||
// Dev-only nodes filter
|
||||
registerNodeDefFilter({
|
||||
id: 'core.dev_only',
|
||||
name: 'Hide Dev-Only Nodes',
|
||||
description: 'Hides nodes marked as dev-only unless dev mode is enabled',
|
||||
predicate: (nodeDef) => showDevOnly.value || !nodeDef.dev_only
|
||||
})
|
||||
|
||||
// Subgraph nodes filter
|
||||
// Filter out litegraph typed subgraphs, saved blueprints are added in separately
|
||||
registerNodeDefFilter({
|
||||
@@ -446,6 +474,7 @@ export const useNodeDefStore = defineStore('nodeDef', () => {
|
||||
nodeDefsByDisplayName,
|
||||
showDeprecated,
|
||||
showExperimental,
|
||||
showDevOnly,
|
||||
nodeDefFilters,
|
||||
|
||||
nodeDefs,
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('nodeFilterUtil', () => {
|
||||
): LGraphNode => {
|
||||
// Create a custom class with the nodeData static property
|
||||
class MockNode extends LGraphNode {
|
||||
static nodeData = isOutputNode ? { output_node: true } : {}
|
||||
static override nodeData = isOutputNode ? { output_node: true } : {}
|
||||
}
|
||||
|
||||
const node = new MockNode('')
|
||||
@@ -71,11 +71,11 @@ describe('nodeFilterUtil', () => {
|
||||
})
|
||||
|
||||
it('should handle nodes with undefined output_node', () => {
|
||||
class MockNodeWithOtherData extends LGraphNode {
|
||||
static nodeData = { someOtherProperty: true }
|
||||
class MockNodeWithEmptyData extends LGraphNode {
|
||||
static override nodeData = {}
|
||||
}
|
||||
|
||||
const node = new MockNodeWithOtherData('')
|
||||
const node = new MockNodeWithEmptyData('')
|
||||
node.id = 1
|
||||
|
||||
const result = filterOutputNodes([node])
|
||||
|
||||
Reference in New Issue
Block a user