feat: add force search (ignore cache) toggle to batch search menu
Adds a checkbox in the resolver footer search menu that bypasses all search result caches for the duration of a batch search run. Frontend (queue_methods.js, resolve_download_methods.js, resolver_dialog.js, search_panel.js, resolver-main.css): - Introduce checkbox-type footer menu items (type: 'checkbox'). - Add 'Force search (ignore cache)' checkbox to the search menu, wired to a new forceBatchSearch flag on ResolverManagerDialog. - Pass forceSearch through searchMissingBatch and searchOnline to the backend API call. - When the manual search button is clicked and results already exist for that model, forceSearch defaults to true. - mergeSearchResults respects forceRefresh: if a forced search returns no results for a source, that source is cleared from the cached results. Backend (__init__.py, core/sources/huggingface.py): - force_search is read from the incoming API request and, when true, clears all active source search caches (local, huggingface, civitai, civarchive, lora_manager_archive) before searching. - force_search is forwarded as force_refresh to search_huggingface_for_file, which folds it into the cache key so the cache is not hit, and propagates it to _get_author_index. - Search result timestamps are no longer inherited from prior cached signatures when force_search is active; a fresh timestamp is written unconditionally. Updated metadata/huggingface-author-index.json reflects the latest cached author index after rebuild.
This commit is contained in:
+35
-5
@@ -949,9 +949,31 @@ class ModelResolverExtension:
|
||||
search_lora_manager_archive_source = (
|
||||
"lora_manager_archive" in normalized_sources
|
||||
)
|
||||
force_search = data.get("force_search", False)
|
||||
force_search = (
|
||||
force_search
|
||||
if isinstance(force_search, bool)
|
||||
else str(force_search).lower() == "true"
|
||||
)
|
||||
|
||||
if force_search:
|
||||
if search_local:
|
||||
reload_popular_databases()
|
||||
reload_model_list()
|
||||
if search_huggingface_source:
|
||||
clear_huggingface_search_cache()
|
||||
if search_civitai_source:
|
||||
clear_civitai_search_cache()
|
||||
if search_civarchive_source:
|
||||
clear_civarchive_search_cache()
|
||||
if search_lora_manager_archive_source:
|
||||
clear_lora_manager_archive_search_cache()
|
||||
log_info(
|
||||
f"Force search enabled: cleared cache for sources={sorted(normalized_sources)}"
|
||||
)
|
||||
|
||||
log_info(
|
||||
f"Search request: filename={filename}, category={category}, is_urn={is_urn}, model_id={data.get('model_id')}, version_id={data.get('version_id')}, sources={sorted(normalized_sources)}, civitai_candidate_limit={civitai_candidate_limit}, civarchive_candidate_limit={civarchive_candidate_limit}"
|
||||
f"Search request: filename={filename}, category={category}, is_urn={is_urn}, model_id={data.get('model_id')}, version_id={data.get('version_id')}, sources={sorted(normalized_sources)}, force_search={force_search}, civitai_candidate_limit={civitai_candidate_limit}, civarchive_candidate_limit={civarchive_candidate_limit}"
|
||||
)
|
||||
|
||||
results = {
|
||||
@@ -1013,13 +1035,20 @@ class ModelResolverExtension:
|
||||
timestamp = (
|
||||
result.get("searched_at")
|
||||
or result.get("searchedAt")
|
||||
or self.search_result_timestamps.get(signature)
|
||||
or (
|
||||
None
|
||||
if force_search
|
||||
else self.search_result_timestamps.get(signature)
|
||||
)
|
||||
)
|
||||
if not timestamp:
|
||||
timestamp = current_search_timestamp()
|
||||
self.search_result_timestamps.setdefault(
|
||||
signature, timestamp
|
||||
)
|
||||
if force_search:
|
||||
self.search_result_timestamps[signature] = timestamp
|
||||
else:
|
||||
self.search_result_timestamps.setdefault(
|
||||
signature, timestamp
|
||||
)
|
||||
result["searched_at"] = timestamp
|
||||
return result
|
||||
|
||||
@@ -1095,6 +1124,7 @@ class ModelResolverExtension:
|
||||
use_api_search=hf_use_api_search,
|
||||
use_comfy_org_fallback=hf_use_comfy_org_fallback,
|
||||
use_brave_fallback=hf_use_brave_fallback,
|
||||
force_refresh=force_search,
|
||||
)
|
||||
log_search_result("huggingface", hf_result)
|
||||
return {"huggingface": hf_result}, bool(hf_result)
|
||||
|
||||
@@ -668,6 +668,7 @@ def search_huggingface_for_file(
|
||||
use_api_search: bool = True,
|
||||
use_comfy_org_fallback: bool = True,
|
||||
use_brave_fallback: bool = True,
|
||||
force_refresh: bool = False,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Search HuggingFace for a specific model file.
|
||||
@@ -689,7 +690,8 @@ def search_huggingface_for_file(
|
||||
methods_key = (
|
||||
f"api{int(bool(use_api_search))}_"
|
||||
f"comfy{int(bool(use_comfy_org_fallback))}_"
|
||||
f"brave{int(bool(use_brave_fallback))}"
|
||||
f"brave{int(bool(use_brave_fallback))}_"
|
||||
f"force{int(bool(force_refresh))}"
|
||||
)
|
||||
cache_key = f"hf_{filename}_exact{exact_only}_{token_key}_{brave_key}_{methods_key}"
|
||||
if cache_key in _search_cache:
|
||||
@@ -751,7 +753,11 @@ def search_huggingface_for_file(
|
||||
author_fallback_repo_count = 0
|
||||
if use_comfy_org_fallback:
|
||||
for author in HF_AUTHOR_FALLBACKS:
|
||||
index = _get_author_index(author, headers={})
|
||||
index = _get_author_index(
|
||||
author,
|
||||
headers={},
|
||||
force_refresh=force_refresh,
|
||||
)
|
||||
if index:
|
||||
result = _find_matching_file_in_author_index(
|
||||
index, filename, exact_only=exact_only
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2470,6 +2470,20 @@
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.mr-footer-menu-checkbox,
|
||||
#model-resolver-modal .mr-footer-menu .mr-footer-menu-checkbox {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mr-footer-menu-checkbox-input {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
margin: 0;
|
||||
accent-color: #72d38a;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.mr-footer-menu-item:hover,
|
||||
.mr-footer-menu-item:focus-visible {
|
||||
outline: none;
|
||||
|
||||
@@ -685,6 +685,25 @@ export const queueMethods = {
|
||||
let element;
|
||||
if (isDivider) {
|
||||
element = $el("div.mr-footer-menu-divider", {});
|
||||
} else if (item?.type === 'checkbox') {
|
||||
const input = $el("input.mr-footer-menu-checkbox-input", {
|
||||
type: 'checkbox',
|
||||
checked: Boolean(item.checked?.())
|
||||
});
|
||||
element = $el("label.mr-footer-menu-item.mr-footer-menu-checkbox", {
|
||||
role: 'menuitemcheckbox',
|
||||
'aria-checked': input.checked ? 'true' : 'false',
|
||||
onclick: (event) => {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}, [
|
||||
input,
|
||||
$el("span", { textContent: item.label })
|
||||
]);
|
||||
input.addEventListener('change', () => {
|
||||
item.action?.(input.checked);
|
||||
element.setAttribute('aria-checked', input.checked ? 'true' : 'false');
|
||||
});
|
||||
} else {
|
||||
const attributes = {
|
||||
type: 'button',
|
||||
@@ -810,18 +829,29 @@ export const queueMethods = {
|
||||
this.selectMenuWrap = selectMenu;
|
||||
|
||||
const hasSelectedMissingModels = () => this.getSelectedMissingModels().length > 0;
|
||||
const searchBatchOptions = () => ({ forceSearch: Boolean(this.forceBatchSearch) });
|
||||
const searchMenu = this.createFooterMenu('search', 'Search', [
|
||||
{ label: 'Stop Searching', className: 'mr-footer-menu-danger', visibleWhen: () => this.batchSearchRunning, action: () => this.stopBatchSearch() },
|
||||
{ type: 'divider', visibleWhen: () => this.batchSearchRunning },
|
||||
{ label: 'Search Selected', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'all') },
|
||||
{ label: 'Search All Missing', visibleWhen: () => !this.batchSearchRunning, previewModels: () => this.missingModels || [], action: () => this.searchMissingBatch('all', 'all') },
|
||||
{ label: 'Search Unsearched', visibleWhen: () => !this.batchSearchRunning, previewModels: () => this.getUnsearchedMissingModels(), action: () => this.searchMissingBatch('unsearched', 'all') },
|
||||
{
|
||||
type: 'checkbox',
|
||||
label: 'Force search (ignore cache)',
|
||||
visibleWhen: () => !this.batchSearchRunning,
|
||||
checked: () => Boolean(this.forceBatchSearch),
|
||||
action: (checked) => {
|
||||
this.forceBatchSearch = Boolean(checked);
|
||||
}
|
||||
},
|
||||
{ type: 'divider', visibleWhen: () => !this.batchSearchRunning },
|
||||
{ label: 'Selected: Local Database', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'local') },
|
||||
{ label: 'Selected: CivitAI', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'civitai') },
|
||||
{ label: 'Selected: HuggingFace', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'huggingface') },
|
||||
{ label: 'Selected: CivArchive', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'civarchive') },
|
||||
{ label: 'Selected: LoRA Manager Archive', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'lora_manager_archive') }
|
||||
{ label: 'Search Selected', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'all', searchBatchOptions()) },
|
||||
{ label: 'Search All Missing', visibleWhen: () => !this.batchSearchRunning, previewModels: () => this.missingModels || [], action: () => this.searchMissingBatch('all', 'all', searchBatchOptions()) },
|
||||
{ label: 'Search Unsearched', visibleWhen: () => !this.batchSearchRunning, previewModels: () => this.getUnsearchedMissingModels(), action: () => this.searchMissingBatch('unsearched', 'all', searchBatchOptions()) },
|
||||
{ type: 'divider', visibleWhen: () => !this.batchSearchRunning },
|
||||
{ label: 'Selected: Local Database', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'local', searchBatchOptions()) },
|
||||
{ label: 'Selected: CivitAI', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'civitai', searchBatchOptions()) },
|
||||
{ label: 'Selected: HuggingFace', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'huggingface', searchBatchOptions()) },
|
||||
{ label: 'Selected: CivArchive', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'civarchive', searchBatchOptions()) },
|
||||
{ label: 'Selected: LoRA Manager Archive', visibleWhen: () => !this.batchSearchRunning, disabledWhen: () => !hasSelectedMissingModels(), previewModels: () => this.getSelectedMissingModels(), action: () => this.searchMissingBatch('selected', 'lora_manager_archive', searchBatchOptions()) }
|
||||
]);
|
||||
this.searchMenuButton = this.footerMenuButtons.get('search');
|
||||
this.searchMenuWrap = searchMenu;
|
||||
|
||||
@@ -323,7 +323,7 @@ export const resolveDownloadMethods = {
|
||||
return missingModels;
|
||||
},
|
||||
|
||||
async searchMissingBatch(mode = 'selected', source = 'all') {
|
||||
async searchMissingBatch(mode = 'selected', source = 'all', { forceSearch = false } = {}) {
|
||||
if (this.batchSearchRunning) {
|
||||
this.stopBatchSearch();
|
||||
return;
|
||||
@@ -353,7 +353,7 @@ export const resolveDownloadMethods = {
|
||||
const state = this.getSearchStateForWorkflow(batchWorkflowKey, missing);
|
||||
state.selectedSource = source || 'all';
|
||||
try {
|
||||
await this.searchOnline(missing, { workflowKey: batchWorkflowKey });
|
||||
await this.searchOnline(missing, { workflowKey: batchWorkflowKey, forceSearch });
|
||||
} catch (error) {
|
||||
failed += 1;
|
||||
console.error('Model Resolver: batch search item failed:', error);
|
||||
@@ -778,7 +778,7 @@ export const resolveDownloadMethods = {
|
||||
/**
|
||||
* Search online for a model
|
||||
*/
|
||||
async searchOnline(missing, { workflowKey = this.getWorkflowScopedQueueKey() } = {}) {
|
||||
async searchOnline(missing, { workflowKey = this.getWorkflowScopedQueueKey(), forceSearch = false } = {}) {
|
||||
let filename = missing.original_path?.split('/').pop()?.split('\\').pop() || '';
|
||||
let category = missing.category || '';
|
||||
const state = this.getSearchStateForWorkflow(workflowKey, missing);
|
||||
@@ -903,7 +903,8 @@ export const resolveDownloadMethods = {
|
||||
brave_search_api_key: tokens.brave_search_api_key,
|
||||
hf_use_api_search: tokens.hf_use_api_search,
|
||||
hf_use_comfy_org_fallback: tokens.hf_use_comfy_org_fallback,
|
||||
hf_use_brave_fallback: tokens.hf_use_brave_fallback
|
||||
hf_use_brave_fallback: tokens.hf_use_brave_fallback,
|
||||
force_search: Boolean(forceSearch)
|
||||
};
|
||||
if (isUrn && missing.urn) {
|
||||
baseSearchData.model_id = missing.urn.model_id;
|
||||
@@ -950,7 +951,8 @@ export const resolveDownloadMethods = {
|
||||
const found = this.hasSearchResults(data);
|
||||
anyFound = anyFound || found;
|
||||
state.results = this.mergeSearchResults(state.results, data, {
|
||||
searchedAt: new Date().toISOString()
|
||||
searchedAt: new Date().toISOString(),
|
||||
forceRefresh: Boolean(forceSearch)
|
||||
});
|
||||
state.lastAttemptSources = Array.from(attemptedSources);
|
||||
state.lastAttemptFound = anyFound;
|
||||
@@ -1193,7 +1195,9 @@ export const resolveDownloadMethods = {
|
||||
if (searchBtn && searchBtn.dataset.mlSearchBound !== 'true') {
|
||||
searchBtn.dataset.mlSearchBound = 'true';
|
||||
searchBtn.addEventListener('click', () => {
|
||||
this.searchOnline(missing);
|
||||
this.searchOnline(missing, {
|
||||
forceSearch: this.hasSearchResultsForMissing(missing)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ export class ResolverManagerDialog extends ComfyDialog {
|
||||
this.activeFooterMenu = null;
|
||||
this.batchSearchRunning = false;
|
||||
this.batchSearchCancelRequested = false;
|
||||
this.forceBatchSearch = false;
|
||||
this.boundHandleOutsideClick = this.handleOutsideClick.bind(this);
|
||||
this.activeTabStorageKey = 'model_resolver_active_tab';
|
||||
this.activeTab = this.restoreActiveTab(); // Default tab
|
||||
|
||||
@@ -135,13 +135,14 @@ export const searchPanelMethods = {
|
||||
|
||||
/**
|
||||
* Merge new search results into cached per-source results.
|
||||
* Empty responses do not delete previously found results.
|
||||
* Empty normal responses do not delete previous results; forced refreshes do.
|
||||
*/
|
||||
mergeSearchResults(existingResults = {}, newResults = {}, { searchedAt = null } = {}) {
|
||||
mergeSearchResults(existingResults = {}, newResults = {}, { searchedAt = null, forceRefresh = false } = {}) {
|
||||
const searchedSources = new Set(Array.isArray(newResults.searched_sources) ? newResults.searched_sources : []);
|
||||
const pickResult = (source) => {
|
||||
if (newResults[source]) {
|
||||
const existingTimestamp = this.getSearchResultTimestamp(existingResults[source]);
|
||||
const resultTimestamp = this.areSearchResultsSame(existingResults[source], newResults[source])
|
||||
const resultTimestamp = !forceRefresh && this.areSearchResultsSame(existingResults[source], newResults[source])
|
||||
? existingTimestamp
|
||||
: null;
|
||||
return this.withSearchResultTimestamp(
|
||||
@@ -149,6 +150,11 @@ export const searchPanelMethods = {
|
||||
resultTimestamp || searchedAt
|
||||
);
|
||||
}
|
||||
const sourceWasSearched = searchedSources.has(source)
|
||||
|| (searchedSources.has('local') && (source === 'popular' || source === 'model_list'));
|
||||
if (forceRefresh && sourceWasSearched) {
|
||||
return null;
|
||||
}
|
||||
return existingResults[source] || null;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user