From 1a4156f00db1e33630e61070a23b006dcdfabeee Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sat, 20 Jun 2026 00:53:51 +0200 Subject: [PATCH] feat: add any-model fallback search for civitai, civarchive, and lora_manager_archive When a model search returns no results for a specific base_model, retry the same query with base_model_context=None so the provider can match any model. Fallback matches are marked with any_model_match and base_model_fallback on the result payload. The UI reports 'Found (Any Model)' when a result came from this fallback path, and the cancel/progress button styling is tightened to 16x16px with border-radius. --- __init__.py | 90 +++++++++++++++++++ web/css/resolver-main.css | 11 +++ .../actions/resolve_download_methods.js | 4 +- web/resolver/search/search_panel.js | 7 ++ 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 1daa90a..5d408b8 100644 --- a/__init__.py +++ b/__init__.py @@ -1337,6 +1337,21 @@ class ModelResolverExtension: ) return payload + def mark_any_model_fallback(result): + if isinstance(result, list): + return [ + mark_any_model_fallback(item) + for item in result + ] + if not isinstance(result, dict): + return result + + marked = dict(result) + marked["any_model_match"] = True + marked["base_model_fallback"] = True + marked["requested_base_model"] = base_model_context + return marked + def search_local_sources(): source_results = {"popular": None, "model_list": None} source_found = False @@ -1521,6 +1536,32 @@ class ModelResolverExtension: use_html_fallback=civitai_use_html_fallback, ) log_search_result("civitai", civitai_result) + if not civitai_result and base_model_context: + log_info( + "Search [civitai] retry any model " + + format_log_fields( + file=filename, + cat=category, + base=base_model_context, + ) + ) + civitai_result = search_civitai_for_file( + filename, + model_type=category, + base_model_context=None, + session_token=civitai_session_token or None, + candidate_limit=civitai_candidate_limit, + use_trpc_search=civitai_use_trpc_search, + use_html_fallback=civitai_use_html_fallback, + ) + log_search_result( + "civitai/any_model", + civitai_result, + ) + if civitai_result: + civitai_result = mark_any_model_fallback( + civitai_result + ) if civitai_result: source_results["civitai"] = civitai_result source_found = True @@ -1577,6 +1618,29 @@ class ModelResolverExtension: limit=civarchive_candidate_limit, ) log_search_result("civarchive", civarchive_result) + if not civarchive_result and base_model_context: + log_info( + "Search [civarchive] retry any model " + + format_log_fields( + file=filename, + cat=category, + base=base_model_context, + ) + ) + civarchive_result = search_civarchive_for_file( + filename, + model_type=category, + base_model_context=None, + limit=civarchive_candidate_limit, + ) + log_search_result( + "civarchive/any_model", + civarchive_result, + ) + if civarchive_result: + civarchive_result = mark_any_model_fallback( + civarchive_result + ) if civarchive_result: source_results["civarchive"] = civarchive_result source_found = True @@ -1605,6 +1669,32 @@ class ModelResolverExtension: "lora_manager_archive", lora_manager_archive_result, ) + if not lora_manager_archive_result and base_model_context: + log_info( + "Search [lora_manager_archive] retry any model " + + format_log_fields( + file=filename, + cat=category, + base=base_model_context, + ) + ) + lora_manager_archive_result = ( + search_lora_manager_archive_for_file( + filename, + model_type=category, + base_model_context=None, + ) + ) + log_search_result( + "lora_manager_archive/any_model", + lora_manager_archive_result, + ) + if lora_manager_archive_result: + lora_manager_archive_result = ( + mark_any_model_fallback( + lora_manager_archive_result + ) + ) return ( { "lora_manager_archive": lora_manager_archive_result diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css index 37d41d9..53b4b5f 100644 --- a/web/css/resolver-main.css +++ b/web/css/resolver-main.css @@ -2237,6 +2237,14 @@ } #model-resolver-modal .mr-search-result-action-btn.mr-search-progress-cancel, #model-resolver-modal .mr-missing-detail-pane .mr-search-result-action-btn.mr-search-progress-cancel { + width: 16px; + height: 16px; + min-width: 16px; + min-height: 16px; + max-width: 16px; + max-height: 16px; + flex: 0 0 16px; + border-radius: 5px; border-color: rgba(255, 120, 112, 0.28); background: rgba(255, 120, 112, 0.055); color: #ff8a80; @@ -2249,6 +2257,9 @@ } #model-resolver-modal .mr-search-result-action-btn.mr-search-progress-cancel svg, #model-resolver-modal .mr-missing-detail-pane .mr-search-result-action-btn.mr-search-progress-cancel svg { + width: 9px; + height: 9px; + flex: 0 0 9px; color: currentColor; stroke: currentColor; } diff --git a/web/resolver/actions/resolve_download_methods.js b/web/resolver/actions/resolve_download_methods.js index 278d50c..cadde49 100644 --- a/web/resolver/actions/resolve_download_methods.js +++ b/web/resolver/actions/resolve_download_methods.js @@ -1623,6 +1623,8 @@ export const resolveDownloadMethods = { || null; const found = this.hasSearchResults(data); + const foundViaAnyModel = this.isAnyModelSearchResult?.(data[source]); + const foundMessage = foundViaAnyModel ? 'Found (Any Model)' : 'Found'; anyFound = anyFound || found; hadError = hadError || Boolean(sourceError); state.results = this.mergeSearchResults(state.results, data, { @@ -1635,7 +1637,7 @@ export const resolveDownloadMethods = { this.setSourceProgress(state, source, { status: sourceError ? 'error' : (found ? 'found' : 'none'), percent: 100, - message: sourceError ? 'Error' : (found ? 'Found' : 'No match'), + message: sourceError ? 'Error' : (found ? foundMessage : 'No match'), error: sourceError || null }, missing, { workflowKey }); diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 5bf4ec8..db7554f 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -556,6 +556,13 @@ export const searchPanelMethods = { return !!(data.popular || data.model_list || data.huggingface || data.civitai || data.civarchive || data.lora_manager_archive); }, + isAnyModelSearchResult(result) { + if (Array.isArray(result)) { + return result.some(item => this.isAnyModelSearchResult(item)); + } + return Boolean(result && typeof result === 'object' && (result.any_model_match || result.base_model_fallback)); + }, + hasSearchResultsForMissing(missing = {}) { if (missing?.download_source?.url) return true; const state = this.searchResultCache?.get(this.getMissingSearchKey(missing));