feat: add essentials_category (#12357)

* feat: add essentials_category field to node schema

Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3

* feat: add ESSENTIALS_CATEGORY to core nodes

Marked nodes:
- Basic: LoadImage, SaveImage, LoadVideo, SaveVideo, Load3D, CLIPTextEncode
- Image Tools: ImageScale, ImageInvert, ImageBatch, ImageCrop, ImageRotate, ImageBlur
- Image Tools/Preprocessing: Canny
- Image Generation: LoraLoader
- Audio: LoadAudio, SaveAudio

Amp-Thread-ID: https://ampcode.com/threads/T-019c2b25-cd90-7218-9071-03cb46b351b3

* Add ESSENTIALS_CATEGORY to more nodes

- SaveGLB (Basic)
- GetVideoComponents (Video Tools)
- TencentTextToModelNode, TencentImageToModelNode (3D)
- RecraftRemoveBackgroundNode (Image Tools)
- KlingLipSyncAudioToVideoNode (Video Generation)
- OpenAIChatNode (Text Generation)
- StabilityTextToAudio (Audio)

Amp-Thread-ID: https://ampcode.com/threads/T-019c2b69-81c1-71c3-8096-450a39e20910

* fix: correct essentials category for Canny node

Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>

* refactor: replace essentials_category string literals with constants

Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>

* refactor: revert constants, use string literals for essentials_category

Amp-Thread-ID: https://ampcode.com/threads/T-019c7303-ab53-7341-be76-a5da1f7a657e
Co-authored-by: Amp <amp@ampcode.com>

* fix: update basics

---------

Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Jedrzej Kosinski <kosinkadink1@gmail.com>
This commit is contained in:
Yourz 2026-02-20 11:00:26 +08:00 committed by GitHub
parent 2687652530
commit 5632b2df9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 34 additions and 0 deletions

View File

@ -1339,6 +1339,7 @@ class NodeInfoV1:
api_node: bool=None api_node: bool=None
price_badge: dict | None = None price_badge: dict | None = None
search_aliases: list[str]=None search_aliases: list[str]=None
essentials_category: str=None
@dataclass @dataclass
@ -1460,6 +1461,8 @@ class Schema:
"""Flags a node as expandable, allowing NodeOutput to include 'expand' property.""" """Flags a node as expandable, allowing NodeOutput to include 'expand' property."""
accept_all_inputs: bool=False accept_all_inputs: bool=False
"""When True, all inputs from the prompt will be passed to the node as kwargs, even if not defined in the schema.""" """When True, all inputs from the prompt will be passed to the node as kwargs, even if not defined in the schema."""
essentials_category: str | None = None
"""Optional category for the Essentials tab. Path-based like category field (e.g., 'Basic', 'Image Tools/Editing')."""
def validate(self): def validate(self):
'''Validate the schema: '''Validate the schema:
@ -1566,6 +1569,7 @@ class Schema:
python_module=getattr(cls, "RELATIVE_PYTHON_MODULE", "nodes"), python_module=getattr(cls, "RELATIVE_PYTHON_MODULE", "nodes"),
price_badge=self.price_badge.as_dict(self.inputs) if self.price_badge is not None else None, price_badge=self.price_badge.as_dict(self.inputs) if self.price_badge is not None else None,
search_aliases=self.search_aliases if self.search_aliases else None, search_aliases=self.search_aliases if self.search_aliases else None,
essentials_category=self.essentials_category,
) )
return info return info

View File

