Files
ComfyUI_frontend/src/constants/essentialsDisplayNames.ts
Yourz d6c1dd2e59 feat: improve essentials tab blueprint support and display names (#10113)
## Changes

### Essential nodes
- Include blueprint nodes in essentials tab via `BLUEPRINT_PREFIX_MAP`
matching, removing dependency on backend `essentials_category`
- Sort essentials folders by `ESSENTIALS_CATEGORIES` order
- Disambiguate duplicate blueprint labels with provider suffix (e.g. two
"Text to image" → "Text to image (Flux 1)")
- Resolve blueprint icons by prefix instead of full node name
- Add 15 new SVG icons for blueprint categories, remove 2 old
subgraph-blueprint-specific SVGs
- Remove unnecessary parenthetical suffixes from unique display names
("Load style (LoRA)" → "Load style", "Text generation (LLM)" → "Text
generation")

essential nodes with blueprints icon

<img width="507" height="550" alt="image"
src="https://github.com/user-attachments/assets/967cd4b6-ea4d-44a2-9d6d-e66c152370c7"
/>

### All nodes panel
- section bottom change from `pb-6` to `pb-2`

<img width="525" height="215" alt="image"
src="https://github.com/user-attachments/assets/252cf655-3138-42f9-a9ef-9d771d3281e4"
/>

### Favorite to Bookmark

- change `Favorite node` to `Bookmark node`
- change `Unfavorite node` to `Unbookmark node`
- change `No favorites yet` to `No bookmarks yet`

<img width="495" height="380" alt="image"
src="https://github.com/user-attachments/assets/7ba6f631-15ae-4406-874b-737551ca441c"
/>

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
2026-03-17 01:11:55 -07:00

167 lines
5.6 KiB
TypeScript

import type { EssentialsCategory } from '@/constants/essentialsNodes'
import { t } from '@/i18n'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
const BLUEPRINT_PREFIX = 'SubgraphBlueprint.'
/**
* Static mapping of node names to their Essentials tab display name i18n keys.
*/
const EXACT_NAME_MAP: Record<string, string> = {
// Basics
LoadImage: 'essentials.loadImage',
SaveImage: 'essentials.saveImage',
LoadVideo: 'essentials.loadVideo',
SaveVideo: 'essentials.saveVideo',
Load3D: 'essentials.load3DModel',
SaveGLB: 'essentials.save3DModel',
PrimitiveStringMultiline: 'essentials.text',
// Image Tools
BatchImagesNode: 'essentials.batchImage',
ImageCrop: 'essentials.cropImage',
ImageScale: 'essentials.resizeImage',
ImageRotate: 'essentials.rotate',
ImageInvert: 'essentials.invert',
Canny: 'essentials.canny',
RecraftRemoveBackgroundNode: 'essentials.removeBackground',
ImageCompare: 'essentials.imageCompare',
// Video Tools
'Video Slice': 'essentials.extractFrame',
// Image Generation
LoraLoader: 'essentials.loadStyleLora',
// Video Generation
KlingLipSyncAudioToVideoNode: 'essentials.lipsync',
KlingLipSyncTextToVideoNode: 'essentials.lipsync',
// Text Generation
OpenAIChatNode: 'essentials.textGenerationLLM',
// 3D
TencentTextToModelNode: 'essentials.textTo3DModel',
TencentImageToModelNode: 'essentials.imageTo3DModel',
MeshyTextToModelNode: 'essentials.textTo3DModel',
MeshyImageToModelNode: 'essentials.imageTo3DModel',
TripoTextToModelNode: 'essentials.textTo3DModel',
TripoImageToModelNode: 'essentials.imageTo3DModel',
// Audio
StabilityTextToAudio: 'essentials.musicGeneration',
LoadAudio: 'essentials.loadAudio',
SaveAudio: 'essentials.saveAudio'
}
/**
* Blueprint prefix patterns mapped to display name i18n keys.
* Entries are matched by checking if the blueprint filename
* (after removing the SubgraphBlueprint. prefix) starts with the key.
* Ordered longest-first so more specific prefixes match before shorter ones.
*/
const BLUEPRINT_PREFIX_MAP: [
prefix: string,
displayNameKey: string,
category: EssentialsCategory
][] = [
// Image Generation
['image_inpainting_', 'essentials.inpaintImage', 'image generation'],
['image_outpainting_', 'essentials.outpaintImage', 'image generation'],
['image_edit', 'essentials.imageToImage', 'image generation'],
['text_to_image', 'essentials.textToImage', 'image generation'],
['pose_to_image', 'essentials.poseToImage', 'image generation'],
['canny_to_image', 'essentials.cannyToImage', 'image generation'],
['depth_to_image', 'essentials.depthToImage', 'image generation'],
// Video Generation
['text_to_video', 'essentials.textToVideo', 'video generation'],
['image_to_video', 'essentials.imageToVideo', 'video generation'],
['pose_to_video', 'essentials.poseToVideo', 'video generation'],
['canny_to_video', 'essentials.cannyToVideo', 'video generation'],
['depth_to_video', 'essentials.depthToVideo', 'video generation']
]
function resolveBlueprintDisplayName(
blueprintName: string
): string | undefined {
for (const [prefix, displayNameKey] of BLUEPRINT_PREFIX_MAP) {
if (blueprintName.startsWith(prefix)) {
return t(displayNameKey)
}
}
return undefined
}
/**
* Resolves the icon class for a blueprint node based on its prefix.
* E.g. `SubgraphBlueprint.canny_to_image_flux` → `"icon-[comfy--canny-to-image]"`
*/
export function resolveBlueprintIcon(nodeName: string): string | undefined {
if (!nodeName.startsWith(BLUEPRINT_PREFIX)) return undefined
const blueprintName = nodeName.slice(BLUEPRINT_PREFIX.length)
for (const [prefix] of BLUEPRINT_PREFIX_MAP) {
if (blueprintName.startsWith(prefix)) {
const iconName = prefix.replace(/_$/, '').replaceAll('_', '-')
return `icon-[comfy--${iconName}]`
}
}
return undefined
}
/**
* Extracts the provider/model suffix from a blueprint name for disambiguation.
* E.g. `SubgraphBlueprint.text_to_image_flux_1` → `"Flux 1"`
*/
export function resolveBlueprintSuffix(nodeName: string): string | undefined {
if (!nodeName.startsWith(BLUEPRINT_PREFIX)) return undefined
const blueprintName = nodeName.slice(BLUEPRINT_PREFIX.length)
for (const [prefix] of BLUEPRINT_PREFIX_MAP) {
if (blueprintName.startsWith(prefix)) {
const raw = blueprintName.slice(prefix.length).replace(/^_/, '')
if (!raw) return undefined
return raw
.split('_')
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ')
}
}
return undefined
}
/**
* Returns the essentials category for a blueprint node based on its name,
* or `undefined` if the blueprint doesn't belong in the essentials tab.
*/
export function resolveBlueprintEssentialsCategory(
nodeName: string
): EssentialsCategory | undefined {
if (!nodeName.startsWith(BLUEPRINT_PREFIX)) return undefined
const blueprintName = nodeName.slice(BLUEPRINT_PREFIX.length)
for (const [prefix, , category] of BLUEPRINT_PREFIX_MAP) {
if (blueprintName.startsWith(prefix)) {
return category
}
}
return undefined
}
/**
* Resolves the Essentials tab display name for a given node definition.
* Returns `undefined` if the node has no Essentials display name mapping.
*/
export function resolveEssentialsDisplayName(
nodeDef: Pick<ComfyNodeDefImpl, 'name'> | undefined
): string | undefined {
if (!nodeDef) return undefined
const { name } = nodeDef
if (name.startsWith(BLUEPRINT_PREFIX)) {
const blueprintName = name.slice(BLUEPRINT_PREFIX.length)
return resolveBlueprintDisplayName(blueprintName)
}
const key = EXACT_NAME_MAP[name]
return key ? t(key) : undefined
}