From 044d4de76a747334656aeeca9bd777863b0a3c85 Mon Sep 17 00:00:00 2001 From: Deep Mehta Date: Wed, 18 Mar 2026 09:35:17 -0700 Subject: [PATCH] fix: progressive hierarchical fallback and HF-download node dropdowns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Fix findProvidersWithFallback to try all intermediate parent paths (a/b/c → a/b → a) instead of jumping to top-level only. This ensures "Use" on CogVideo/ControlNet/* models creates the correct DownloadAndLoadCogVideoControlNet node. 2. Use empty key for DownloadAndLoadCogVideoControlNet and DownloadAndLoadCogVideoModel so the asset browser doesn't hijack the combo widget — these nodes use HF repo names, not file assets. 3. Add comprehensive tests for 1-4 level nested path fallback. --- src/stores/modelToNodeStore.test.ts | 65 +++++++++++++++++++++++++++++ src/stores/modelToNodeStore.ts | 22 +++++----- 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/stores/modelToNodeStore.test.ts b/src/stores/modelToNodeStore.test.ts index 66dd5b3635..563b0fa00b 100644 --- a/src/stores/modelToNodeStore.test.ts +++ b/src/stores/modelToNodeStore.test.ts @@ -178,6 +178,71 @@ describe('useModelToNodeStore', () => { ).toBeUndefined() }) + describe('progressive hierarchical fallback', () => { + it('should resolve 1-level path via exact match', () => { + const modelToNodeStore = useModelToNodeStore() + modelToNodeStore.quickRegister('level1', 'UNETLoader', 'key1') + + const provider = modelToNodeStore.getNodeProvider('level1') + expect(provider?.nodeDef?.name).toBe('UNETLoader') + }) + + it('should resolve 2-level path to registered parent', () => { + const modelToNodeStore = useModelToNodeStore() + modelToNodeStore.quickRegister('level1', 'UNETLoader', 'key1') + + const provider = modelToNodeStore.getNodeProvider('level1/child') + expect(provider?.nodeDef?.name).toBe('UNETLoader') + }) + + it('should resolve 3-level path to nearest registered ancestor', () => { + const modelToNodeStore = useModelToNodeStore() + modelToNodeStore.quickRegister('level1', 'UNETLoader', 'key1') + modelToNodeStore.quickRegister('level1/level2', 'VAELoader', 'key2') + + // 3 levels: should match level1/level2 (nearest), not level1 + const provider = modelToNodeStore.getNodeProvider('level1/level2/child') + expect(provider?.nodeDef?.name).toBe('VAELoader') + }) + + it('should resolve 4-level path to nearest registered ancestor', () => { + const modelToNodeStore = useModelToNodeStore() + modelToNodeStore.quickRegister('a', 'UNETLoader', 'k1') + modelToNodeStore.quickRegister('a/b', 'VAELoader', 'k2') + modelToNodeStore.quickRegister('a/b/c', 'StyleModelLoader', 'k3') + + // 4 levels: should match a/b/c (nearest), not a/b or a + const provider = modelToNodeStore.getNodeProvider('a/b/c/d') + expect(provider?.nodeDef?.name).toBe('StyleModelLoader') + }) + + it('should skip intermediate unregistered levels', () => { + const modelToNodeStore = useModelToNodeStore() + modelToNodeStore.quickRegister('a', 'UNETLoader', 'k1') + // a/b is NOT registered + + // 3 levels: a/b not found, falls back to a + const provider = modelToNodeStore.getNodeProvider('a/b/c') + expect(provider?.nodeDef?.name).toBe('UNETLoader') + }) + + it('should prefer exact match over any fallback', () => { + const modelToNodeStore = useModelToNodeStore() + modelToNodeStore.quickRegister('a', 'UNETLoader', 'k1') + modelToNodeStore.quickRegister('a/b/c', 'VAELoader', 'k2') + + const provider = modelToNodeStore.getNodeProvider('a/b/c') + expect(provider?.nodeDef?.name).toBe('VAELoader') + }) + + it('should return undefined when no ancestor is registered', () => { + const modelToNodeStore = useModelToNodeStore() + modelToNodeStore.quickRegister('x', 'UNETLoader', 'k1') + + expect(modelToNodeStore.getNodeProvider('y/z/w')).toBeUndefined() + }) + }) + it('should return provider for chatterbox nodes with empty key', () => { const modelToNodeStore = useModelToNodeStore() modelToNodeStore.registerDefaults() diff --git a/src/stores/modelToNodeStore.ts b/src/stores/modelToNodeStore.ts index e570d5de2c..b2a000617d 100644 --- a/src/stores/modelToNodeStore.ts +++ b/src/stores/modelToNodeStore.ts @@ -75,8 +75,8 @@ export const useModelToNodeStore = defineStore('modelToNode', () => { /** * 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". + * Tries exact match first, then progressively shorter parent paths. + * e.g., "a/b/c" tries "a/b/c" → "a/b" → "a". */ function findProvidersWithFallback( modelType: string @@ -88,12 +88,12 @@ export const useModelToNodeStore = defineStore('modelToNode', () => { 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 + const segments = modelType.split('/') + for (let i = segments.length - 1; i >= 1; i--) { + const parentPath = segments.slice(0, i).join('/') + const fallback = modelToNodeMap.value[parentPath] + if (fallback && fallback.length > 0) return fallback + } return undefined } @@ -361,10 +361,11 @@ export const useModelToNodeStore = defineStore('modelToNode', () => { // CogVideoX models (comfyui-cogvideoxwrapper) quickRegister('CogVideo/GGUF', 'DownloadAndLoadCogVideoGGUFModel', 'model') + // Empty key: HF-download node — don't activate asset browser for the combo widget quickRegister( 'CogVideo/ControlNet', 'DownloadAndLoadCogVideoControlNet', - 'model' + '' ) // DynamiCrafter models (ComfyUI-DynamiCrafterWrapper) @@ -386,7 +387,8 @@ export const useModelToNodeStore = defineStore('modelToNode', () => { quickRegister('lama', 'LaMa', 'lama_model') // CogVideoX video generation models (comfyui-cogvideoxwrapper) - quickRegister('CogVideo', 'DownloadAndLoadCogVideoModel', 'model') + // Empty key: HF-download node — don't activate asset browser for the combo widget + quickRegister('CogVideo', 'DownloadAndLoadCogVideoModel', '') // Inpaint models (comfyui-inpaint-nodes) quickRegister('inpaint', 'INPAINT_LoadInpaintModel', 'model_name')