refactor(frontend): extract search hash policies

This commit is contained in:
Dariusz L
2026-08-02 13:38:30 +02:00
parent 9d0321bb81
commit 3551a601c8
4 changed files with 184 additions and 175 deletions
+20 -14
View File
@@ -19,6 +19,7 @@ import {
} from '../web/resolver/node_context_menu.js';
import { startSplitterDrag } from '../web/resolver/utils/splitter_drag.js';
import { baseModelAliasMethods } from '../web/resolver/search/base_model_alias_methods.js';
import { searchHashMethods } from '../web/resolver/search/search_hash_methods.js';
import {
getCustomNodeModelAdapter,
getCustomNodeModelCategory,
@@ -5039,10 +5040,12 @@ test('missing model preview shows accepted GGUF format next to its category', ()
});
test('hash match label map numbers distinct matched hashes', () => {
const normalizeSearchResultSha256 = eval(`(${extractMethod(searchPanelMethodsSource, 'normalizeSearchResultSha256')})`);
const getLocalMatchHash = eval(`(${extractMethod(searchPanelMethodsSource, 'getLocalMatchHash')})`);
const collectHashLabelMapHashes = eval(`(${extractMethod(searchPanelMethodsSource, 'collectHashLabelMapHashes')})`);
const getHashMatchLabelMap = eval(`(${extractMethod(searchPanelMethodsSource, 'getHashMatchLabelMap')})`);
const {
normalizeSearchResultSha256,
getLocalMatchHash,
collectHashLabelMapHashes,
getHashMatchLabelMap,
} = searchHashMethods;
const firstHash = 'a'.repeat(64);
const secondHash = 'b'.repeat(64);
const dialog = {
@@ -5068,9 +5071,11 @@ test('hash match label map numbers distinct matched hashes', () => {
});
test('search result hash labels use linked local hash identity', () => {
const normalizeSearchResultSha256 = eval(`(${extractMethod(searchPanelMethodsSource, 'normalizeSearchResultSha256')})`);
const getSearchResultSha256 = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchResultSha256')})`);
const getHashMatchLabelForSearchResult = eval(`(${extractMethod(searchPanelMethodsSource, 'getHashMatchLabelForSearchResult')})`);
const {
normalizeSearchResultSha256,
getSearchResultSha256,
getHashMatchLabelForSearchResult,
} = searchHashMethods;
const getSearchResultMatchDisplay = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchResultMatchDisplay')})`);
const sha256 = 'd'.repeat(64);
const hashLabelMap = new Map([[sha256, 'Hash 2']]);
@@ -5098,11 +5103,13 @@ test('search result hash labels use linked local hash identity', () => {
});
test('local hash identities link search results across sources by sha', () => {
const normalizeSearchResultSha256 = eval(`(${extractMethod(searchPanelMethodsSource, 'normalizeSearchResultSha256')})`);
const getLocalMatchHash = eval(`(${extractMethod(searchPanelMethodsSource, 'getLocalMatchHash')})`);
const getSearchResultSha256 = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchResultSha256')})`);
const normalizeHashLookupSourceKey = eval(`(${extractMethod(searchPanelMethodsSource, 'normalizeHashLookupSourceKey')})`);
const getLocalHashMatchIdentitiesForResult = eval(`(${extractMethod(searchPanelMethodsSource, 'getLocalHashMatchIdentitiesForResult')})`);
const {
normalizeSearchResultSha256,
getLocalMatchHash,
getSearchResultSha256,
normalizeHashLookupSourceKey,
getLocalHashMatchIdentitiesForResult,
} = searchHashMethods;
const sha256 = 'e'.repeat(64);
const dialog = {
normalizeSearchResultSha256,
@@ -5127,8 +5134,7 @@ test('local hash identities link search results across sources by sha', () => {
});
test('remote hash sync fetches local matches by selected result sha', async () => {
const normalizeSearchResultSha256 = eval(`(${extractMethod(searchPanelMethodsSource, 'normalizeSearchResultSha256')})`);
const getSearchResultSha256 = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchResultSha256')})`);
const { normalizeSearchResultSha256, getSearchResultSha256 } = searchHashMethods;
const getExistingLocalHashMatchesForSha = eval(`(${extractMethod(resolveDownloadMethodsSource, 'getExistingLocalHashMatchesForSha')})`);
const syncRemoteHashMatchesForResult = eval(`(${extractMethod(resolveDownloadMethodsSource, 'syncRemoteHashMatchesForResult')})`);
const sha256 = 'f'.repeat(64);
+2
View File
@@ -14,6 +14,7 @@ import { downloadTargetMethods } from "./search/download_target_methods.js";
import { searchPanelMethods } from "./search/search_panel.js";
import { searchSourceMethods } from "./search/search_source_methods.js";
import { baseModelAliasMethods } from "./search/base_model_alias_methods.js";
import { searchHashMethods } from "./search/search_hash_methods.js";
import { queueMethods } from "./actions/queue_methods.js";
import { queueStorageMethods } from "./actions/queue_storage_methods.js";
import { downloadProgressMethods } from "./actions/download_progress_methods.js";
@@ -337,6 +338,7 @@ function applyDialogMethods(...sources) {
applyDialogMethods(
searchSourceMethods,
baseModelAliasMethods,
searchHashMethods,
searchPanelMethods,
modelInfoMethods,
renderFormatMethods,
+162
View File
@@ -0,0 +1,162 @@
export const searchHashMethods = {
normalizeSearchResultSha256(value = '') {
let text = String(value || '').trim();
text = text.replace(/^sha256[:=]/i, '').trim().toLowerCase();
return /^[a-f0-9]{64}$/.test(text) ? text : '';
},
getSearchResultSha256(result = {}) {
if (!result || typeof result !== 'object') return '';
const hashes = result.hashes && typeof result.hashes === 'object' ? result.hashes : {};
const fileInfo = result.file_info && typeof result.file_info === 'object' ? result.file_info : {};
const fileHashes = fileInfo.hashes && typeof fileInfo.hashes === 'object' ? fileInfo.hashes : {};
const candidates = [
result.hash_verified_sha256,
result.sha256,
result.hash,
hashes.SHA256,
hashes.sha256,
fileInfo.sha256,
fileInfo.hash,
fileHashes.SHA256,
fileHashes.sha256
];
for (const candidate of candidates) {
const hash = this.normalizeSearchResultSha256(candidate);
if (hash) return hash;
}
return '';
},
collectHashLabelMapHashes(missing = {}, results = null) {
const hashes = [];
const seen = new Set();
const addHash = (value) => {
const hash = this.normalizeSearchResultSha256?.(value)
|| String(value || '').trim().toLowerCase();
if (!hash || !/^[a-f0-9]{64}$/.test(hash) || seen.has(hash)) return;
seen.add(hash);
hashes.push(hash);
};
const addMatch = (match) => {
if (!match || typeof match !== 'object') return;
if (!(match.hash_match || match.match_type === 'hash' || match.hash_lookup_source)) return;
addHash(this.getLocalMatchHash?.(match) || match.sha256 || match.hash);
};
const addResult = (result) => {
if (Array.isArray(result)) {
result.forEach(addResult);
return;
}
if (!result || typeof result !== 'object') return;
const matchType = String(result.match_type || '').toLowerCase();
if (!(result.hash_verified || result.hash_verified_sha256 || matchType === 'hash')) return;
addHash(this.getSearchResultSha256?.(result) || result.sha256 || result.hash);
};
(Array.isArray(missing.matches) ? missing.matches : []).forEach(addMatch);
const stateResults = results
|| this.getSearchState?.(missing)?.results
|| this.searchResultCache?.get?.(this.getMissingSearchKey?.(missing))?.results
|| {};
(Array.isArray(stateResults.local_hash_matches) ? stateResults.local_hash_matches : []).forEach(addMatch);
[
missing.download_source,
stateResults.popular,
stateResults.model_list,
stateResults.huggingface,
stateResults.civitai,
stateResults.civarchive,
stateResults.lora_manager_archive,
...(Array.isArray(stateResults.custom) ? stateResults.custom : [])
].forEach(addResult);
return hashes;
},
getHashMatchLabelMap(missing = {}, results = null) {
const hashes = this.collectHashLabelMapHashes?.(missing, results) || [];
const labelMap = new Map();
if (!hashes.length) return labelMap;
const useNumbers = hashes.length > 1;
hashes.forEach((hash, index) => {
labelMap.set(hash, useNumbers ? `Hash ${index + 1}` : 'Hash');
});
return labelMap;
},
getHashMatchLabelForSearchResult(result = {}, hashLabelMap = null, identities = []) {
const hash = this.getSearchResultSha256?.(result) || '';
const label = hash && hashLabelMap?.get?.(hash) ? hashLabelMap.get(hash) : '';
if (!label) return '';
const matchType = String(result.match_type || '').toLowerCase();
const hasLinkedLocalMatch = Array.isArray(identities) && identities.length > 0;
return (hasLinkedLocalMatch || result.hash_verified || result.hash_verified_sha256 || matchType === 'hash')
? label
: '';
},
normalizeHashLookupSourceKey(source = '') {
return String(source || '')
.trim()
.toLowerCase()
.replace(/-/g, '_');
},
getLocalHashMatchIdentitiesForResult(hashMatches = [], sourceKey = '', sourceResult = {}) {
const identities = [];
const seen = new Set();
const addIdentity = (identity) => {
const normalized = String(identity || '').trim();
if (!normalized || seen.has(normalized)) return;
seen.add(normalized);
identities.push(normalized);
};
if (Array.isArray(sourceResult?.hash_verified_local_match_identities)) {
sourceResult.hash_verified_local_match_identities.forEach(addIdentity);
}
const normalizedSource = this.normalizeHashLookupSourceKey(sourceKey);
const sourceHash = this.getSearchResultSha256(sourceResult);
if (!Array.isArray(hashMatches) || !hashMatches.length || !normalizedSource) {
return identities;
}
hashMatches.forEach(match => {
if (!match || typeof match !== 'object') return;
const matchSource = this.normalizeHashLookupSourceKey(match.hash_lookup_source || '');
const matchHash = this.normalizeSearchResultSha256(this.getLocalMatchHash(match));
if (sourceHash) {
if (matchHash !== sourceHash) return;
} else if (!matchSource || matchSource !== normalizedSource) {
return;
}
addIdentity(this.getLocalMatchIdentity?.(match) || '');
});
return identities;
},
getLocalMatchHash(match = {}) {
const model = match.model || {};
const hashes = model.hashes && typeof model.hashes === 'object' ? model.hashes : {};
return String(
match.sha256
|| match.hash
|| model.sha256
|| model.hash
|| hashes.SHA256
|| hashes.sha256
|| ''
).trim();
},
};
-161
View File
@@ -1629,153 +1629,6 @@ export const searchPanelMethods = {
return { label: fallbackLabel, className: fallbackClass };
},
normalizeSearchResultSha256(value = '') {
let text = String(value || '').trim();
text = text.replace(/^sha256[:=]/i, '').trim().toLowerCase();
return /^[a-f0-9]{64}$/.test(text) ? text : '';
},
getSearchResultSha256(result = {}) {
if (!result || typeof result !== 'object') return '';
const hashes = result.hashes && typeof result.hashes === 'object' ? result.hashes : {};
const fileInfo = result.file_info && typeof result.file_info === 'object' ? result.file_info : {};
const fileHashes = fileInfo.hashes && typeof fileInfo.hashes === 'object' ? fileInfo.hashes : {};
const candidates = [
result.hash_verified_sha256,
result.sha256,
result.hash,
hashes.SHA256,
hashes.sha256,
fileInfo.sha256,
fileInfo.hash,
fileHashes.SHA256,
fileHashes.sha256
];
for (const candidate of candidates) {
const hash = this.normalizeSearchResultSha256(candidate);
if (hash) return hash;
}
return '';
},
collectHashLabelMapHashes(missing = {}, results = null) {
const hashes = [];
const seen = new Set();
const addHash = (value) => {
const hash = this.normalizeSearchResultSha256?.(value)
|| String(value || '').trim().toLowerCase();
if (!hash || !/^[a-f0-9]{64}$/.test(hash) || seen.has(hash)) return;
seen.add(hash);
hashes.push(hash);
};
const addMatch = (match) => {
if (!match || typeof match !== 'object') return;
if (!(match.hash_match || match.match_type === 'hash' || match.hash_lookup_source)) return;
addHash(this.getLocalMatchHash?.(match) || match.sha256 || match.hash);
};
const addResult = (result) => {
if (Array.isArray(result)) {
result.forEach(addResult);
return;
}
if (!result || typeof result !== 'object') return;
const matchType = String(result.match_type || '').toLowerCase();
if (!(result.hash_verified || result.hash_verified_sha256 || matchType === 'hash')) return;
addHash(this.getSearchResultSha256?.(result) || result.sha256 || result.hash);
};
(Array.isArray(missing.matches) ? missing.matches : []).forEach(addMatch);
const stateResults = results
|| this.getSearchState?.(missing)?.results
|| this.searchResultCache?.get?.(this.getMissingSearchKey?.(missing))?.results
|| {};
(Array.isArray(stateResults.local_hash_matches) ? stateResults.local_hash_matches : []).forEach(addMatch);
[
missing.download_source,
stateResults.popular,
stateResults.model_list,
stateResults.huggingface,
stateResults.civitai,
stateResults.civarchive,
stateResults.lora_manager_archive,
...(Array.isArray(stateResults.custom) ? stateResults.custom : [])
].forEach(addResult);
return hashes;
},
getHashMatchLabelMap(missing = {}, results = null) {
const hashes = this.collectHashLabelMapHashes?.(missing, results) || [];
const labelMap = new Map();
if (!hashes.length) return labelMap;
const useNumbers = hashes.length > 1;
hashes.forEach((hash, index) => {
labelMap.set(hash, useNumbers ? `Hash ${index + 1}` : 'Hash');
});
return labelMap;
},
getHashMatchLabelForSearchResult(result = {}, hashLabelMap = null, identities = []) {
const hash = this.getSearchResultSha256?.(result) || '';
const label = hash && hashLabelMap?.get?.(hash) ? hashLabelMap.get(hash) : '';
if (!label) return '';
const matchType = String(result.match_type || '').toLowerCase();
const hasLinkedLocalMatch = Array.isArray(identities) && identities.length > 0;
return (hasLinkedLocalMatch || result.hash_verified || result.hash_verified_sha256 || matchType === 'hash')
? label
: '';
},
normalizeHashLookupSourceKey(source = '') {
return String(source || '')
.trim()
.toLowerCase()
.replace(/-/g, '_');
},
getLocalHashMatchIdentitiesForResult(hashMatches = [], sourceKey = '', sourceResult = {}) {
const identities = [];
const seen = new Set();
const addIdentity = (identity) => {
const normalized = String(identity || '').trim();
if (!normalized || seen.has(normalized)) return;
seen.add(normalized);
identities.push(normalized);
};
if (Array.isArray(sourceResult?.hash_verified_local_match_identities)) {
sourceResult.hash_verified_local_match_identities.forEach(addIdentity);
}
const normalizedSource = this.normalizeHashLookupSourceKey(sourceKey);
const sourceHash = this.getSearchResultSha256(sourceResult);
if (!Array.isArray(hashMatches) || !hashMatches.length || !normalizedSource) {
return identities;
}
hashMatches.forEach(match => {
if (!match || typeof match !== 'object') return;
const matchSource = this.normalizeHashLookupSourceKey(match.hash_lookup_source || '');
const matchHash = this.normalizeSearchResultSha256(this.getLocalMatchHash(match));
if (sourceHash) {
if (matchHash !== sourceHash) return;
} else if (!matchSource || matchSource !== normalizedSource) {
return;
}
addIdentity(this.getLocalMatchIdentity?.(match) || '');
});
return identities;
},
getDownloadSourceTableRow(missing, downloadSource = {}, hashLabelMap = null) {
if (!downloadSource?.url) return null;
@@ -2247,20 +2100,6 @@ export const searchPanelMethods = {
};
},
getLocalMatchHash(match = {}) {
const model = match.model || {};
const hashes = model.hashes && typeof model.hashes === 'object' ? model.hashes : {};
return String(
match.sha256
|| match.hash
|| model.sha256
|| model.hash
|| hashes.SHA256
|| hashes.sha256
|| ''
).trim();
},
getLocalMatchContextData(missing = {}, match = {}) {
const hash = this.getLocalMatchHash(match);
const metadataPath = match.metadata_path || match.model?.metadata_path || '';