refactor(frontend): extract search hash policies
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
};
|
||||
@@ -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 || '';
|
||||
|
||||
Reference in New Issue
Block a user