From e4485f3bdf18506e7730f82aba43fc85f4104620 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Thu, 16 Jul 2026 09:10:00 +0200 Subject: [PATCH] Treat SAM and Ultralytics models as base-model-independent in search panel --- tests/test_downloads_tab_workflow_route.mjs | 42 +++++++++++++++++ web/resolver/search/search_panel.js | 51 +++++++++++++++++++-- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/tests/test_downloads_tab_workflow_route.mjs b/tests/test_downloads_tab_workflow_route.mjs index 3d8fd03..08adf93 100644 --- a/tests/test_downloads_tab_workflow_route.mjs +++ b/tests/test_downloads_tab_workflow_route.mjs @@ -148,6 +148,48 @@ test('base model alias resolves FLUX KREA as Flux.1 Krea', () => { assert.equal(resolveBaseModelAlias.call(dialog, 'KREA'), 'Krea 2'); }); +test('auto base model uses Any model for standalone SAM and Ultralytics models', () => { + const getBaseModelIndependentSearchType = eval(`(${extractMethod(searchPanelMethodsSource, 'getBaseModelIndependentSearchType')})`); + const getMissingAutoBaseModelInfo = eval(`(${extractMethod(searchPanelMethodsSource, 'getMissingAutoBaseModelInfo')})`); + const getMissingAutoBaseModel = eval(`(${extractMethod(searchPanelMethodsSource, 'getMissingAutoBaseModel')})`); + const getSearchBaseModelLabel = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchBaseModelLabel')})`); + const getSearchBaseModelContext = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchBaseModelContext')})`); + const state = { selectedBaseModel: 'auto' }; + const dialog = { + getBaseModelIndependentSearchType, + getMissingAutoBaseModelInfo, + getMissingAutoBaseModel, + getSearchBaseModelLabel, + getSearchBaseModelContext, + getSearchState() { + return state; + }, + getDefaultSearchBaseModel() { + return 'auto'; + }, + getDominantWorkflowBaseModel() { + return 'SDXL 1.0'; + } + }; + + for (const missing of [ + { category: 'sams', node_type: 'SAMLoader' }, + { category: 'ultralytics', node_type: 'UltralyticsDetectorProvider' } + ]) { + assert.equal(getMissingAutoBaseModel.call(dialog, missing), ''); + assert.equal(getSearchBaseModelLabel.call(dialog, 'auto', missing), 'Auto (Any model)'); + assert.equal(getSearchBaseModelContext.call(dialog, missing), ''); + assert.match(getMissingAutoBaseModelInfo.call(dialog, missing).message, /Auto uses Any model/); + } + + state.selectedBaseModel = 'SDXL 1.0'; + assert.equal( + getSearchBaseModelContext.call(dialog, { category: 'sams' }), + 'SDXL 1.0', + 'Manual base model selection should remain available' + ); +}); + test('base model path mapping ignores conflicting full-path base model', () => { const normalizeBaseModelToken = eval(`(${extractMethod(searchPanelMethodsSource, 'normalizeBaseModelToken')})`); const getBaseModelTokenVariants = eval(`(${extractMethod(searchPanelMethodsSource, 'getBaseModelTokenVariants')})`); diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 43911fb..e291399 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -188,7 +188,52 @@ export const searchPanelMethods = { return this.getMissingAutoBaseModelInfo(missing).value; }, + getBaseModelIndependentSearchType(missing = {}) { + const normalizeType = (value) => String(value || '') + .trim() + .replace(/([a-z0-9])([A-Z])/g, '$1_$2') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_+|_+$/g, ''); + const typeLabels = { + sam: 'SAM', + sams: 'SAM', + sam_model: 'SAM', + sam_models: 'SAM', + ultralytics: 'Ultralytics', + ultralytics_bbox: 'Ultralytics', + ultralytics_segm: 'Ultralytics', + yolo: 'Ultralytics' + }; + const candidates = [ + missing?.category, + missing?.model_type, + missing?.urn_type + ]; + for (const candidate of candidates) { + const label = typeLabels[normalizeType(candidate)]; + if (label) return label; + } + + const nodeType = normalizeType(missing?.node_type); + if (nodeType === 'samloader' || nodeType === 'sam_loader') return 'SAM'; + if (nodeType === 'ultralyticsdetectorprovider' || nodeType === 'ultralytics_detector_provider') { + return 'Ultralytics'; + } + return ''; + }, + getMissingAutoBaseModelInfo(missing = {}) { + const independentType = this.getBaseModelIndependentSearchType(missing); + if (independentType) { + return { + value: '', + source: 'independent model type', + usesAnyModel: true, + message: `${independentType} models are not tied to a base model, so Auto uses Any model.` + }; + } + const searchSuggestion = this.getCachedSearchSuggestionData?.(missing) || {}; const directCandidates = [ { value: missing?.civitai_info?.base_model, source: 'model metadata' }, @@ -408,9 +453,9 @@ export const searchPanelMethods = { getSearchBaseModelLabel(value = 'auto', missing = {}) { if (value === 'auto') { - const detected = missing - ? this.getMissingAutoBaseModel(missing) - : this.getDominantWorkflowBaseModel(); + const autoInfo = missing ? this.getMissingAutoBaseModelInfo(missing) : null; + if (autoInfo?.usesAnyModel) return 'Auto (Any model)'; + const detected = autoInfo ? autoInfo.value : this.getDominantWorkflowBaseModel(); return detected ? `Auto (${detected})` : 'Auto'; } const option = this.getKnownBaseModelOptions().find(item => item.value === value);