Files

230 lines
8.9 KiB
JavaScript

export const searchStateMethods = {
/**
* Build stable cache key for a missing model entry
*/
getMissingSearchKey(missing) {
if (missing?.missing_search_key) {
return String(missing.missing_search_key);
}
if (missing?.search_key) {
return String(missing.search_key);
}
return this.getMissingModelKey(missing);
},
/**
* Get or initialize search state for a missing model entry
*/
getSearchState(missing) {
const key = this.getMissingSearchKey(missing);
if (!this.searchResultCache.has(key)) {
this.searchResultCache.set(key, this.createEmptySearchState());
}
return this.searchResultCache.get(key);
},
createEmptySearchState() {
return {
selectedSource: 'all',
selectedBaseModel: this.getDefaultSearchBaseModel(),
inputMode: 'link',
manualSearchQuery: '',
results: {
popular: null,
model_list: null,
huggingface: null,
civitai: null,
civarchive: null,
lora_manager_archive: null,
custom: [],
local_hash_matches: []
},
explicitSearchSources: [],
lastAttemptSources: [],
lastAttemptBaseModelContext: '',
lastAttemptFound: null,
lastAttemptError: null,
sourceProgress: {},
activeSearchRunId: null
};
},
getBackgroundSearchJobKey(workflowKey, missingSearchKey) {
return `${workflowKey || 'workflow'}\n${missingSearchKey || 'missing'}`;
},
getBackgroundSearchJob(workflowKey, missingSearchKey) {
return this.backgroundSearchJobs?.get(
this.getBackgroundSearchJobKey(workflowKey, missingSearchKey)
) || null;
},
hasBackgroundSearchJob(workflowKey, missingSearchKey, runId = null) {
const job = this.getBackgroundSearchJob(workflowKey, missingSearchKey);
if (!job) return false;
return !runId || job.runId === runId;
},
isBackgroundSearchRunActive(workflowKey, missingSearchKey, runId) {
return this.hasBackgroundSearchJob(workflowKey, missingSearchKey, runId);
},
isSearchSourceCancelled(workflowKey, missingSearchKey, runId, source) {
const job = this.getBackgroundSearchJob(workflowKey, missingSearchKey);
return Boolean(job?.runId === runId && job.cancelledSources?.has(source));
},
getWorkflowSearchCache(workflowKey, { create = false } = {}) {
if (!workflowKey) return null;
let cache = this.workflowSearchResultCaches.get(workflowKey);
if (!cache && create) {
cache = new Map();
this.workflowSearchResultCaches.set(workflowKey, cache);
}
return cache || null;
},
getSearchStateForWorkflow(workflowKey, missing) {
if (!workflowKey || workflowKey === this.getWorkflowScopedQueueKey()) {
return this.getSearchState(missing);
}
const missingSearchKey = this.getMissingSearchKey(missing);
const cache = this.getWorkflowSearchCache(workflowKey, { create: true });
if (!cache.has(missingSearchKey)) {
cache.set(missingSearchKey, this.createEmptySearchState());
}
return cache.get(missingSearchKey);
},
persistSearchStateForWorkflow(workflowKey, missing, state) {
const missingSearchKey = this.getMissingSearchKey(missing);
if (!workflowKey || !missingSearchKey || !state) return;
const cache = this.getWorkflowSearchCache(workflowKey, { create: true });
cache.set(
missingSearchKey,
this.cloneSearchState(state, {
preserveActive: this.hasBackgroundSearchJob(
workflowKey,
missingSearchKey,
state.activeSearchRunId
)
})
);
if (workflowKey === this.getWorkflowScopedQueueKey()) {
this.searchResultCache.set(missingSearchKey, state);
return;
}
const activeWorkflowKey = this.getWorkflowScopedQueueKey();
const activeState = this.searchResultCache?.get(missingSearchKey);
const runId = state.activeSearchRunId || activeState?.activeSearchRunId;
const mirrorsActiveSearch = Boolean(
activeState === state
|| (
runId
&& activeState?.activeSearchRunId === runId
&& this.hasBackgroundSearchJob(activeWorkflowKey, missingSearchKey, runId)
)
);
if (mirrorsActiveSearch) {
this.searchResultCache.set(missingSearchKey, state);
this.saveSearchCacheForActiveWorkflow?.();
}
},
/**
* Merge new search results into cached per-source results.
* Empty normal responses do not delete previous results; forced refreshes do.
*/
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 = !forceRefresh && this.areSearchResultsSame(existingResults[source], newResults[source])
? existingTimestamp
: null;
return this.withSearchResultTimestamp(
newResults[source],
resultTimestamp || searchedAt
);
}
const sourceWasSearched = searchedSources.has(source)
|| (searchedSources.has('local') && (source === 'popular' || source === 'model_list'));
if (forceRefresh && sourceWasSearched) {
return null;
}
return existingResults[source] || null;
};
const hashSourcesToClear = forceRefresh
? this.getHashLookupSourcesForSearchSources(Array.from(searchedSources))
: new Set();
const existingHashMatches = Array.isArray(existingResults.local_hash_matches)
? existingResults.local_hash_matches.filter(match => {
if (!hashSourcesToClear.size) return true;
const source = String(match?.hash_lookup_source || '').trim();
return source && !hashSourcesToClear.has(source);
})
: [];
const localHashMatches = this.mergeLocalMatches
? this.mergeLocalMatches(
existingHashMatches,
Array.isArray(newResults.local_hash_matches) ? newResults.local_hash_matches : []
)
: [
...existingHashMatches,
...(Array.isArray(newResults.local_hash_matches) ? newResults.local_hash_matches : [])
];
return {
popular: pickResult('popular'),
model_list: pickResult('model_list'),
huggingface: pickResult('huggingface'),
civitai: pickResult('civitai'),
civarchive: pickResult('civarchive'),
lora_manager_archive: pickResult('lora_manager_archive'),
custom: Array.isArray(newResults.custom)
? newResults.custom
: (Array.isArray(existingResults.custom) ? existingResults.custom : []),
local_hash_matches: localHashMatches
};
},
getSearchResultSignature(result) {
if (Array.isArray(result)) {
return result.map(item => this.getSearchResultSignature(item)).join('|');
}
if (!result || typeof result !== 'object') return '';
return [
result.download_url || result.url || result.model_url || '',
result.filename || result.path || '',
result.repo_id || result.repo || '',
result.model_id || '',
result.version_id || '',
result.name || ''
].map(value => String(value || '').trim()).join('::');
},
areSearchResultsSame(previousResult, nextResult) {
const previousSignature = this.getSearchResultSignature(previousResult);
const nextSignature = this.getSearchResultSignature(nextResult);
return Boolean(previousSignature && nextSignature && previousSignature === nextSignature);
},
withSearchResultTimestamp(result, searchedAt = null) {
if (!result || !searchedAt) return result;
if (Array.isArray(result)) {
return result.map(item => this.withSearchResultTimestamp(item, searchedAt));
}
if (typeof result !== 'object') return result;
return {
...result,
searchedAt: result.searchedAt || result.searched_at || searchedAt
};
},
};