feat: add model-to-node mappings for cloud asset categories#8468
feat: add model-to-node mappings for cloud asset categories#8468christian-byrne merged 8 commits intomainfrom
Conversation
- Register Chatterbox TTS node mappings in modelToNodeStore: - chatterbox/chatterbox → FL_ChatterboxTTS - chatterbox/chatterbox_turbo → FL_ChatterboxTurboTTS - chatterbox/chatterbox_multilingual → FL_ChatterboxMultilingualTTS - chatterbox/chatterbox_vc → FL_ChatterboxVC - Add hierarchical fallback in findProvidersWithFallback() for model type lookups (e.g., 'parent/child' falls back to 'parent') - Support empty widget key for auto-load nodes (skip widget assignment in createModelNodeFromAsset when provider.key is empty) - Add comprehensive tests for new behavior Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-019c0849-5650-7060-b4bd-23ef0c8dfa39
Co-authored-by: Alexander Brown <drjkl@comfy.org>
…llback - Early return undefined for null/undefined/non-string inputs - Prevents potential runtime errors from .split() on invalid types - Add tests for invalid input handling Amp-Thread-ID: https://ampcode.com/threads/T-019c0849-5650-7060-b4bd-23ef0c8dfa39 Co-authored-by: Amp <amp@ampcode.com>
Add mappings for 13 previously unmapped model categories in the Cloud asset browser, enabling users to click on models to create corresponding loader nodes on the canvas. Core nodes: - latent_upscale_models -> LatentUpscaleModelLoader Extension nodes: - sam2 -> DownloadAndLoadSAM2Model - sams -> SAMLoader - ultralytics -> UltralyticsDetectorProvider (with bbox/segm fallback) - depthanything -> DownloadAndLoadDepthAnythingV2Model - ipadapter -> IPAdapterModelLoader - segformer_b2_clothes, segformer_b3_clothes, segformer_b3_fashion -> LS_LoadSegformerModel - nlf -> LoadNLFModel - FlashVSR, FlashVSR-v1.1 -> FlashVSRNode (auto-load pattern) Amp-Thread-ID: https://ampcode.com/threads/T-019c0c8c-d68e-73ba-a2b3-3db28e960eb1 Co-authored-by: Amp <amp@ampcode.com>
📝 WalkthroughWalkthroughThe changes implement a hierarchical fallback system for model type lookups and add support for empty provider keys to enable auto-loading behavior. Widget assignment in model node creation is now conditional on provider key presence, and numerous new model type to node provider mappings are registered. Changes
Possibly related PRs
Suggested reviewers
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/stores/modelToNodeStore.ts`:
- Around line 184-232: The segformer registrations use the wrong node class
name: update quickRegister calls for 'segformer_b2_clothes',
'segformer_b3_clothes', and 'segformer_b3_fashion' to use the actual class name
"LoadSegformerModel" instead of "LS_LoadSegformerModel" so
nodeDefStore.nodeDefsByName can find them; additionally, add a runtime warning
where registrations are skipped (the check that looks up
nodeDefStore.nodeDefsByName — currently silently continues around line ~141) to
log which node class name and quickRegister key failed so missing/renamed node
classes are visible at startup.
| 'FL_ChatterboxTurboTTS', | ||
| 'FL_ChatterboxMultilingualTTS', | ||
| 'FL_ChatterboxVC', | ||
| // New extension node mappings |
There was a problem hiding this comment.
Remove this useless comment after rebasing on main
There was a problem hiding this comment.
@coderabbitai make a tracking Issue to remove these unneccessary code comments
There was a problem hiding this comment.
🧩 Analysis chain
@christian-byrne I'll create a tracking issue for removing unnecessary code comments in the test file.
🏁 Script executed:
#!/bin/bash
# Create a GitHub issue to track removal of unnecessary code comments
gh issue create \
--repo Comfy-Org/ComfyUI_frontend \
--title "Remove unnecessary code comments from test files" \
--body "As noted in PR `#8468`, there are unnecessary code comments in test files that should be removed to maintain code cleanliness and align with the project's preference for self-documenting code.
Specific example: \`// New extension node mappings\` comment in \`src/stores/modelToNodeStore.test.ts\`
**Context:**
- PR: https://github.com/Comfy-Org/ComfyUI_frontend/pull/8468
- Comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/8468#discussion_r2744451100
- Requested by: `@christian-byrne`
**Scope:**
Review test files for redundant comments that don't add value beyond what the code already expresses through clear naming and structure." \
--assignee christian-byrne
Length of output: 57
@christian-byrne Issue created!
#8705: Remove unnecessary code comments from test files
The issue tracks removal of redundant comments like // New extension node mappings that don't add value beyond what the code already expresses through clear naming.
🧬 Code Graph Analysis Results
[src/stores/modelToNodeStore.ts] (lines 22-245)
export const useModelToNodeStore = defineStore('modelToNode', () => {
const modelToNodeMap = ref<Record<string, ModelNodeProvider[]>>({})
const nodeDefStore = useNodeDefStore()
const haveDefaultsLoaded = ref(false)
/** Internal computed for reactive caching of registered node types */
const registeredNodeTypes = computed<Record<string, string>>(() => {
return Object.fromEntries(
Object.values(modelToNodeMap.value)
.flat()
.filter((provider) => !!provider.nodeDef)
.map((provider) => [provider.nodeDef.name, provider.key])
)
})
/** Internal computed for efficient reverse lookup: nodeType -> category */
const nodeTypeToCategory = computed(() => {
const lookup: Record<string, string> = {}
for (const [category, providers] of Object.entries(modelToNodeMap.value)) {
for (const provider of providers) {
// Extension nodes may not be installed
if (!provider.nodeDef) continue
// Only store the first category for each node type (matches current assetService behavior)
if (!lookup[provider.nodeDef.name]) {
lookup[provider.nodeDef.name] = category
}
}
}
return lookup
})
/** Get set of all registered node types for efficient lookup */
function getRegisteredNodeTypes(): Record<string, string> {
registerDefaults()
return registeredNodeTypes.value
}
/**
* Get the category for a given node type.
* Performs efficient O(1) lookup using cached reverse map.
* `@param` nodeType The node type name to find the category for
* `@returns` The category name, or undefined if not found
*/
function getCategoryForNodeType(nodeType: string): string | undefined {
registerDefaults()
// Handle invalid input gracefully
if (!nodeType || typeof nodeType !== 'string') {
return undefined
}
return nodeTypeToCategory.value[nodeType]
}
/**
* Find providers for modelType with hierarchical fallback.
* Tries exact match first, then falls back to top-level segment (e.g., "parent/child" → "parent").
* Note: Only falls back one level; "a/b/c" tries "a/b/c" then "a", not "a/b".
*/
function findProvidersWithFallback(
modelType: string
): ModelNodeProvider[] | undefined {
if (!modelType || typeof modelType !== 'string') {
return undefined
}
const exactMatch = modelToNodeMap.value[modelType]
if (exactMatch && exactMatch.length > 0) return exactMatch
const topLevel = modelType.split('/')[0]
if (topLevel === modelType) return undefined
const fallback = modelToNodeMap.value[topLevel]
if (fallback && fallback.length > 0) return fallback
return undefined
}
/**
* Get the node provider for the given model type name.
* Supports hierarchical lookups: if "parent/child" has no match, falls back to "parent".
* `@param` modelType The name of the model type to get the node provider for.
* `@returns` The node provider for the given model type name.
*/
function getNodeProvider(modelType: string): ModelNodeProvider | undefined {
registerDefaults()
return findProvidersWithFallback(modelType)?.[0]
}
/**
* Get the list of all valid node providers for the given model type name.
* Supports hierarchical lookups: if "parent/child" has no match, falls back to "parent".
* `@param` modelType The name of the model type to get the node providers for.
* `@returns` The list of all valid node providers for the given model type name.
*/
function getAllNodeProviders(modelType: string): ModelNodeProvider[] {
registerDefaults()
return findProvidersWithFallback(modelType) ?? []
}
/**
* Register a node provider for the given model type name.
* `@param` modelType The name of the model type to register the node provider for.
* `@param` nodeProvider The node provider to register.
*/
function registerNodeProvider(
modelType: string,
nodeProvider: ModelNodeProvider
) {
registerDefaults()
if (!nodeProvider.nodeDef) return
if (!modelToNodeMap.value[modelType]) {
modelToNodeMap.value[modelType] = []
}
modelToNodeMap.value[modelType].push(nodeProvider)
}
/**
* Register a node provider for the given simple names.
* `@param` modelType The name of the model type to register the node provider for.
* `@param` nodeClass The node class name to register.
* `@param` key The key to use for the node input.
*/
function quickRegister(modelType: string, nodeClass: string, key: string) {
registerNodeProvider(
modelType,
new ModelNodeProvider(nodeDefStore.nodeDefsByName[nodeClass], key)
)
}
function registerDefaults() {
if (haveDefaultsLoaded.value) {
return
}
if (Object.keys(nodeDefStore.nodeDefsByName).length === 0) {
return
}
haveDefaultsLoaded.value = true
quickRegister('checkpoints', 'CheckpointLoaderSimple', 'ckpt_name')
quickRegister('checkpoints', 'ImageOnlyCheckpointLoader', 'ckpt_name')
quickRegister('loras', 'LoraLoader', 'lora_name')
quickRegister('loras', 'LoraLoaderModelOnly', 'lora_name')
quickRegister('vae', 'VAELoader', 'vae_name')
quickRegister('controlnet', 'ControlNetLoader', 'control_net_name')
quickRegister('diffusion_models', 'UNETLoader', 'unet_name')
quickRegister('upscale_models', 'UpscaleModelLoader', 'model_name')
quickRegister('style_models', 'StyleModelLoader', 'style_model_name')
quickRegister('gligen', 'GLIGENLoader', 'gligen_name')
quickRegister('clip_vision', 'CLIPVisionLoader', 'clip_name')
quickRegister('text_encoders', 'CLIPLoader', 'clip_name')
quickRegister('audio_encoders', 'AudioEncoderLoader', 'audio_encoder_name')
quickRegister('model_patches', 'ModelPatchLoader', 'name')
quickRegister(
'animatediff_models',
'ADE_LoadAnimateDiffModel',
'model_name'
)
quickRegister(
'animatediff_motion_lora',
'ADE_AnimateDiffLoRALoader',
'name'
)
// Chatterbox TTS nodes: empty key means the node auto-loads models without
// a widget selector (createModelNodeFromAsset skips widget assignment)
quickRegister('chatterbox/chatterbox', 'FL_ChatterboxTTS', '')
quickRegister('chatterbox/chatterbox_turbo', 'FL_ChatterboxTurboTTS', '')
quickRegister(
'chatterbox/chatterbox_multilingual',
'FL_ChatterboxMultilingualTTS',
''
)
quickRegister('chatterbox/chatterbox_vc', 'FL_ChatterboxVC', '')
// Latent upscale models (ComfyUI core - nodes_hunyuan.py)
quickRegister(
'latent_upscale_models',
'LatentUpscaleModelLoader',
'model_name'
)
// SAM/SAM2 segmentation models (comfyui-segment-anything-2, comfyui-impact-pack)
quickRegister('sam2', 'DownloadAndLoadSAM2Model', 'model')
quickRegister('sams', 'SAMLoader', 'model_name')
// Ultralytics detection models (comfyui-impact-subpack)
// Note: ultralytics/bbox and ultralytics/segm fall back to this via hierarchical lookup
quickRegister('ultralytics', 'UltralyticsDetectorProvider', 'model_name')
// DepthAnything models (comfyui-depthanythingv2)
quickRegister(
'depthanything',
'DownloadAndLoadDepthAnythingV2Model',
'model'
)
// IP-Adapter models (comfyui_ipadapter_plus)
quickRegister('ipadapter', 'IPAdapterModelLoader', 'ipadapter_file')
// Segformer clothing/fashion segmentation models (comfyui_layerstyle)
quickRegister('segformer_b2_clothes', 'LS_LoadSegformerModel', 'model_name')
quickRegister('segformer_b3_clothes', 'LS_LoadSegformerModel', 'model_name')
quickRegister('segformer_b3_fashion', 'LS_LoadSegformerModel', 'model_name')
// NLF pose estimation models (ComfyUI-WanVideoWrapper)
quickRegister('nlf', 'LoadNLFModel', 'nlf_model')
// FlashVSR video super-resolution (ComfyUI-FlashVSR_Ultra_Fast)
// Empty key means the node auto-loads models without a widget selector
quickRegister('FlashVSR', 'FlashVSRNode', '')
quickRegister('FlashVSR-v1.1', 'FlashVSRNode', '')
}
return {
modelToNodeMap,
getRegisteredNodeTypes,
getCategoryForNodeType,
getNodeProvider,
getAllNodeProviders,
registerNodeProvider,
quickRegister,
registerDefaults
}
})
[src/stores/modelToNodeStore.ts] (lines 8-19)
export class ModelNodeProvider {
/** The node definition to use for this model. */
public nodeDef: ComfyNodeDefImpl
/** The node input key for where to insert the model name. */
public key: string
constructor(nodeDef: ComfyNodeDefImpl, key: string) {
this.nodeDef = nodeDef
this.key = key
}
}
🧠 Learnings used
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/AGENTS.md:0-0
Timestamp: 2026-01-27T20:51:47.936Z
Learning: Applies to src/**/stores/**/*.{js,ts} : Restrict store extension access to keep internal implementation private
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: browser_tests/AGENTS.md:0-0
Timestamp: 2026-02-03T20:31:03.021Z
Learning: Applies to browser_tests/**/*.spec.ts : Use premade JSON workflows from the assets/ directory to load desired graph state in tests
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: browser_tests/AGENTS.md:0-0
Timestamp: 2026-02-03T20:33:39.759Z
Learning: Applies to browser_tests/**/*.spec.ts : Use premade JSON workflows in the `assets/` directory to load desired graph state in E2E tests
Learnt from: jtydhr88
Repo: Comfy-Org/ComfyUI_frontend PR: 7214
File: src/i18n.ts:97-98
Timestamp: 2025-12-08T01:21:41.368Z
Learning: In src/i18n.ts and related i18n code, use `Record<string, unknown>` for locale data structures (including custom nodes i18n data) to maintain consistency with existing patterns used in localeLoaders, nodeDefsLoaders, commandsLoaders, and settingsLoaders.
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-03T20:30:57.039Z
Learning: Do not add or retain redundant comments; clean as you go
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8497
File: src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue:223-236
Timestamp: 2026-02-01T21:10:36.669Z
Learning: In the ComfyUI_frontend repository, the maintainer DrJKL prefers not to maintain or fix code comments, expressing that "all comments are lies" and they become outdated. Focus on self-documenting code through clear naming rather than suggesting comment fixes or additions.
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7177
File: src/platform/assets/components/UploadModelFooter.vue:72-78
Timestamp: 2025-12-05T06:11:09.383Z
Learning: For the ComfyUI_frontend repository, avoid suggesting comments that would be redundant when the code is already self-explanatory through descriptive naming (e.g., filenames, prop names, aria-labels). The project prefers clean code without unnecessary documentation comments.
- Resolve merge conflicts with main - Fix incorrect Segformer node class: LS_LoadSegformerModel -> LoadSegformerModel (LS_LoadSegformerModel is only the internal node ID, not the Python class name) - Update tests to use correct class name Amp-Thread-ID: https://ampcode.com/threads/T-019c0cdc-0150-7698-b71f-1f18d053521c
🎨 Storybook Build Status✅ Build completed successfully! ⏰ Completed at: 02/06/2026, 10:06:57 PM UTC 🔗 Links🎉 Your Storybook is ready for review! |
🎭 Playwright Tests:
|
Bundle Size ReportSummary
Category Glance Per-category breakdownApp Entry Points — 22.5 kB (baseline 22.5 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 840 kB (baseline 840 kB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 69 kB (baseline 69 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed Panels & Settings — 410 kB (baseline 410 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 12 added / 12 removed User & Accounts — 16 kB (baseline 16 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed Editors & Dialogs — 3.47 kB (baseline 3.47 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 2 added / 2 removed UI Components — 37.8 kB (baseline 37.8 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed Data & Services — 2.1 MB (baseline 2.1 MB) • 🔴 +825 BStores, services, APIs, and repositories
Status: 11 added / 11 removed Utilities & Hooks — 234 kB (baseline 234 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 12 added / 12 removed Vendor & Third-Party — 9.37 MB (baseline 9.37 MB) • ⚪ 0 BExternal libraries and shared vendor chunks
Other — 7.1 MB (baseline 7.1 MB) • ⚪ 0 BBundles that do not match a named category
Status: 49 added / 49 removed |
CodeRabbit suggestion was incorrect - LS_LoadSegformerModel is the correct node type name used in NODE_CLASS_MAPPINGS for comfyui_layerstyle. Amp-Thread-ID: https://ampcode.com/threads/T-019c0cdc-0150-7698-b71f-1f18d053521c
There was a problem hiding this comment.
@coderabbitai Open an issue to parameterize this test.
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Create GitHub issue for parameterizing tests
gh issue create \
--title "Parameterize repetitive test cases in modelToNodeStore.test.ts" \
--body "## Context
Identified during review of PR `#8468`: https://github.com/Comfy-Org/ComfyUI_frontend/pull/8468
Comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/8468#discussion_r2776248903
## Description
Several test cases in \`src/stores/modelToNodeStore.test.ts\` follow similar patterns and could be parameterized using Vitest's \`it.each()\` or similar approaches to reduce code duplication and improve maintainability.
## Test Cases to Parameterize
The following tests in the \`getNodeProvider\` describe block have similar structures:
1. **\"should return provider for new extension model types\"** - tests multiple model types (sam2, sams, ipadapter, depthanything) with assertions on nodeDef name and key
2. **\"should use hierarchical fallback for ultralytics subcategories\"** - tests ultralytics/bbox and ultralytics/segm
3. **\"should return provider for FlashVSR nodes with empty key (auto-load)\"** - tests FlashVSR and FlashVSR-v1.1
4. **\"should return provider for segformer models\"** - tests segformer_b2_clothes and segformer_b3_fashion
## Suggested Approach
Use Vitest's \`it.each()\` to create a test table with columns for:
- Model type
- Expected node definition name
- Expected key value
Example pattern:
\`\`\`typescript
it.each([
['sam2', 'DownloadAndLoadSAM2Model', 'model'],
['sams', 'SAMLoader', 'model_name'],
['ipadapter', 'IPAdapterModelLoader', 'ipadapter_file'],
// ... more test cases
])('should return provider for %s model type', (modelType, expectedNodeName, expectedKey) => {
const modelToNodeStore = useModelToNodeStore()
modelToNodeStore.registerDefaults()
const provider = modelToNodeStore.getNodeProvider(modelType)
expect(provider?.nodeDef?.name).toBe(expectedNodeName)
expect(provider?.key).toBe(expectedKey)
})
\`\`\`
## Benefits
- Reduces code duplication
- Makes it easier to add new test cases
- Improves test maintainability
- Clearer test structure
---
Requested by: `@DrJKL`" \
--assignee DrJKL
Length of output: 57
@DrJKL, I've opened issue #8703 to track the test parameterization work: #8703
The issue outlines using Vitest's it.each() to consolidate the repetitive test cases for model type providers, which should reduce code duplication and make it easier to add new test cases in the future.
🧬 Code Graph Analysis Results
[src/stores/modelToNodeStore.ts:22-245]
export const useModelToNodeStore = defineStore('modelToNode', () => {
const modelToNodeMap = ref<Record<string, ModelNodeProvider[]>>({})
const nodeDefStore = useNodeDefStore()
const haveDefaultsLoaded = ref(false)
/** Internal computed for reactive caching of registered node types */
const registeredNodeTypes = computed<Record<string, string>>(() => {
return Object.fromEntries(
Object.values(modelToNodeMap.value)
.flat()
.filter((provider) => !!provider.nodeDef)
.map((provider) => [provider.nodeDef.name, provider.key])
)
})
/** Internal computed for efficient reverse lookup: nodeType -> category */
const nodeTypeToCategory = computed(() => {
const lookup: Record<string, string> = {}
for (const [category, providers] of Object.entries(modelToNodeMap.value)) {
for (const provider of providers) {
// Extension nodes may not be installed
if (!provider.nodeDef) continue
// Only store the first category for each node type (matches current assetService behavior)
if (!lookup[provider.nodeDef.name]) {
lookup[provider.nodeDef.name] = category
}
}
}
return lookup
})
/** Get set of all registered node types for efficient lookup */
function getRegisteredNodeTypes(): Record<string, string> {
registerDefaults()
return registeredNodeTypes.value
}
/**
- Get the category for a given node type.
- Performs efficient O(1) lookup using cached reverse map.
@paramnodeType The node type name to find the category for@returnsThe category name, or undefined if not found
*/
function getCategoryForNodeType(nodeType: string): string | undefined {
registerDefaults()
// Handle invalid input gracefully
if (!nodeType || typeof nodeType !== 'string') {
return undefined
}
return nodeTypeToCategory.value[nodeType]
}
/**
- Find providers for modelType with hierarchical fallback.
- Tries exact match first, then falls back to top-level segment (e.g., "parent/child" → "parent").
- Note: Only falls back one level; "a/b/c" tries "a/b/c" then "a", not "a/b".
*/
function findProvidersWithFallback(
modelType: string
): ModelNodeProvider[] | undefined {
if (!modelType || typeof modelType !== 'string') {
return undefined
}
const exactMatch = modelToNodeMap.value[modelType]
if (exactMatch && exactMatch.length > 0) return exactMatch
const topLevel = modelType.split('/')[0]
if (topLevel === modelType) return undefined
const fallback = modelToNodeMap.value[topLevel]
if (fallback && fallback.length > 0) return fallback
return undefined
}
/**
- Get the node provider for the given model type name.
- Supports hierarchical lookups: if "parent/child" has no match, falls back to "parent".
@parammodelType The name of the model type to get the node provider for.@returnsThe node provider for the given model type name.
*/
function getNodeProvider(modelType: string): ModelNodeProvider | undefined {
registerDefaults()
return findProvidersWithFallback(modelType)?.[0]
}
/**
- Get the list of all valid node providers for the given model type name.
- Supports hierarchical lookups: if "parent/child" has no match, falls back to "parent".
@parammodelType The name of the model type to get the node providers for.@returnsThe list of all valid node providers for the given model type name.
/
function getAllNodeProviders(modelType: string): ModelNodeProvider[] {
registerDefaults()
return findProvidersWithFallback(modelType) ?? []
}
/*- Register a node provider for the given model type name.
@parammodelType The name of the model type to register the node provider for.@paramnodeProvider The node provider to register.
/
function registerNodeProvider(
modelType: string,
nodeProvider: ModelNodeProvider
) {
registerDefaults()
if (!nodeProvider.nodeDef) return
if (!modelToNodeMap.value[modelType]) {
modelToNodeMap.value[modelType] = []
}
modelToNodeMap.value[modelType].push(nodeProvider)
}
/*- Register a node provider for the given simple names.
@parammodelType The name of the model type to register the node provider for.@paramnodeClass The node class name to register.@paramkey The key to use for the node input.
*/
function quickRegister(modelType: string, nodeClass: string, key: string) {
registerNodeProvider(
modelType,
new ModelNodeProvider(nodeDefStore.nodeDefsByName[nodeClass], key)
)
}
function registerDefaults() {
if (haveDefaultsLoaded.value) {
return
}
if (Object.keys(nodeDefStore.nodeDefsByName).length === 0) {
return
}
haveDefaultsLoaded.value = true
quickRegister('checkpoints', 'CheckpointLoaderSimple', 'ckpt_name')
quickRegister('checkpoints', 'ImageOnlyCheckpointLoader', 'ckpt_name')
quickRegister('loras', 'LoraLoader', 'lora_name')
quickRegister('loras', 'LoraLoaderModelOnly', 'lora_name')
quickRegister('vae', 'VAELoader', 'vae_name')
quickRegister('controlnet', 'ControlNetLoader', 'control_net_name')
quickRegister('diffusion_models', 'UNETLoader', 'unet_name')
quickRegister('upscale_models', 'UpscaleModelLoader', 'model_name')
quickRegister('style_models', 'StyleModelLoader', 'style_model_name')
quickRegister('gligen', 'GLIGENLoader', 'gligen_name')
quickRegister('clip_vision', 'CLIPVisionLoader', 'clip_name')
quickRegister('text_encoders', 'CLIPLoader', 'clip_name')
quickRegister('audio_encoders', 'AudioEncoderLoader', 'audio_encoder_name')
quickRegister('model_patches', 'ModelPatchLoader', 'name')
quickRegister(
'animatediff_models',
'ADE_LoadAnimateDiffModel',
'model_name'
)
quickRegister(
'animatediff_motion_lora',
'ADE_AnimateDiffLoRALoader',
'name'
)
// Chatterbox TTS nodes: empty key means the node auto-loads models without
// a widget selector (createModelNodeFromAsset skips widget assignment)
quickRegister('chatterbox/chatterbox', 'FL_ChatterboxTTS', '')
quickRegister('chatterbox/chatterbox_turbo', 'FL_ChatterboxTurboTTS', '')
quickRegister(
'chatterbox/chatterbox_multilingual',
'FL_ChatterboxMultilingualTTS',
''
)
quickRegister('chatterbox/chatterbox_vc', 'FL_ChatterboxVC', '')
// Latent upscale models (ComfyUI core - nodes_hunyuan.py)
quickRegister(
'latent_upscale_models',
'LatentUpscaleModelLoader',
'model_name'
)
// SAM/SAM2 segmentation models (comfyui-segment-anything-2, comfyui-impact-pack)
quickRegister('sam2', 'DownloadAndLoadSAM2Model', 'model')
quickRegister('sams', 'SAMLoader', 'model_name')
// Ultralytics detection models (comfyui-impact-subpack)
// Note: ultralytics/bbox and ultralytics/segm fall back to this via hierarchical lookup
quickRegister('ultralytics', 'UltralyticsDetectorProvider', 'model_name')
// DepthAnything models (comfyui-depthanythingv2)
quickRegister(
'depthanything',
'DownloadAndLoadDepthAnythingV2Model',
'model'
)
// IP-Adapter models (comfyui_ipadapter_plus)
quickRegister('ipadapter', 'IPAdapterModelLoader', 'ipadapter_file')
// Segformer clothing/fashion segmentation models (comfyui_layerstyle)
quickRegister('segformer_b2_clothes', 'LS_LoadSegformerModel', 'model_name')
quickRegister('segformer_b3_clothes', 'LS_LoadSegformerModel', 'model_name')
quickRegister('segformer_b3_fashion', 'LS_LoadSegformerModel', 'model_name')
// NLF pose estimation models (ComfyUI-WanVideoWrapper)
quickRegister('nlf', 'LoadNLFModel', 'nlf_model')
// FlashVSR video super-resolution (ComfyUI-FlashVSR_Ultra_Fast)
// Empty key means the node auto-loads models without a widget selector
quickRegister('FlashVSR', 'FlashVSRNode', '')
quickRegister('FlashVSR-v1.1', 'FlashVSRNode', '')
}
return {
modelToNodeMap,
getRegisteredNodeTypes,
getCategoryForNodeType,
getNodeProvider,
getAllNodeProviders,
registerNodeProvider,
quickRegister,
registerDefaults
}
})
[src/stores/modelToNodeStore.ts:8-19]
export class ModelNodeProvider {
/** The node definition to use for this model. */
public nodeDef: ComfyNodeDefImpl
/** The node input key for where to insert the model name. */
public key: string
constructor(nodeDef: ComfyNodeDefImpl, key: string) {
this.nodeDef = nodeDef
this.key = key
}
}
There was a problem hiding this comment.
@coderabbitai it.for is better: https://vitest.dev/api/#test-for
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Update the issue to use it.for instead of it.each()
gh issue edit 8703 --body "## Context
Identified during review of PR `#8468`: https://github.com/Comfy-Org/ComfyUI_frontend/pull/8468
Comment: https://github.com/Comfy-Org/ComfyUI_frontend/pull/8468#discussion_r2776248903
## Description
Several test cases in \`src/stores/modelToNodeStore.test.ts\` follow similar patterns and could be parameterized using Vitest's \`it.for()\` to reduce code duplication and improve maintainability.
## Test Cases to Parameterize
The following tests in the \`getNodeProvider\` describe block have similar structures:
1. **\"should return provider for new extension model types\"** - tests multiple model types (sam2, sams, ipadapter, depthanything) with assertions on nodeDef name and key
2. **\"should use hierarchical fallback for ultralytics subcategories\"** - tests ultralytics/bbox and ultralytics/segm
3. **\"should return provider for FlashVSR nodes with empty key (auto-load)\"** - tests FlashVSR and FlashVSR-v1.1
4. **\"should return provider for segformer models\"** - tests segformer_b2_clothes and segformer_b3_fashion
## Suggested Approach
Use Vitest's \`it.for()\` to create a test table with columns for:
- Model type
- Expected node definition name
- Expected key value
\`it.for()\` is preferred over \`it.each()\` as it provides better TestContext support and follows Vitest best practices.
Example pattern:
\`\`\`typescript
it.for([
['sam2', 'DownloadAndLoadSAM2Model', 'model'],
['sams', 'SAMLoader', 'model_name'],
['ipadapter', 'IPAdapterModelLoader', 'ipadapter_file'],
['depthanything', 'DownloadAndLoadDepthAnythingV2Model', 'model'],
// ... more test cases
])('should return provider for %s model type', ([modelType, expectedNodeName, expectedKey]) => {
const modelToNodeStore = useModelToNodeStore()
modelToNodeStore.registerDefaults()
const provider = modelToNodeStore.getNodeProvider(modelType)
expect(provider?.nodeDef?.name).toBe(expectedNodeName)
expect(provider?.key).toBe(expectedKey)
})
\`\`\`
Note: \`it.for()\` doesn't spread array arguments (unlike \`it.each()\`), so the test function receives the entire array and needs to destructure it.
## Benefits
- Reduces code duplication
- Makes it easier to add new test cases
- Improves test maintainability
- Clearer test structure
- Better TestContext support compared to \`it.each()\`
---
Requested by: `@DrJKL`"
Length of output: 57
@DrJKL, you're absolutely right—thank you for the correction! I've updated issue #8703 to recommend it.for() instead of it.each().
it.for() is indeed the better choice as it provides TestContext support and follows Vitest best practices. The main difference is that it.for() doesn't spread array arguments, so the test function receives the entire array and needs to destructure it, which gives more flexibility with TestContext access.
The updated issue now includes the correct example pattern using it.for().
In test files at src/**/*.test.ts in the Comfy-Org/ComfyUI_frontend repository, prefer using Vitest's it.for() over it.each() for parameterized tests, as it.for() provides better TestContext support and follows Vitest best practices.
🧬 Code Graph Analysis Results
[src/stores/modelToNodeStore.ts] (lines 22-245)
"""
Docstring: useModelToNodeStore implementation and associated internal helpers.
Context:
- Exports useModelToNodeStore, a Pinia store managing mapping from model types to
ModelNodeProvider instances, enabling dynamic loading/registration of node
providers for different model categories.
Key API and internal state:
- modelToNodeMap: ref<Record<string, ModelNodeProvider[]>>; maps modelType -> list of providers.
- nodeDefStore: dependency on useNodeDefStore(); provides available node definitions.
- haveDefaultsLoaded: ref; guards one-time population of defaults.
Computed helpers:
- registeredNodeTypes: derived mapping of nodeDef.name -> provider.key for all registered providers.
- nodeTypeToCategory: derived reverse mapping: nodeDef.name -> category (first category only).
Public/store methods:
- getRegisteredNodeTypes(): Record<string, string> — ensures defaults loaded; returns mapping of node type to widget key.
- getCategoryForNodeType(nodeType: string): string | undefined — returns category for a given node type; ensures defaults; handles invalid input gracefully.
- findProvidersWithFallback(modelType: string): ModelNodeProvider[] | undefined — exact match or hierarchical top-level fallback (e.g., "parent/child" -> "parent" if needed).
- getNodeProvider(modelType: string): ModelNodeProvider | undefined — returns primary provider; supports fallback.
- getAllNodeProviders(modelType: string): ModelNodeProvider[] — returns all providers matching modelType or its fallback.
- registerNodeProvider(modelType: string, nodeProvider: ModelNodeProvider): void — registers a provider; ignores if provider.nodeDef is undefined.
- quickRegister(modelType: string, nodeClass: string, key: string): void — convenience to register via a nodeDefStore lookup.
- registerDefaults(): void — populates default registrations for all known model types using predefined mappings; idempotent.
Behaviors:
- Lazy registration: getNodeProvider/getAllNodeProviders trigger registerDefaults() if not yet loaded.
- Hierarchical fallback support for model types like "ultralytics/bbox" or "checkpoints/subfolder".
- Robustness: handles invalid inputs gracefully; non-existent node classes are filtered out.
Notes:
- This snippet models the core store logic used by tests to verify default registrations,
provider retrieval, fallback behavior, and dynamic registration paths.
"""
[src/stores/modelToNodeStore.ts] (lines 8-19)
"""
Docstring: ModelNodeProvider class
Context:
- Simple container for a node definition and its associated key used when registering
model types to node providers.
API:
- class ModelNodeProvider
- nodeDef: ComfyNodeDefImpl — the node definition used by this provider.
- key: string — the input key for where to insert the model name.
- constructor(nodeDef: ComfyNodeDefImpl, key: string) — initializes the two fields.
Usage:
- Instances are created and passed to registerNodeProvider / quickRegister to wire
model types to specific node definitions and keys.
"""
🧠 Learnings used
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-03T20:30:57.039Z
Learning: Applies to **/*.test.ts : For mocking in Vitest, leverage Vitest's utilities where possible
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/lib/litegraph/AGENTS.md:0-0
Timestamp: 2026-02-03T23:08:09.731Z
Learning: Applies to src/lib/litegraph/**/*.test.{ts,tsx} : Use provided test helper functions `createTestSubgraph` and `createTestSubgraphNode` from `__fixtures__/subgraphHelpers` for test setup
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8497
File: src/platform/assets/utils/assetFilterUtils.test.ts:14-25
Timestamp: 2026-02-01T21:08:47.817Z
Learning: In test files at src/**/*.test.ts, when creating test helper functions that construct mock objects implementing an interface (e.g., AssetItem), use `satisfies InterfaceType` for shape validation instead of type assertions like `as Partial<InterfaceType> as InterfaceType` or `as any`.
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7894
File: src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.test.ts:11-14
Timestamp: 2026-01-08T02:40:22.621Z
Learning: In the Comfy-Org/ComfyUI_frontend repository test files: When testing components, import the real type definitions from the component files instead of duplicating interface definitions in the test files. This prevents type drift and maintains consistency.
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-03T20:30:57.039Z
Learning: Applies to **/*.test.ts : Keep module mocks contained; do not use global mutable state within test files; use `vi.hoisted()` if necessary for per-test Arrange phase manipulation
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: browser_tests/AGENTS.md:0-0
Timestamp: 2026-02-03T20:33:39.759Z
Learning: Applies to browser_tests/**/*.spec.ts : Use premade JSON workflows in the `assets/` directory to load desired graph state in E2E tests
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: browser_tests/AGENTS.md:0-0
Timestamp: 2026-02-03T20:31:03.021Z
Learning: Applies to browser_tests/**/*.spec.ts : Use premade JSON workflows from the assets/ directory to load desired graph state in tests
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-03T20:33:39.759Z
Learning: Applies to src/**/*.test.ts : Use Vitest (happy-dom) for unit and component tests
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-03T20:30:57.039Z
Learning: Applies to browser_tests/**/*.spec.ts : Follow Playwright Best Practices in E2E tests
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-03T20:30:57.039Z
Learning: Applies to **/*.test.ts : For Component testing, use Vue Test Utils and follow best practices about making components easy to test
## Summary Add mappings for 13 previously unmapped model categories in the Cloud asset browser, enabling users to click on models to create corresponding loader nodes on the canvas. ## Changes ### Core nodes - `latent_upscale_models` → `LatentUpscaleModelLoader` ### Extension nodes | Category | Node Class | Widget Key | |----------|-----------|-----------| | `sam2` | `DownloadAndLoadSAM2Model` | `model` | | `sams` | `SAMLoader` | `model_name` | | `ultralytics` | `UltralyticsDetectorProvider` | `model_name` | | `depthanything` | `DownloadAndLoadDepthAnythingV2Model` | `model` | | `ipadapter` | `IPAdapterModelLoader` | `ipadapter_file` | | `segformer_b2_clothes` | `LS_LoadSegformerModel` | `model_name` | | `segformer_b3_clothes` | `LS_LoadSegformerModel` | `model_name` | | `segformer_b3_fashion` | `LS_LoadSegformerModel` | `model_name` | | `nlf` | `LoadNLFModel` | `nlf_model` | | `FlashVSR` | `FlashVSRNode` | (auto-load) | | `FlashVSR-v1.1` | `FlashVSRNode` | (auto-load) | ### Hierarchical fallback - `ultralytics/bbox` and `ultralytics/segm` fall back to the `ultralytics` mapping ### Skipped categories - `vae_approx` - No user-facing loader (used internally for latent previews) - `detection` - No specific loader exists ## Testing - Added unit tests for all new mappings - Tests verify hierarchical fallback works correctly - All 40 tests pass ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8468-feat-add-model-to-node-mappings-for-cloud-asset-categories-2f86d73d365081389ea5fbfc52ecbfad) by [Unito](https://www.unito.io) --------- Co-authored-by: Subagent 5 <subagent@example.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: GitHub Action <action@github.com>
|
@christian-byrne Successfully backported to #8706 |
…t categories (#8706) Backport of #8468 to `cloud/1.38` Automatically created by backport workflow. Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Subagent 5 <subagent@example.com> Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: GitHub Action <action@github.com>
Summary
Add mappings for 13 previously unmapped model categories in the Cloud asset browser, enabling users to click on models to create corresponding loader nodes on the canvas.
Changes
Core nodes
latent_upscale_models→LatentUpscaleModelLoaderExtension nodes
sam2DownloadAndLoadSAM2ModelmodelsamsSAMLoadermodel_nameultralyticsUltralyticsDetectorProvidermodel_namedepthanythingDownloadAndLoadDepthAnythingV2ModelmodelipadapterIPAdapterModelLoaderipadapter_filesegformer_b2_clothesLS_LoadSegformerModelmodel_namesegformer_b3_clothesLS_LoadSegformerModelmodel_namesegformer_b3_fashionLS_LoadSegformerModelmodel_namenlfLoadNLFModelnlf_modelFlashVSRFlashVSRNodeFlashVSR-v1.1FlashVSRNodeHierarchical fallback
ultralytics/bboxandultralytics/segmfall back to theultralyticsmappingSkipped categories
vae_approx- No user-facing loader (used internally for latent previews)detection- No specific loader existsTesting
┆Issue is synchronized with this Notion page by Unito