mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
## 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>
148 lines
4.2 KiB
TypeScript
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'
|
|
])
|