Files
ComfyUI_frontend/src/constants/essentialsNodes.ts
Yourz 001916edf6 refactor: clean up essentials node organization logic (#10433)
## Summary

Refactor essentials tab node organization to eliminate duplicated logic
and restrict essentials to core nodes only.

## Changes

- **What**: 
- Extract `resolveEssentialsCategory` to centralize category resolution
(was duplicated between filter and pathExtractor).
- Add `isCoreNode` guard so third-party nodes never appear in
essentials.
- Replace `indexOf`-based sorting with precomputed rank maps
(`ESSENTIALS_CATEGORY_RANK`, `ESSENTIALS_NODE_RANK`).

<img width="589" height="769" alt="image"
src="https://github.com/user-attachments/assets/66f41f35-aef5-4e12-97d5-0f33baf0ac45"
/>


## Review Focus

- The `isCoreNode` guard in `resolveEssentialsCategory` — ensures only
core nodes can appear in essentials even if a custom node sets
`essentials_category`.
- Rank map precomputation vs previous `indexOf` — functionally
equivalent but O(1) lookup.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10433-refactor-clean-up-essentials-node-organization-logic-32d6d73d36508193a4d1f7f9c18fcef7)
by [Unito](https://www.unito.io)

Co-authored-by: Amp <amp@ampcode.com>
2026-03-24 16:22:09 +08:00

148 lines
4.2 KiB
TypeScript

/**
* Single source of truth for Essentials tab node categorization and ordering.
*
* Adding a new node to the Essentials tab? Add it here and nowhere else.
*
* Source: https://www.notion.so/comfy-org/2fe6d73d365080d0a951d14cdf540778
*/
export const ESSENTIALS_ICON_OVERRIDES: Record<string, string> = {
LoadImage: 'icon-s1.3-[lucide--image-up]',
LoadImageOutput: 'icon-s1.3-[lucide--image-up]',
SaveImage: 'icon-s1.3-[lucide--image-down]',
PrimitiveStringMultiline: 'icon-s1.3-[lucide--text]',
ImageCrop: 'icon-s1.3-[lucide--crop]',
VideoCrop: 'icon-s1.3-[lucide--crop]',
KlingLipSyncAudioToVideoNode: 'icon-s1.3-[lucide--mic-vocal]',
WebcamCapture: 'icon-s1.3-[lucide--camera]'
}
export const ESSENTIALS_CATEGORIES = [
'basics',
'text generation',
'image generation',
'video generation',
'image tools',
'video tools',
'audio',
'3D'
] as const
export type EssentialsCategory = (typeof ESSENTIALS_CATEGORIES)[number]
/**
* Ordered list of nodes per category.
* Array order = display order in the Essentials tab.
* Presence in a category = the node's essentials_category mock fallback.
*/
export const ESSENTIALS_NODES: Record<EssentialsCategory, readonly string[]> = {
basics: [
'LoadImage',
'LoadVideo',
'Load3D',
'SaveImage',
'SaveVideo',
'SaveGLB',
'PrimitiveStringMultiline',
'PreviewImage'
],
'text generation': ['OpenAIChatNode'],
'image generation': [
'LoraLoader',
'LoraLoaderModelOnly',
'ConditioningCombine'
],
'video generation': [
'SubgraphBlueprint.pose_to_video_ltx_2_0',
'SubgraphBlueprint.canny_to_video_ltx_2_0',
'KlingLipSyncAudioToVideoNode',
'KlingOmniProEditVideoNode'
],
'image tools': [
'ImageBatch',
'ImageCrop',
'ImageCropV2',
'ImageScale',
'ImageScaleBy',
'ImageRotate',
'ImageBlur',
'ImageBlend',
'ImageInvert',
'ImageCompare',
'Canny',
'RecraftRemoveBackgroundNode',
'RecraftVectorizeImageNode',
'LoadImageMask',
'GLSLShader'
],
'video tools': ['GetVideoComponents', 'CreateVideo', 'Video Slice'],
audio: [
'LoadAudio',
'SaveAudio',
'SaveAudioMP3',
'StabilityTextToAudio',
'EmptyLatentAudio'
],
'3D': ['TencentTextToModelNode', 'TencentImageToModelNode']
}
/**
* Flat map: node name → category (derived from ESSENTIALS_NODES).
* Used as mock/fallback when backend doesn't provide essentials_category.
*/
export const ESSENTIALS_CATEGORY_MAP: Record<string, EssentialsCategory> =
Object.fromEntries(
Object.entries(ESSENTIALS_NODES).flatMap(([category, nodes]) =>
nodes.map((node) => [node, category])
)
) as Record<string, EssentialsCategory>
/**
* Case-insensitive lookup: lowercase category → canonical category.
* Used to normalize backend categories (which may be title-cased) to the
* canonical form used in ESSENTIALS_CATEGORIES.
*/
export const ESSENTIALS_CATEGORY_CANONICAL: ReadonlyMap<
string,
EssentialsCategory
> = new Map(ESSENTIALS_CATEGORIES.map((c) => [c.toLowerCase(), c]))
/**
* Precomputed rank map: category → display order index.
* Used for sorting essentials folders in their canonical order.
*/
export const ESSENTIALS_CATEGORY_RANK: ReadonlyMap<string, number> = new Map(
ESSENTIALS_CATEGORIES.map((c, i) => [c, i])
)
/**
* Precomputed rank maps: category → (node name → display order index).
* Used for sorting nodes within each essentials folder.
*/
export const ESSENTIALS_NODE_RANK: Partial<
Record<EssentialsCategory, ReadonlyMap<string, number>>
> = Object.fromEntries(
Object.entries(ESSENTIALS_NODES).map(([category, nodes]) => [
category,
new Map(nodes.map((name, i) => [name, i]))
])
)
/**
* "Novel" toolkit nodes for telemetry — basics excluded.
* Derived from ESSENTIALS_NODES minus the 'basics' category.
*/
export const TOOLKIT_NOVEL_NODE_NAMES: ReadonlySet<string> = new Set(
Object.entries(ESSENTIALS_NODES)
.filter(([cat]) => cat !== 'basics')
.flatMap(([, nodes]) => nodes)
.filter((n) => !n.startsWith('SubgraphBlueprint.'))
)
/**
* python_module values that identify toolkit blueprint nodes.
*/
export const TOOLKIT_BLUEPRINT_MODULES: ReadonlySet<string> = new Set([
'comfy_essentials'
])