refactor: centralize search result row construction

This commit is contained in:
Dariusz L
2026-08-07 00:24:03 +02:00
parent 3f294d38d3
commit 30b1bf99e9
3 changed files with 230 additions and 54 deletions
+145
View File
@@ -7178,6 +7178,151 @@ test('search result refreshes preserve keyed progress and result DOM', () => {
assert.match(searchPanelMethodsSource, /data-search-result-key=/);
});
test('search provider rows preserve a shared result row contract', () => {
const displaySearchResults = eval(`(${extractMethod(resolveDownloadMethodsSource, 'displaySearchResults')})`);
const buildSearchResultRow = eval(`(${extractMethod(searchPanelMethodsSource, 'buildSearchResultRow')})`);
const previousGlobals = {
getModelCardUrl: globalThis.getModelCardUrl,
getCivitaiModelUrl: globalThis.getCivitaiModelUrl,
parseHuggingFaceFileUrl: globalThis.parseHuggingFaceFileUrl,
};
const rows = [];
const dialog = {
buildSearchResultRow,
getHashMatchLabelMap() {
return null;
},
shouldDisplayKnownDownloadSource() {
return false;
},
getDownloadSourceTableRow() {
return null;
},
renderSearchProgress() {
return '';
},
hasActiveSearchProgress() {
return false;
},
getLocalHashMatchIdentitiesForResult() {
return [];
},
getHashMatchLabelForSearchResult() {
return '';
},
getSearchResultMatchDisplay(_result, fallbackLabel, fallbackClass) {
return { label: fallbackLabel, className: fallbackClass };
},
getFilenameFromPath(value = '') {
return String(value).split(/[\\/]/).at(-1) || '';
},
getMissingDownloadCategory() {
return 'checkpoints';
},
getSourceResultDownloadCategory(_result, fallback) {
return fallback;
},
formatSearchResultSize(result = {}) {
return result.size || '';
},
getSearchResultTimestamp() {
return '';
},
getMissingModelKey() {
return 'missing-key';
},
getDownloadPathMetadata() {
return {};
},
getDownloadMetadata() {
return {};
},
renderStatusMessage() {
return '';
},
renderSearchResultsTable(searchRows) {
rows.push(...searchRows);
return '';
},
patchSearchResultsContainer() {},
wireSearchProgressCancelButtons() {},
wireSearchProgressRetryButtons() {},
wireSearchDownloadButtons() {},
};
globalThis.getModelCardUrl = value => value || '';
globalThis.getCivitaiModelUrl = (modelId, versionId) => `civitai/${modelId}/${versionId}`;
globalThis.parseHuggingFaceFileUrl = () => null;
try {
displaySearchResults.call(dialog, {
original_path: 'models/model.safetensors',
category: 'checkpoints',
matches: [],
}, {
results: {
popular: {
name: 'Popular model',
filename: 'popular.safetensors',
url: 'popular/download',
},
model_list: {
name: 'Local model',
filename: 'local.safetensors',
url: 'local/download',
},
huggingface: {
repo_id: 'owner/repo',
filename: 'hf.safetensors',
url: 'hf/download',
},
civarchive: {
name: 'Archive model',
filename: 'archive.safetensors',
download_url: 'archive/download',
url: 'archive/page',
},
lora_manager_archive: {
name: 'LoRA model',
filename: 'lora.safetensors',
download_url: 'lora/download',
url: 'lora/page',
},
civitai: {
name: 'CivitAI model',
filename: 'civitai.safetensors',
download_url: 'civitai/download',
url: 'civitai/page',
},
},
}, {});
} finally {
if (previousGlobals.getModelCardUrl === undefined) delete globalThis.getModelCardUrl;
else globalThis.getModelCardUrl = previousGlobals.getModelCardUrl;
if (previousGlobals.getCivitaiModelUrl === undefined) delete globalThis.getCivitaiModelUrl;
else globalThis.getCivitaiModelUrl = previousGlobals.getCivitaiModelUrl;
if (previousGlobals.parseHuggingFaceFileUrl === undefined) delete globalThis.parseHuggingFaceFileUrl;
else globalThis.parseHuggingFaceFileUrl = previousGlobals.parseHuggingFaceFileUrl;
}
assert.equal(rows.length, 6);
for (const row of rows) {
assert.equal(typeof row.sourceKey, 'string');
assert.equal(typeof row.sourceLabel, 'string');
assert.equal(typeof row.model, 'string');
assert.equal(typeof row.filename, 'string');
assert.equal(typeof row.match, 'object');
assert.equal(typeof row.size, 'string');
assert.equal(typeof row.downloadUrl, 'string');
assert.equal(typeof row.downloadFilename, 'string');
assert.equal(typeof row.category, 'string');
assert.equal(typeof row.searchedAt, 'string');
assert.ok(Array.isArray(row.localHashMatchIdentities));
assert.ok(row.pathMetadata);
assert.ok(row.downloadMetadata);
}
});
test('source model details file selection includes selected file hash metadata', () => {
const renderSourceModelDetailsFiles = eval(`(${extractMethod(modelInfoMethodsSource, 'renderSourceModelDetailsFiles')})`);
const sha256 = 'a'.repeat(64);
@@ -3662,17 +3662,6 @@ export const resolveDownloadMethods = {
...(Array.isArray(missing.matches) ? missing.matches.filter(match => match?.hash_lookup_source) : [])
];
const hashLabelMap = this.getHashMatchLabelMap?.(missing, results) || null;
const getHashMatchIdentities = (sourceKey, result) => (
this.getLocalHashMatchIdentitiesForResult?.(localHashMatches, sourceKey, result) || []
);
const getHashMatchDisplay = (sourceKey, result, fallbackLabel = 'Match', fallbackClass = 'neutral') => {
const identities = getHashMatchIdentities(sourceKey, result);
const hashLabel = this.getHashMatchLabelForSearchResult?.(result, hashLabelMap, identities) || '';
return {
identities,
match: this.getSearchResultMatchDisplay(result, fallbackLabel, fallbackClass, hashLabel)
};
};
const knownDownloadRow = this.shouldDisplayKnownDownloadSource(missing, missing.download_source, state)
? this.getDownloadSourceTableRow(missing, missing.download_source, hashLabelMap)
: null;
@@ -3740,7 +3729,6 @@ export const resolveDownloadMethods = {
addRow(knownDownloadRow);
if (popular) {
const popularHash = getHashMatchDisplay('popular', popular, 'Known', 'strong');
const popularFilename = popular.filename || this.getFilenameFromPath(missing.original_path);
const missingCategory = this.getMissingDownloadCategory?.(missing, 'checkpoints') || missing.category || 'checkpoints';
const popularSize = popular.size
@@ -3751,25 +3739,26 @@ export const resolveDownloadMethods = {
? modelListResult.size
: ''
);
addRow({
addRow(this.buildSearchResultRow(missing, {
result: popular,
sourceKey: 'popular',
sourceLabel: 'Popular',
model: popular.name || popularFilename,
filename: popularFilename,
secondary: popular.name && popular.name !== popularFilename ? popularFilename : '',
match: popularHash.match,
size: this.formatSearchResultSize({ ...popular, size: popularSize }),
sizeResult: { ...popular, size: popularSize },
downloadUrl: popular.url,
downloadFilename: popularFilename,
category: popular.directory || missingCategory,
openUrl: getModelCardUrl(popular.url),
searchedAt: this.getSearchResultTimestamp(popular),
localHashMatchIdentities: popularHash.identities
});
hashLabelMap,
localHashMatches,
fallbackLabel: 'Known',
fallbackClass: 'strong',
}));
}
if (modelListResult && modelListResult.url) {
const modelListHash = getHashMatchDisplay('model_list', modelListResult);
const missingCategory = this.getMissingDownloadCategory?.(missing, 'checkpoints') || missing.category || 'checkpoints';
const huggingFaceFile = parseHuggingFaceFileUrl(modelListResult.url);
const modelListDetailsContext = huggingFaceFile
@@ -3787,43 +3776,40 @@ export const resolveDownloadMethods = {
category: modelListResult.directory || missingCategory
}
: null;
addRow({
addRow(this.buildSearchResultRow(missing, {
result: modelListResult,
sourceKey: 'model-list',
sourceLabel: 'Local Database',
model: modelListResult.name || modelListResult.filename,
filename: modelListResult.filename,
secondary: modelListResult.name && modelListResult.name !== modelListResult.filename ? modelListResult.filename : '',
match: modelListHash.match,
size: this.formatSearchResultSize(modelListResult),
downloadUrl: modelListResult.url,
downloadFilename: modelListResult.filename,
category: modelListResult.directory || missingCategory,
openUrl: getModelCardUrl(modelListResult.url),
searchedAt: this.getSearchResultTimestamp(modelListResult),
localHashMatchIdentities: modelListHash.identities,
detailsContext: modelListDetailsContext
});
hashLabelMap,
localHashMatches,
detailsContext: modelListDetailsContext,
}));
}
if (hfResult && hfResult.url) {
const hfHash = getHashMatchDisplay('huggingface', hfResult);
const hfRepo = hfResult.repo_id || hfResult.repo || '';
const hfModelUrl = hfRepo ? `https://huggingface.co/${hfRepo}` : getModelCardUrl(hfResult.url);
const missingCategory = this.getMissingDownloadCategory?.(missing, 'checkpoints') || missing.category || 'checkpoints';
addRow({
addRow(this.buildSearchResultRow(missing, {
result: hfResult,
sourceKey: 'huggingface',
sourceLabel: 'HuggingFace',
model: hfRepo || hfResult.filename,
filename: hfResult.filename,
secondary: hfResult.path && hfResult.path !== hfResult.filename ? hfResult.path : '',
match: hfHash.match,
size: this.formatSearchResultSize(hfResult),
downloadUrl: hfResult.url,
downloadFilename: hfResult.filename,
category: missingCategory,
openUrl: hfModelUrl,
searchedAt: this.getSearchResultTimestamp(hfResult),
localHashMatchIdentities: hfHash.identities,
hashLabelMap,
localHashMatches,
detailsContext: {
...hfResult,
name: hfRepo || hfResult.filename,
@@ -3832,11 +3818,10 @@ export const resolveDownloadMethods = {
missing_key: this.getMissingModelKey(missing),
category: missingCategory
}
});
}));
}
if (civarchiveResult && civarchiveResult.download_url) {
const civarchiveHash = getHashMatchDisplay('civarchive', civarchiveResult);
const archiveFilename = civarchiveResult.filename || this.getFilenameFromPath(missing.original_path);
const archiveName = civarchiveResult.name || archiveFilename || 'Model';
const archiveCategory = this.getSourceResultDownloadCategory?.(
@@ -3849,32 +3834,30 @@ export const resolveDownloadMethods = {
civarchiveResult.platform || '',
civarchiveResult.base_model || ''
].filter(Boolean).join(' / ');
addRow({
addRow(this.buildSearchResultRow(missing, {
result: civarchiveResult,
sourceKey: 'civarchive',
sourceLabel: 'CivArchive',
model: archiveName,
version: civarchiveResult.version_name || '',
filename: archiveFilename,
secondary: archiveSecondary,
match: civarchiveHash.match,
size: this.formatSearchResultSize(civarchiveResult),
downloadUrl: civarchiveResult.download_url,
downloadFilename: archiveFilename,
category: archiveCategory,
openUrl: civarchiveResult.url,
searchedAt: this.getSearchResultTimestamp(civarchiveResult),
localHashMatchIdentities: civarchiveHash.identities,
hashLabelMap,
localHashMatches,
detailsContext: {
...civarchiveResult,
details_source: 'civarchive',
missing_key: this.getMissingModelKey(missing),
category: archiveCategory
}
});
}));
}
if (loraManagerArchiveResult && loraManagerArchiveResult.download_url) {
const loraArchiveHash = getHashMatchDisplay('lora_manager_archive', loraManagerArchiveResult);
const archiveFilename = loraManagerArchiveResult.filename || this.getFilenameFromPath(missing.original_path);
const archiveName = loraManagerArchiveResult.name || archiveFilename;
const archiveCategory = this.getSourceResultDownloadCategory?.(
@@ -3882,21 +3865,21 @@ export const resolveDownloadMethods = {
this.getMissingDownloadCategory?.(missing, 'loras') || 'loras',
missing
) || this.getMissingDownloadCategory?.(missing, 'loras') || 'loras';
addRow({
addRow(this.buildSearchResultRow(missing, {
result: loraManagerArchiveResult,
sourceKey: 'lora-archive',
sourceLabel: 'LoRA Archive',
model: archiveName,
version: loraManagerArchiveResult.version_name || '',
filename: archiveFilename,
secondary: archiveName && archiveName !== archiveFilename ? archiveFilename : '',
match: loraArchiveHash.match,
size: this.formatSearchResultSize(loraManagerArchiveResult),
downloadUrl: loraManagerArchiveResult.download_url || '',
downloadFilename: archiveFilename,
category: archiveCategory,
openUrl: loraManagerArchiveResult.url || getModelCardUrl(loraManagerArchiveResult.download_url),
searchedAt: this.getSearchResultTimestamp(loraManagerArchiveResult),
localHashMatchIdentities: loraArchiveHash.identities,
hashSourceKey: 'lora_manager_archive',
hashLabelMap,
localHashMatches,
detailsContext: {
...loraManagerArchiveResult,
source: 'lora_manager_archive',
@@ -3904,11 +3887,10 @@ export const resolveDownloadMethods = {
missing_key: this.getMissingModelKey(missing),
category: archiveCategory
}
});
}));
}
if (civitaiResult && civitaiResult.download_url) {
const civitaiHash = getHashMatchDisplay('civitai', civitaiResult);
const modelUrl = civitaiResult.url || getCivitaiModelUrl(civitaiResult.model_id, civitaiResult.version_id);
const downloadFilename = civitaiResult.filename || missing.civitai_info?.expected_filename || civitaiResult.name;
const modelName = civitaiResult.name || missing.civitai_info?.model_name || downloadFilename || 'Model';
@@ -3921,21 +3903,20 @@ export const resolveDownloadMethods = {
civitaiResult.type || '',
civitaiResult.base_model || missing.civitai_info?.base_model || ''
].filter(Boolean).join(' / ');
addRow({
addRow(this.buildSearchResultRow(missing, {
result: civitaiResult,
sourceKey: 'civitai',
sourceLabel: 'CivitAI',
model: modelName,
version: civitaiResult.version_name || missing.civitai_info?.version_name || '',
filename: downloadFilename,
secondary: civitaiSecondary,
match: civitaiHash.match,
size: this.formatSearchResultSize(civitaiResult),
downloadUrl: civitaiResult.download_url,
downloadFilename,
category: civitaiCategory,
openUrl: modelUrl,
searchedAt: this.getSearchResultTimestamp(civitaiResult),
localHashMatchIdentities: civitaiHash.identities,
hashLabelMap,
localHashMatches,
detailsContext: {
...civitaiResult,
name: modelName,
@@ -3944,7 +3925,7 @@ export const resolveDownloadMethods = {
missing_key: this.getMissingModelKey(missing),
category: civitaiCategory
}
});
}));
}
customResults.forEach((customResult) => {
+50
View File
@@ -1594,6 +1594,56 @@ export const searchPanelMethods = {
return { label: fallbackLabel, className: fallbackClass };
},
buildSearchResultRow(missing, {
result = {},
sourceKey,
sourceLabel,
hashSourceKey = sourceKey,
hashLabelMap = null,
localHashMatches = [],
model = '',
version = '',
filename = '',
secondary = '',
sizeResult = result,
downloadUrl = '',
downloadFilename = filename,
category = '',
openUrl = '',
detailsContext = null,
fallbackLabel = 'Match',
fallbackClass = 'neutral',
} = {}) {
const localHashMatchIdentities = this.getLocalHashMatchIdentitiesForResult?.(
localHashMatches,
hashSourceKey,
result
) || [];
const hashMatchLabel = this.getHashMatchLabelForSearchResult?.(
result,
hashLabelMap,
localHashMatchIdentities
) || '';
return {
sourceKey,
sourceLabel,
model,
version,
filename,
secondary,
match: this.getSearchResultMatchDisplay(result, fallbackLabel, fallbackClass, hashMatchLabel),
size: this.formatSearchResultSize(sizeResult),
downloadUrl,
downloadFilename,
category,
openUrl,
searchedAt: this.getSearchResultTimestamp(result),
localHashMatchIdentities,
detailsContext,
};
},
getDownloadSourceTableRow(missing, downloadSource = {}, hashLabelMap = null) {
if (!downloadSource?.url) return null;