track explicit search sources and gate local database download visibility

This commit is contained in:
Dariusz L
2026-07-22 13:25:33 +02:00
parent a822ef09c3
commit 854cb2c08c
5 changed files with 110 additions and 50 deletions
@@ -43,6 +43,80 @@ const renderFormatMethodsSource = fs.readFileSync(
'utf8'
);
test('Local Database source ignores installed local model matches before search', () => {
const hasMissingSourceSearchAttempt = eval(`(${extractMethod(missingBrowserMethodsSource, 'hasMissingSourceSearchAttempt')})`);
const getMissingSourceResultStatus = eval(`(${extractMethod(missingBrowserMethodsSource, 'getMissingSourceResultStatus')})`);
const dialog = {
hasMissingSourceSearchAttempt,
getSearchResultStatusLevel: () => 'exact'
};
const missing = {
matches: [{ confidence: 100, path: 'models/checkpoints/already-installed.safetensors' }],
download_source: {
source: 'model_list',
url: 'https://example.invalid/model.safetensors'
}
};
assert.equal(
getMissingSourceResultStatus.call(dialog, missing, 'local', {
results: { model_list: null, popular: null },
explicitSearchSources: [],
lastAttemptSources: ['local'],
sourceProgress: { local: { status: 'found' } }
}),
''
);
});
test('Local Database source reports its result after Local Database or Everything search', () => {
const hasMissingSourceSearchAttempt = eval(`(${extractMethod(missingBrowserMethodsSource, 'hasMissingSourceSearchAttempt')})`);
const getMissingSourceResultStatus = eval(`(${extractMethod(missingBrowserMethodsSource, 'getMissingSourceResultStatus')})`);
const getSearchResultStatusLevel = eval(`(${extractMethod(missingBrowserMethodsSource, 'getSearchResultStatusLevel')})`);
const dialog = { hasMissingSourceSearchAttempt, getSearchResultStatusLevel };
for (const attemptedSource of ['local', 'all']) {
assert.equal(
getMissingSourceResultStatus.call(dialog, {}, 'local', {
results: {
model_list: { source: 'model_list', confidence: 100, match_type: 'exact' },
popular: null
},
explicitSearchSources: [attemptedSource],
lastAttemptSources: [],
sourceProgress: {}
}),
'exact'
);
}
});
test('automatic Local Database download source stays hidden until that source is searched', () => {
const hasMissingSourceSearchAttempt = eval(`(${extractMethod(missingBrowserMethodsSource, 'hasMissingSourceSearchAttempt')})`);
const isLocalDatabaseDownloadSource = eval(`(${extractMethod(missingBrowserMethodsSource, 'isLocalDatabaseDownloadSource')})`);
const shouldDisplayKnownDownloadSource = eval(`(${extractMethod(missingBrowserMethodsSource, 'shouldDisplayKnownDownloadSource')})`);
const state = {
selectedSource: 'all',
explicitSearchSources: [],
lastAttemptSources: [],
sourceProgress: {}
};
const dialog = {
searchResultCache: new Map([['missing-key', state]]),
getMissingSearchKey: () => 'missing-key',
hasMissingSourceSearchAttempt,
isLocalDatabaseDownloadSource
};
const downloadSource = {
source: 'model_list',
url: 'https://example.invalid/model.safetensors'
};
assert.equal(shouldDisplayKnownDownloadSource.call(dialog, {}, downloadSource, state), false);
state.explicitSearchSources = ['local'];
assert.equal(shouldDisplayKnownDownloadSource.call(dialog, {}, downloadSource, state), true);
});
test('html template escapes every item in interpolated arrays', () => {
const rendered = html`<div>${['<script>', '"quoted"', '&value']}</div>`;
@@ -657,17 +657,6 @@ export const resolveDownloadMethods = {
hashMatches
)
};
state.lastAttemptSources = Array.from(new Set([...(state.lastAttemptSources || []), 'local']));
state.lastAttemptFound = true;
state.sourceProgress = {
...(state.sourceProgress || {}),
local: {
status: 'found',
percent: 100,
message: 'Hash metadata match',
updatedAt: Date.now()
}
};
if (workflowKey) {
this.persistSearchStateForWorkflow?.(workflowKey, currentMissing, state);
}
@@ -788,7 +777,6 @@ export const resolveDownloadMethods = {
});
this.persistLocalMatchesInAnalysisCache(missing, matches);
this.persistDownloadedLocalMatchSearchState(missing, matches, metadata);
return snapshot;
},
@@ -983,34 +971,6 @@ export const resolveDownloadMethods = {
}
},
persistDownloadedLocalMatchSearchState(missing, matches = [], metadata = {}) {
if (!missing || !Array.isArray(matches)) return;
const workflowKey = this.getWorkflowScopedQueueKey?.();
if (!workflowKey) return;
const state = this.getSearchStateForWorkflow?.(workflowKey, missing);
if (!state) return;
const hasRenderableMatch = matches.some(match => Number(match.confidence || 0) >= 70);
const hasExactMatch = matches.some(match => Number(match.confidence || 0) >= 100);
state.lastAttemptSources = Array.from(new Set([...(state.lastAttemptSources || []), 'local']));
state.lastAttemptFound = hasRenderableMatch || state.lastAttemptFound || false;
state.lastAttemptError = state.lastAttemptError || null;
state.sourceProgress = {
...(state.sourceProgress || {}),
local: {
status: hasRenderableMatch ? 'found' : 'none',
percent: 100,
message: hasExactMatch ? 'Exact local match' : (hasRenderableMatch ? 'Local match' : 'No local match'),
updatedAt: Date.now(),
filename: metadata.targetFilename || metadata.filename || ''
}
};
this.persistSearchStateForWorkflow?.(workflowKey, missing, state);
},
rememberDownloadUiState(downloadId, info, progress = {}, options = {}) {
if (!info?.missing) return null;
@@ -2394,6 +2354,10 @@ export const resolveDownloadMethods = {
};
this.backgroundSearchJobs.set(backgroundJobKey, backgroundJob);
state.activeSearchRunId = searchRunId;
state.explicitSearchSources = Array.from(new Set([
...(Array.isArray(state.explicitSearchSources) ? state.explicitSearchSources : []),
...sourceIds
]));
state.lastAttemptSources = sourceIds;
state.lastAttemptFound = null;
state.lastAttemptError = null;
@@ -3007,7 +2971,9 @@ export const resolveDownloadMethods = {
match: this.getSearchResultMatchDisplay(result, fallbackLabel, fallbackClass, hashLabel)
};
};
const knownDownloadRow = this.getDownloadSourceTableRow(missing, missing.download_source, hashLabelMap);
const knownDownloadRow = this.shouldDisplayKnownDownloadSource(missing, missing.download_source, state)
? this.getDownloadSourceTableRow(missing, missing.download_source, hashLabelMap)
: null;
const hasResults = knownDownloadRow || popular || modelListResult || hfResult || civitaiResult || civarchiveResult || loraManagerArchiveResult || customResults.length;
const progressHtml = this.renderSearchProgress(state);
const hasActiveProgress = this.hasActiveSearchProgress(state);
+1
View File
@@ -45,6 +45,7 @@ export const searchPanelMethods = {
custom: [],
local_hash_matches: []
},
explicitSearchSources: [],
lastAttemptSources: [],
lastAttemptBaseModelContext: '',
lastAttemptFound: null,
@@ -467,6 +467,9 @@ export const workflowStateMethods = {
clone.selectedSource = clone.selectedSource || 'all';
clone.selectedBaseModel = clone.selectedBaseModel || this.getDefaultSearchBaseModel?.() || 'auto';
clone.results = this.mergeSearchResults({}, clone.results || {});
clone.explicitSearchSources = Array.isArray(clone.explicitSearchSources)
? clone.explicitSearchSources
: [];
clone.lastAttemptSources = Array.isArray(clone.lastAttemptSources)
? clone.lastAttemptSources
: [];
+25 -9
View File
@@ -303,20 +303,36 @@ export const missingBrowserMethods = {
return 'idle';
},
hasMissingSourceSearchAttempt(missing = {}, source = '', state = null) {
const searchState = state || this.searchResultCache?.get(this.getMissingSearchKey(missing));
const explicitSources = new Set(
Array.isArray(searchState?.explicitSearchSources) ? searchState.explicitSearchSources : []
);
return explicitSources.has(source) || explicitSources.has('all');
},
isLocalDatabaseDownloadSource(downloadSource = {}) {
const source = String(downloadSource?.source || '')
.trim()
.toLowerCase()
.replace(/-/g, '_');
return source === 'local' || source === 'model_list' || source === 'popular';
},
shouldDisplayKnownDownloadSource(missing = {}, downloadSource = {}, state = null) {
if (!downloadSource?.url) return false;
if (!this.isLocalDatabaseDownloadSource(downloadSource)) return true;
return this.hasMissingSourceSearchAttempt(missing, 'local', state);
},
getMissingSourceResultStatus(missing = {}, source = '', state = null) {
const results = state?.results || {};
const candidates = [];
if (source === 'local') {
if (!this.hasMissingSourceSearchAttempt(missing, source, state)) return '';
candidates.push(results.model_list, results.popular);
const bestLocalMatch = this.getBestLocalMatch(missing, 70);
if (bestLocalMatch) {
candidates.push({
source: 'local_match',
confidence: Number(bestLocalMatch.confidence || 0),
match_type: Number(bestLocalMatch.confidence || 0) >= 100 ? 'exact' : 'similar'
});
}
} else if (source === 'huggingface') {
candidates.push(results.huggingface);
} else if (source === 'civitai') {
@@ -2248,7 +2264,7 @@ export const missingBrowserMethods = {
const downloadSource = missing.download_source;
const urnDownloadId = `urn-download-${missing.node_id}-${missing.widget_index}`;
if (downloadSource && downloadSource.url) {
if (this.shouldDisplayKnownDownloadSource(missing, downloadSource)) {
html += this.renderKnownDownloadPanel(missing, downloadSource);
} else if (perfectMatches.length > 0) {
// Has perfect local match - download not needed, but allow online re-check.