mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-22 05:19:03 +00:00
Compare commits
2 Commits
v1.45.12
...
glary/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a122079bfe | ||
|
|
918cd8b57b |
84
browser_tests/tests/nodeSearchBoxV2Essentials.spec.ts
Normal file
84
browser_tests/tests/nodeSearchBoxV2Essentials.spec.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { createMockNodeDefinitions } from '@e2e/fixtures/data/nodeDefinitions'
|
||||
import {
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
const CORE_ESSENTIAL = 'FE568CoreEssential'
|
||||
const CUSTOM_PACK_ESSENTIAL = 'FE568CustomPackEssential'
|
||||
|
||||
const fixtureDefs = createMockNodeDefinitions({
|
||||
[CORE_ESSENTIAL]: {
|
||||
input: { required: {}, optional: {} },
|
||||
output: ['IMAGE'],
|
||||
output_name: ['image'],
|
||||
output_is_list: [false],
|
||||
output_node: false,
|
||||
name: CORE_ESSENTIAL,
|
||||
display_name: 'FE568 Core Essential',
|
||||
description: 'Core essential — FE-568 regression fixture',
|
||||
category: 'image/upscaling',
|
||||
python_module: 'comfy_extras.nodes_images',
|
||||
essentials_category: 'image tools'
|
||||
},
|
||||
[CUSTOM_PACK_ESSENTIAL]: {
|
||||
input: { required: {}, optional: {} },
|
||||
output: ['IMAGE'],
|
||||
output_name: ['image'],
|
||||
output_is_list: [false],
|
||||
output_node: false,
|
||||
name: CUSTOM_PACK_ESSENTIAL,
|
||||
display_name: 'FE568 Custom Pack Essential',
|
||||
description: 'Custom-pack essential — FE-568 regression fixture',
|
||||
category: 'KJNodes/masking',
|
||||
python_module: 'custom_nodes.comfyui-kjnodes',
|
||||
essentials_category: 'image tools'
|
||||
}
|
||||
})
|
||||
|
||||
test.describe(
|
||||
'Node search box V2 — Essentials/Extensions classification (FE-568)',
|
||||
{ tag: '@node' },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
test.setTimeout(60_000)
|
||||
await comfyPage.page.route('**/api/object_info', (route) =>
|
||||
route.fulfill({ json: fixtureDefs })
|
||||
)
|
||||
await comfyPage.workflow.reloadAndWaitForApp()
|
||||
await comfyPage.searchBoxV2.setup()
|
||||
})
|
||||
|
||||
test('Essentials chip excludes custom-pack nodes that declare essentials_category', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const { searchBoxV2 } = comfyPage
|
||||
await searchBoxV2.open()
|
||||
await searchBoxV2.rootCategoryButton('essentials').click()
|
||||
await searchBoxV2.input.fill('FE568')
|
||||
|
||||
await expect(
|
||||
searchBoxV2.results.filter({ hasText: 'FE568 Core Essential' })
|
||||
).toHaveCount(1)
|
||||
await expect(
|
||||
searchBoxV2.results.filter({ hasText: 'FE568 Custom Pack Essential' })
|
||||
).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('Extensions chip includes custom-pack nodes that declare essentials_category', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const { searchBoxV2 } = comfyPage
|
||||
await searchBoxV2.open()
|
||||
await searchBoxV2.rootCategoryButton('custom').click()
|
||||
await searchBoxV2.input.fill('FE568')
|
||||
|
||||
await expect(
|
||||
searchBoxV2.results.filter({ hasText: 'FE568 Custom Pack Essential' })
|
||||
).toHaveCount(1)
|
||||
await expect(
|
||||
searchBoxV2.results.filter({ hasText: 'FE568 Core Essential' })
|
||||
).toHaveCount(0)
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -3,8 +3,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
NodeSourceType,
|
||||
getNodeSource,
|
||||
isCustomNode,
|
||||
isEssentialNode
|
||||
isCustomNode
|
||||
} from '../classifiers/nodeSource'
|
||||
import type { NodeSource } from '../classifiers/nodeSource'
|
||||
|
||||
@@ -79,27 +78,14 @@ describe('getNodeSource', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('essentials nodes', () => {
|
||||
it('should identify essentials nodes when essentials_category is set', () => {
|
||||
const result = getNodeSource('nodes.some_module', 'Image')
|
||||
expect(result.type).toBe(NodeSourceType.Essentials)
|
||||
expect(result.className).toBe('comfy-essentials')
|
||||
})
|
||||
it('should return UNKNOWN_NODE_SOURCE for custom_nodes with no pack segment', () => {
|
||||
expect(getNodeSource('custom_nodes').type).toBe(NodeSourceType.Unknown)
|
||||
})
|
||||
|
||||
it('should identify essentials nodes from custom_nodes module', () => {
|
||||
const result = getNodeSource(
|
||||
'custom_nodes.ComfyUI-Example@1.0.0',
|
||||
'Video'
|
||||
)
|
||||
expect(result.type).toBe(NodeSourceType.Essentials)
|
||||
expect(result.className).toBe('comfy-essentials')
|
||||
expect(result.displayText).toBe('Example')
|
||||
})
|
||||
|
||||
it('should not identify nodes without essentials_category as essentials', () => {
|
||||
const result = getNodeSource('nodes.some_module', undefined)
|
||||
expect(result.type).toBe(NodeSourceType.Core)
|
||||
})
|
||||
it('should strip ComfyUI- and -ComfyUI prefixes/suffixes from custom pack names', () => {
|
||||
expect(getNodeSource('custom_nodes.ComfyUI-foo').displayText).toBe('foo')
|
||||
expect(getNodeSource('custom_nodes.bar-ComfyUI').displayText).toBe('bar')
|
||||
expect(getNodeSource('custom_nodes.Comfy_baz').displayText).toBe('baz')
|
||||
})
|
||||
|
||||
describe('blueprint nodes', () => {
|
||||
@@ -126,21 +112,6 @@ function makeNode(type: NodeSourceType): { nodeSource: NodeSource } {
|
||||
}
|
||||
}
|
||||
|
||||
describe('isEssentialNode', () => {
|
||||
it('returns true for Essentials nodes', () => {
|
||||
expect(isEssentialNode(makeNode(NodeSourceType.Essentials))).toBe(true)
|
||||
})
|
||||
|
||||
it.for([
|
||||
NodeSourceType.Core,
|
||||
NodeSourceType.CustomNodes,
|
||||
NodeSourceType.Blueprint,
|
||||
NodeSourceType.Unknown
|
||||
])('returns false for %s nodes', (type) => {
|
||||
expect(isEssentialNode(makeNode(type))).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isCustomNode', () => {
|
||||
it('returns true for CustomNodes', () => {
|
||||
expect(isCustomNode(makeNode(NodeSourceType.CustomNodes))).toBe(true)
|
||||
@@ -148,7 +119,6 @@ describe('isCustomNode', () => {
|
||||
|
||||
it.for([
|
||||
NodeSourceType.Core,
|
||||
NodeSourceType.Essentials,
|
||||
NodeSourceType.Unknown,
|
||||
NodeSourceType.Blueprint
|
||||
])('returns false for %s nodes', (type) => {
|
||||
|
||||
@@ -4,7 +4,6 @@ export enum NodeSourceType {
|
||||
Core = 'core',
|
||||
CustomNodes = 'custom_nodes',
|
||||
Blueprint = 'blueprint',
|
||||
Essentials = 'essentials',
|
||||
Unknown = 'unknown'
|
||||
}
|
||||
export const CORE_NODE_MODULES = ['nodes', 'comfy_extras', 'comfy_api_nodes']
|
||||
@@ -29,24 +28,12 @@ function shortenNodeName(name: string) {
|
||||
.replace(/(-ComfyUI|_ComfyUI|-Comfy|_Comfy)$/, '')
|
||||
}
|
||||
|
||||
export function getNodeSource(
|
||||
python_module?: string,
|
||||
essentials_category?: string
|
||||
): NodeSource {
|
||||
export function getNodeSource(python_module?: string): NodeSource {
|
||||
if (!python_module) {
|
||||
return UNKNOWN_NODE_SOURCE
|
||||
}
|
||||
const modules = python_module.split('.')
|
||||
if (essentials_category) {
|
||||
const moduleName = modules[1] ?? modules[0] ?? 'essentials'
|
||||
const displayName = shortenNodeName(moduleName.split('@')[0])
|
||||
return {
|
||||
type: NodeSourceType.Essentials,
|
||||
className: 'comfy-essentials',
|
||||
displayText: displayName,
|
||||
badgeText: displayName
|
||||
}
|
||||
} else if (CORE_NODE_MODULES.includes(modules[0])) {
|
||||
if (CORE_NODE_MODULES.includes(modules[0])) {
|
||||
return {
|
||||
type: NodeSourceType.Core,
|
||||
className: 'comfy-core',
|
||||
@@ -82,10 +69,6 @@ interface NodeDefLike {
|
||||
nodeSource: NodeSource
|
||||
}
|
||||
|
||||
export function isEssentialNode(node: NodeDefLike): boolean {
|
||||
return node.nodeSource.type === NodeSourceType.Essentials
|
||||
}
|
||||
|
||||
export function isCustomNode(node: NodeDefLike): boolean {
|
||||
return node.nodeSource.type === NodeSourceType.CustomNodes
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export function groupNodesByPack(
|
||||
const byPackId = new Map<string, NodePack>()
|
||||
|
||||
for (const [className, def] of Object.entries(defs)) {
|
||||
const source = getNodeSource(def.python_module, def.essentials_category)
|
||||
const source = getNodeSource(def.python_module)
|
||||
if (source.type !== NodeSourceType.CustomNodes) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -230,6 +230,60 @@ describe('NodeSearchContent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should not show custom-pack nodes under Essentials even when essentials_category is set', async () => {
|
||||
useNodeDefStore().updateNodeDefs([
|
||||
createMockNodeDef({
|
||||
name: 'CoreEssential',
|
||||
display_name: 'Core Essential',
|
||||
essentials_category: 'image tools',
|
||||
python_module: 'comfy_extras.nodes_images'
|
||||
}),
|
||||
createMockNodeDef({
|
||||
name: 'CustomPackEssential',
|
||||
display_name: 'Custom Pack Essential',
|
||||
essentials_category: 'image tools',
|
||||
python_module: 'custom_nodes.comfyui-kjnodes',
|
||||
category: 'KJNodes/masking'
|
||||
})
|
||||
])
|
||||
|
||||
const { user } = renderComponent()
|
||||
await clickFilterBarButton(user, 'Essentials')
|
||||
|
||||
await waitFor(() => {
|
||||
const items = screen.getAllByTestId('node-item')
|
||||
expect(items).toHaveLength(1)
|
||||
expect(items[0]).toHaveTextContent('Core Essential')
|
||||
})
|
||||
})
|
||||
|
||||
it('should expose custom-pack nodes under Extensions even when essentials_category is set', async () => {
|
||||
useNodeDefStore().updateNodeDefs([
|
||||
createMockNodeDef({
|
||||
name: 'CustomPackEssential',
|
||||
display_name: 'Custom Pack Essential',
|
||||
essentials_category: 'image tools',
|
||||
python_module: 'custom_nodes.comfyui-kjnodes',
|
||||
category: 'KJNodes/masking'
|
||||
}),
|
||||
createMockNodeDef({
|
||||
name: 'CoreEssential',
|
||||
display_name: 'Core Essential',
|
||||
essentials_category: 'image tools',
|
||||
python_module: 'comfy_extras.nodes_images'
|
||||
})
|
||||
])
|
||||
|
||||
const { user } = renderComponent()
|
||||
await clickFilterBarButton(user, 'Extensions')
|
||||
|
||||
await waitFor(() => {
|
||||
const items = screen.getAllByTestId('node-item')
|
||||
expect(items).toHaveLength(1)
|
||||
expect(items[0]).toHaveTextContent('Custom Pack Essential')
|
||||
})
|
||||
})
|
||||
|
||||
it('should show only API nodes when Partner Nodes filter is active', async () => {
|
||||
useNodeDefStore().updateNodeDefs([
|
||||
createMockNodeDef({
|
||||
|
||||
@@ -125,15 +125,18 @@ import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
||||
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
||||
import { useNodeDefStore, useNodeFrequencyStore } from '@/stores/nodeDefStore'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { resolveEssentialsCategory } from '@/services/nodeOrganizationService'
|
||||
import {
|
||||
BLUEPRINT_CATEGORY,
|
||||
isCustomNode,
|
||||
isEssentialNode,
|
||||
NodeSourceType
|
||||
} from '@/types/nodeSource'
|
||||
import type { FuseFilter, FuseFilterWithValue } from '@/utils/fuseUtil'
|
||||
import { cn } from '@comfyorg/tailwind-utils'
|
||||
|
||||
const isEssentialNode = (n: ComfyNodeDefImpl) =>
|
||||
resolveEssentialsCategory(n) !== undefined
|
||||
|
||||
const sourceCategoryFilters: Record<string, (n: ComfyNodeDefImpl) => boolean> =
|
||||
{
|
||||
[RootCategory.Essentials]: isEssentialNode,
|
||||
|
||||
@@ -22,7 +22,7 @@ import { sortedTree, unwrapTreeRoot } from '@/utils/treeUtil'
|
||||
const DEFAULT_ICON = 'pi pi-sort'
|
||||
const UNKNOWN_RANK = Number.MAX_SAFE_INTEGER
|
||||
|
||||
function resolveEssentialsCategory(
|
||||
export function resolveEssentialsCategory(
|
||||
nodeDef: ComfyNodeDefImpl
|
||||
): EssentialsCategory | undefined {
|
||||
if (!nodeDef.isCoreNode) return undefined
|
||||
@@ -317,10 +317,7 @@ class NodeOrganizationService {
|
||||
else myBlueprints.push(node)
|
||||
} else if (node.api_node || node.category?.startsWith('api node')) {
|
||||
partnerNodes.push(node)
|
||||
} else if (
|
||||
node.nodeSource.type === NodeSourceType.Core ||
|
||||
node.nodeSource.type === NodeSourceType.Essentials
|
||||
) {
|
||||
} else if (node.nodeSource.type === NodeSourceType.Core) {
|
||||
comfyNodes.push(node)
|
||||
} else {
|
||||
extensions.push(node)
|
||||
|
||||
@@ -179,8 +179,7 @@ export class ComfyNodeDefImpl
|
||||
this.outputs = defV2.outputs
|
||||
this.hidden = defV2.hidden
|
||||
|
||||
// Initialize node source
|
||||
this.nodeSource = getNodeSource(obj.python_module, this.essentials_category)
|
||||
this.nodeSource = getNodeSource(obj.python_module)
|
||||
this.inputTypes = _.uniq(
|
||||
Object.values(this.inputs).flatMap(resolveInputType)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user