@ -54,6 +54,7 @@ class TencentTextToModelNode(IO.ComfyNode):
node_id="TencentTextToModelNode", node_id="TencentTextToModelNode",
display_name="Hunyuan3D: Text to Model", display_name="Hunyuan3D: Text to Model",
category="api node/3d/Tencent", category="api node/3d/Tencent",
essentials_category="3D",
inputs=[ inputs=[
IO.Combo.Input( IO.Combo.Input(
"model", "model",
@ -168,6 +169,7 @@ class TencentImageToModelNode(IO.ComfyNode):
node_id="TencentImageToModelNode", node_id="TencentImageToModelNode",
display_name="Hunyuan3D: Image(s) to Model", display_name="Hunyuan3D: Image(s) to Model",
category="api node/3d/Tencent", category="api node/3d/Tencent",
essentials_category="3D",
inputs=[ inputs=[
IO.Combo.Input( IO.Combo.Input(
"model", "model",

View File

@ -2262,6 +2262,7 @@ class KlingLipSyncAudioToVideoNode(IO.ComfyNode):
node_id="KlingLipSyncAudioToVideoNode", node_id="KlingLipSyncAudioToVideoNode",
display_name="Kling Lip Sync Video with Audio", display_name="Kling Lip Sync Video with Audio",
category="api node/video/Kling", category="api node/video/Kling",
essentials_category="Video Generation",
description="Kling Lip Sync Audio to Video Node. Syncs mouth movements in a video file to the audio content of an audio file. When using, ensure that the audio contains clearly distinguishable vocals and that the video contains a distinct face. The audio file should not be larger than 5MB. The video file should not be larger than 100MB, should have height/width between 720px and 1920px, and should be between 2s and 10s in length.", description="Kling Lip Sync Audio to Video Node. Syncs mouth movements in a video file to the audio content of an audio file. When using, ensure that the audio contains clearly distinguishable vocals and that the video contains a distinct face. The audio file should not be larger than 5MB. The video file should not be larger than 100MB, should have height/width between 720px and 1920px, and should be between 2s and 10s in length.",
inputs=[ inputs=[
IO.Video.Input("video"), IO.Video.Input("video"),

View File

@ -575,6 +575,7 @@ class OpenAIChatNode(IO.ComfyNode):
node_id="OpenAIChatNode", node_id="OpenAIChatNode",
display_name="OpenAI ChatGPT", display_name="OpenAI ChatGPT",
category="api node/text/OpenAI", category="api node/text/OpenAI",
essentials_category="Text Generation",
description="Generate text responses from an OpenAI model.", description="Generate text responses from an OpenAI model.",
inputs=[ inputs=[
IO.String.Input( IO.String.Input(

View File

@ -963,6 +963,7 @@ class RecraftRemoveBackgroundNode(IO.ComfyNode):
node_id="RecraftRemoveBackgroundNode", node_id="RecraftRemoveBackgroundNode",
display_name="Recraft Remove Background", display_name="Recraft Remove Background",
category="api node/image/Recraft", category="api node/image/Recraft",
essentials_category="Image Tools",
description="Remove background from image, and return processed image and mask.", description="Remove background from image, and return processed image and mask.",
inputs=[ inputs=[
IO.Image.Input("image"), IO.Image.Input("image"),

View File

@ -624,6 +624,7 @@ class StabilityTextToAudio(IO.ComfyNode):
node_id="StabilityTextToAudio", node_id="StabilityTextToAudio",
display_name="Stability AI Text To Audio", display_name="Stability AI Text To Audio",
category="api node/audio/Stability AI", category="api node/audio/Stability AI",
essentials_category="Audio",
description=cleandoc(cls.__doc__ or ""), description=cleandoc(cls.__doc__ or ""),
inputs=[ inputs=[
IO.Combo.Input( IO.Combo.Input(

View File

@ -159,6 +159,7 @@ class SaveAudio(IO.ComfyNode):
search_aliases=["export flac"], search_aliases=["export flac"],
display_name="Save Audio (FLAC)", display_name="Save Audio (FLAC)",
category="audio", category="audio",
essentials_category="Audio",
inputs=[ inputs=[
IO.Audio.Input("audio"), IO.Audio.Input("audio"),
IO.String.Input("filename_prefix", default="audio/ComfyUI"), IO.String.Input("filename_prefix", default="audio/ComfyUI"),
@ -300,6 +301,7 @@ class LoadAudio(IO.ComfyNode):
search_aliases=["import audio", "open audio", "audio file"], search_aliases=["import audio", "open audio", "audio file"],
display_name="Load Audio", display_name="Load Audio",
category="audio", category="audio",
essentials_category="Audio",
inputs=[ inputs=[
IO.Combo.Input("audio", upload=IO.UploadType.audio, options=sorted(files)), IO.Combo.Input("audio", upload=IO.UploadType.audio, options=sorted(files)),
], ],

View File

@ -12,6 +12,7 @@ class Canny(io.ComfyNode):
node_id="Canny", node_id="Canny",
search_aliases=["edge detection", "outline", "contour detection", "line art"], search_aliases=["edge detection", "outline", "contour detection", "line art"],
category="image/preprocessors", category="image/preprocessors",
essentials_category="Image Tools",
inputs=[ inputs=[
io.Image.Input("image"), io.Image.Input("image"),
io.Float.Input("low_threshold", default=0.4, min=0.01, max=0.99, step=0.01), io.Float.Input("low_threshold", default=0.4, min=0.01, max=0.99, step=0.01),

View File

@ -621,6 +621,7 @@ class SaveGLB(IO.ComfyNode):
display_name="Save 3D Model", display_name="Save 3D Model",
search_aliases=["export 3d model", "save mesh"], search_aliases=["export 3d model", "save mesh"],
category="3d", category="3d",
essentials_category="Basics",
is_output_node=True, is_output_node=True,
inputs=[ inputs=[
IO.MultiType.Input( IO.MultiType.Input(

View File

@ -26,6 +26,7 @@ class ImageCrop(IO.ComfyNode):
display_name="Image Crop (Deprecated)", display_name="Image Crop (Deprecated)",
category="image/transform", category="image/transform",
is_deprecated=True, is_deprecated=True,
essentials_category="Image Tools",
inputs=[ inputs=[
IO.Image.Input("image"), IO.Image.Input("image"),
IO.Int.Input("width", default=512, min=1, max=nodes.MAX_RESOLUTION, step=1), IO.Int.Input("width", default=512, min=1, max=nodes.MAX_RESOLUTION, step=1),
@ -589,6 +590,7 @@ class ImageRotate(IO.ComfyNode):
node_id="ImageRotate", node_id="ImageRotate",
search_aliases=["turn", "flip orientation"], search_aliases=["turn", "flip orientation"],
category="image/transform", category="image/transform",
essentials_category="Image Tools",
inputs=[ inputs=[
IO.Image.Input("image"), IO.Image.Input("image"),
IO.Combo.Input("rotation", options=["none", "90 degrees", "180 degrees", "270 degrees"]), IO.Combo.Input("rotation", options=["none", "90 degrees", "180 degrees", "270 degrees"]),

View File

@ -31,6 +31,7 @@ class Load3D(IO.ComfyNode):
node_id="Load3D", node_id="Load3D",
display_name="Load 3D & Animation", display_name="Load 3D & Animation",
category="3d", category="3d",
essentials_category="Basics",
is_experimental=True, is_experimental=True,
inputs=[ inputs=[
IO.Combo.Input("model_file", options=sorted(files), upload=IO.UploadType.model), IO.Combo.Input("model_file", options=sorted(files), upload=IO.UploadType.model),

View File

@ -77,6 +77,7 @@ class Blur(io.ComfyNode):
return io.Schema( return io.Schema(
node_id="ImageBlur", node_id="ImageBlur",
category="image/postprocessing", category="image/postprocessing",
essentials_category="Image Tools",
inputs=[ inputs=[
io.Image.Input("image"), io.Image.Input("image"),
io.Int.Input("blur_radius", default=1, min=1, max=31, step=1), io.Int.Input("blur_radius", default=1, min=1, max=31, step=1),

View File

@ -73,6 +73,7 @@ class SaveVideo(io.ComfyNode):
search_aliases=["export video"], search_aliases=["export video"],
display_name="Save Video", display_name="Save Video",
category="image/video", category="image/video",
essentials_category="Basics",
description="Saves the input images to your ComfyUI output directory.", description="Saves the input images to your ComfyUI output directory.",
inputs=[ inputs=[
io.Video.Input("video", tooltip="The video to save."), io.Video.Input("video", tooltip="The video to save."),
@ -146,6 +147,7 @@ class GetVideoComponents(io.ComfyNode):
search_aliases=["extract frames", "split video", "video to images", "demux"], search_aliases=["extract frames", "split video", "video to images", "demux"],
display_name="Get Video Components", display_name="Get Video Components",
category="image/video", category="image/video",
essentials_category="Video Tools",
description="Extracts all components from a video: frames, audio, and framerate.", description="Extracts all components from a video: frames, audio, and framerate.",
inputs=[ inputs=[
io.Video.Input("video", tooltip="The video to extract components from."), io.Video.Input("video", tooltip="The video to extract components from."),
@ -174,6 +176,7 @@ class LoadVideo(io.ComfyNode):
search_aliases=["import video", "open video", "video file"], search_aliases=["import video", "open video", "video file"],
display_name="Load Video", display_name="Load Video",
category="image/video", category="image/video",
essentials_category="Basics",
inputs=[ inputs=[
io.Combo.Input("file", options=sorted(files), upload=io.UploadType.video), io.Combo.Input("file", options=sorted(files), upload=io.UploadType.video),
], ],

View File

@ -8,6 +8,7 @@ import json
import glob import glob
import hashlib import hashlib
import inspect import inspect
import traceback import traceback
import math import math
import time import time
@ -69,6 +70,7 @@ class CLIPTextEncode(ComfyNodeABC):
FUNCTION = "encode" FUNCTION = "encode"
CATEGORY = "conditioning" CATEGORY = "conditioning"
ESSENTIALS_CATEGORY = "Basics"
DESCRIPTION = "Encodes a text prompt using a CLIP model into an embedding that can be used to guide the diffusion model towards generating specific images." DESCRIPTION = "Encodes a text prompt using a CLIP model into an embedding that can be used to guide the diffusion model towards generating specific images."
SEARCH_ALIASES = ["text", "prompt", "text prompt", "positive prompt", "negative prompt", "encode text", "text encoder", "encode prompt"] SEARCH_ALIASES = ["text", "prompt", "text prompt", "positive prompt", "negative prompt", "encode text", "text encoder", "encode prompt"]
@ -667,6 +669,8 @@ class CLIPSetLastLayer:
return (clip,) return (clip,)
class LoraLoader: class LoraLoader:
ESSENTIALS_CATEGORY = "Image Generation"
def __init__(self): def __init__(self):
self.loaded_lora = None self.loaded_lora = None
@ -1648,6 +1652,7 @@ class SaveImage:
OUTPUT_NODE = True OUTPUT_NODE = True
CATEGORY = "image" CATEGORY = "image"
ESSENTIALS_CATEGORY = "Basics"
DESCRIPTION = "Saves the input images to your ComfyUI output directory." DESCRIPTION = "Saves the input images to your ComfyUI output directory."
SEARCH_ALIASES = ["save", "save image", "export image", "output image", "write image", "download"] SEARCH_ALIASES = ["save", "save image", "export image", "output image", "write image", "download"]
@ -1706,6 +1711,7 @@ class LoadImage:
} }
CATEGORY = "image" CATEGORY = "image"
ESSENTIALS_CATEGORY = "Basics"
SEARCH_ALIASES = ["load image", "open image", "import image", "image input", "upload image", "read image", "image loader"] SEARCH_ALIASES = ["load image", "open image", "import image", "image input", "upload image", "read image", "image loader"]
RETURN_TYPES = ("IMAGE", "MASK") RETURN_TYPES = ("IMAGE", "MASK")
@ -1863,6 +1869,7 @@ class ImageScale:
FUNCTION = "upscale" FUNCTION = "upscale"
CATEGORY = "image/upscaling" CATEGORY = "image/upscaling"
ESSENTIALS_CATEGORY = "Image Tools"
SEARCH_ALIASES = ["resize", "resize image", "scale image", "image resize", "zoom", "zoom in", "change size"] SEARCH_ALIASES = ["resize", "resize image", "scale image", "image resize", "zoom", "zoom in", "change size"]
def upscale(self, image, upscale_method, width, height, crop): def upscale(self, image, upscale_method, width, height, crop):
@ -1902,6 +1909,7 @@ class ImageScaleBy:
class ImageInvert: class ImageInvert:
SEARCH_ALIASES = ["reverse colors"] SEARCH_ALIASES = ["reverse colors"]
ESSENTIALS_CATEGORY = "Image Tools"
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
@ -1918,6 +1926,7 @@ class ImageInvert:
class ImageBatch: class ImageBatch:
SEARCH_ALIASES = ["combine images", "merge images", "stack images"] SEARCH_ALIASES = ["combine images", "merge images", "stack images"]
ESSENTIALS_CATEGORY = "Image Tools"
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):

View File

@ -689,6 +689,10 @@ class PromptServer():
info['api_node'] = obj_class.API_NODE info['api_node'] = obj_class.API_NODE
info['search_aliases'] = getattr(obj_class, 'SEARCH_ALIASES', []) info['search_aliases'] = getattr(obj_class, 'SEARCH_ALIASES', [])
if hasattr(obj_class, 'ESSENTIALS_CATEGORY'):
info['essentials_category'] = obj_class.ESSENTIALS_CATEGORY
return info return info
@routes.get("/object_info") @routes.get("/object_info")