refactor: unify frontend source normalization
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
import { startSplitterDrag } from '../web/resolver/utils/splitter_drag.js';
|
||||
import { normalizeDownloadCategoryValue } from '../web/resolver/utils/category_utils.js';
|
||||
import { getSha256Field, normalizeSha256 } from '../web/resolver/utils/hash_utils.js';
|
||||
import { getSourceDisplayLabel } from '../web/resolver/utils/source_labels.js';
|
||||
import { getSourceDisplayLabel, normalizeSourceKey } from '../web/resolver/utils/source_labels.js';
|
||||
import { baseModelAliasMethods } from '../web/resolver/search/base_model_alias_methods.js';
|
||||
import { searchHashMethods } from '../web/resolver/search/search_hash_methods.js';
|
||||
import { missingModelStateMethods } from '../web/resolver/views/missing_model_state_methods.js';
|
||||
@@ -5767,14 +5767,12 @@ test('local hash identities link search results across sources by sha', () => {
|
||||
const {
|
||||
getLocalMatchHash,
|
||||
getSearchResultSha256,
|
||||
normalizeHashLookupSourceKey,
|
||||
getLocalHashMatchIdentitiesForResult,
|
||||
} = searchHashMethods;
|
||||
const sha256 = 'e'.repeat(64);
|
||||
const dialog = {
|
||||
getLocalMatchHash,
|
||||
getSearchResultSha256,
|
||||
normalizeHashLookupSourceKey,
|
||||
getLocalMatchIdentity() {
|
||||
return 'local-match-id';
|
||||
},
|
||||
|
||||
@@ -78,6 +78,8 @@ test('known local download sources are hidden until local search was attempted',
|
||||
const state = { explicitSearchSources: [] };
|
||||
|
||||
assert.equal(dialog.isLocalDatabaseDownloadSource(source), true);
|
||||
assert.equal(dialog.isLocalDatabaseDownloadSource({ source: 'POPULAR' }), true);
|
||||
assert.equal(dialog.isLocalDatabaseDownloadSource({ source: 'model-list' }), true);
|
||||
assert.equal(dialog.shouldDisplayKnownDownloadSource({}, source, state), false);
|
||||
assert.equal(
|
||||
dialog.shouldDisplayKnownDownloadSource({}, source, { explicitSearchSources: ['local'] }),
|
||||
@@ -89,6 +91,18 @@ test('known local download sources are hidden until local search was attempted',
|
||||
);
|
||||
});
|
||||
|
||||
test('custom provider results use normalized source identities', () => {
|
||||
const dialog = createDialog();
|
||||
const missing = { download_source: null };
|
||||
const state = {
|
||||
results: {
|
||||
custom: [{ source: 'CivArchive', url: 'https://example.test/archive' }],
|
||||
},
|
||||
};
|
||||
|
||||
assert.equal(dialog.getMissingSourceResultStatus(missing, 'civarchive', state), 'found');
|
||||
});
|
||||
|
||||
test('search result status and missing model counters expose stable state', () => {
|
||||
const dialog = createDialog();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import test from 'node:test';
|
||||
|
||||
import { searchHashMethods } from '../web/resolver/search/search_hash_methods.js';
|
||||
import { getSha256Field, normalizeSha256 } from '../web/resolver/utils/hash_utils.js';
|
||||
import { normalizeSourceKey } from '../web/resolver/utils/source_labels.js';
|
||||
|
||||
function createDialog(overrides = {}) {
|
||||
return { ...searchHashMethods, ...overrides };
|
||||
@@ -108,7 +109,7 @@ test('hash source keys and local identities match by hash or normalized source',
|
||||
null,
|
||||
];
|
||||
|
||||
assert.equal(dialog.normalizeHashLookupSourceKey(' Civit-AI '), 'civit_ai');
|
||||
assert.equal(normalizeSourceKey(' Civit-AI '), 'civit_ai');
|
||||
assert.deepEqual(
|
||||
dialog.getLocalHashMatchIdentitiesForResult(matches, 'civit-ai', { sha256: hashA }),
|
||||
['local-a']
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { searchSourceMethods } from '../web/resolver/search/search_source_methods.js';
|
||||
import { getSourceDisplayLabel } from '../web/resolver/utils/source_labels.js';
|
||||
import {
|
||||
getSourceDisplayLabel,
|
||||
normalizeSourceKey,
|
||||
normalizeSourceList,
|
||||
} from '../web/resolver/utils/source_labels.js';
|
||||
|
||||
const {
|
||||
getSearchSourceLabel,
|
||||
@@ -64,6 +68,26 @@ test('shared source labels preserve search and hash comparison contexts', () =>
|
||||
assert.equal(getSourceDisplayLabel('unknown-source', { fallback: 'Fallback' }), 'Fallback');
|
||||
});
|
||||
|
||||
test('source normalization preserves labels, trimmed selections, and provider errors', () => {
|
||||
const dialog = { getSearchSourceLabel, getSearchSourceErrorMessage };
|
||||
|
||||
assert.equal(normalizeSourceKey(' Civit-AI '), 'civit_ai');
|
||||
assert.equal(normalizeSourceKey(' Civit-AI ', { trim: false }), ' civit_ai ');
|
||||
assert.deepEqual([...normalizeSourceList([' ', ' local ', null, 'civitai'])], ['local', 'civitai']);
|
||||
assert.equal(
|
||||
getSourceDisplayLabel(' Civit-AI ', { fallback: 'Fallback' }),
|
||||
'Fallback'
|
||||
);
|
||||
assert.deepEqual(
|
||||
getSearchResultKeysForSources([' ', ' local ', null]),
|
||||
['popular', 'model_list']
|
||||
);
|
||||
assert.equal(
|
||||
getSearchSourceErrorMessage.call(dialog, ' CivArchive ', 'HTTP 502'),
|
||||
'CivArchive may be overloaded or temporarily unavailable. Please try again.'
|
||||
);
|
||||
});
|
||||
|
||||
test('CivArchive availability failures receive a user-facing retry message', () => {
|
||||
const dialog = { getSearchSourceLabel, getSearchSourceErrorMessage };
|
||||
const temporaryFailure = getSearchSourceErrorMessage.call(
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getCivitaiModelUrl } from "../globals.js";
|
||||
import { normalizeSha256 } from "../utils/hash_utils.js";
|
||||
import { normalizePathIdentity } from "../utils/html_utils.js";
|
||||
import { bindEventOnce, bindInstantAction, syncElementAttributes } from "../utils/dom_patch_utils.js";
|
||||
import { normalizeSourceKey } from "../utils/source_labels.js";
|
||||
const log = createModuleLogger('resolve_download_methods');
|
||||
|
||||
export const resolveDownloadMethods = {
|
||||
@@ -739,13 +740,13 @@ export const resolveDownloadMethods = {
|
||||
return existingMatches;
|
||||
}
|
||||
|
||||
const sourceKey = String(
|
||||
const sourceKey = normalizeSourceKey(
|
||||
options.sourceKey
|
||||
|| result.sourceKey
|
||||
|| result.source
|
||||
|| result.details_source
|
||||
|| 'download_source'
|
||||
).trim().toLowerCase().replace(/-/g, '_');
|
||||
);
|
||||
const category = options.category
|
||||
|| result.category
|
||||
|| result.directory
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { normalizeSha256 } from '../utils/hash_utils.js';
|
||||
import { normalizeSourceKey } from '../utils/source_labels.js';
|
||||
|
||||
export const searchHashMethods = {
|
||||
getSearchResultSha256(result = {}) {
|
||||
@@ -97,13 +98,6 @@ export const searchHashMethods = {
|
||||
: '';
|
||||
},
|
||||
|
||||
normalizeHashLookupSourceKey(source = '') {
|
||||
return String(source || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/-/g, '_');
|
||||
},
|
||||
|
||||
getLocalHashMatchIdentitiesForResult(hashMatches = [], sourceKey = '', sourceResult = {}) {
|
||||
const identities = [];
|
||||
const seen = new Set();
|
||||
@@ -118,7 +112,7 @@ export const searchHashMethods = {
|
||||
sourceResult.hash_verified_local_match_identities.forEach(addIdentity);
|
||||
}
|
||||
|
||||
const normalizedSource = this.normalizeHashLookupSourceKey(sourceKey);
|
||||
const normalizedSource = normalizeSourceKey(sourceKey);
|
||||
const sourceHash = this.getSearchResultSha256(sourceResult);
|
||||
if (!Array.isArray(hashMatches) || !hashMatches.length || !normalizedSource) {
|
||||
return identities;
|
||||
@@ -127,7 +121,7 @@ export const searchHashMethods = {
|
||||
hashMatches.forEach(match => {
|
||||
if (!match || typeof match !== 'object') return;
|
||||
|
||||
const matchSource = this.normalizeHashLookupSourceKey(match.hash_lookup_source || '');
|
||||
const matchSource = normalizeSourceKey(match.hash_lookup_source || '');
|
||||
const matchHash = normalizeSha256(this.getLocalMatchHash(match));
|
||||
if (sourceHash) {
|
||||
if (matchHash !== sourceHash) return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
import { getModelCardUrl, parseHuggingFaceFileUrl } from "../utils/url_utils.js";
|
||||
import { getSourceDisplayLabel } from "../utils/source_labels.js";
|
||||
import { getSourceDisplayLabel, normalizeSourceKey } from "../utils/source_labels.js";
|
||||
import { getCivitaiModelUrl } from "../globals.js";
|
||||
import { safeStorage, normalizePathIdentity } from "../utils/html_utils.js";
|
||||
import { bindEventOnce } from "../utils/dom_patch_utils.js";
|
||||
@@ -1710,7 +1710,10 @@ export const searchPanelMethods = {
|
||||
getCustomUrlResultTableRow(missing, result = {}, hashLabelMap = null, localHashMatches = []) {
|
||||
if (!result || typeof result !== 'object') return null;
|
||||
|
||||
const source = String(result.source || result.details_source || 'custom').toLowerCase().replace(/-/g, '_');
|
||||
const source = normalizeSourceKey(
|
||||
result.source || result.details_source || 'custom',
|
||||
{ trim: false }
|
||||
);
|
||||
const sourceKeys = {
|
||||
huggingface: 'huggingface',
|
||||
civitai: 'civitai',
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { safeStorage } from "../utils/html_utils.js";
|
||||
import { getSourceDisplayLabel } from "../utils/source_labels.js";
|
||||
import {
|
||||
getSourceDisplayLabel,
|
||||
normalizeSourceKey,
|
||||
normalizeSourceList,
|
||||
} from "../utils/source_labels.js";
|
||||
|
||||
const localStorage = safeStorage;
|
||||
|
||||
export const searchSourceMethods = {
|
||||
getSearchResultKeysForSources(sources = []) {
|
||||
const normalized = new Set((Array.isArray(sources) ? sources : [sources])
|
||||
.map(source => String(source || '').trim())
|
||||
.filter(Boolean));
|
||||
const normalized = normalizeSourceList(sources);
|
||||
if (normalized.has('all')) {
|
||||
return ['popular', 'model_list', 'huggingface', 'civitai', 'civarchive', 'lora_manager_archive'];
|
||||
}
|
||||
@@ -25,9 +27,7 @@ export const searchSourceMethods = {
|
||||
},
|
||||
|
||||
getHashLookupSourcesForSearchSources(sources = []) {
|
||||
const normalized = new Set((Array.isArray(sources) ? sources : [sources])
|
||||
.map(source => String(source || '').trim())
|
||||
.filter(Boolean));
|
||||
const normalized = normalizeSourceList(sources);
|
||||
const hashSources = ['huggingface', 'civitai', 'civarchive'];
|
||||
if (normalized.has('all')) return new Set(hashSources);
|
||||
return new Set(hashSources.filter(source => normalized.has(source)));
|
||||
@@ -75,7 +75,7 @@ export const searchSourceMethods = {
|
||||
}
|
||||
|
||||
const message = String(error || '').trim();
|
||||
const normalizedSource = String(source || '').trim().toLowerCase().replace(/-/g, '_');
|
||||
const normalizedSource = normalizeSourceKey(source);
|
||||
const statusCode = String(status?.code || '').trim().toLowerCase();
|
||||
if (normalizedSource === 'civarchive') {
|
||||
const messagesByCode = {
|
||||
@@ -116,7 +116,7 @@ export const searchSourceMethods = {
|
||||
|
||||
isSearchSourceRetryable(source, error = '', status = null) {
|
||||
if (typeof status?.retryable === 'boolean') return status.retryable;
|
||||
const normalizedSource = String(source || '').trim().toLowerCase().replace(/-/g, '_');
|
||||
const normalizedSource = normalizeSourceKey(source);
|
||||
if (normalizedSource !== 'civarchive') return false;
|
||||
return /network error|timeout|timed out|connection (?:error|reset|refused)|temporarily unavailable|\bHTTP\s+(?:408|429|5\d{2})\b/i.test(String(error || ''));
|
||||
},
|
||||
|
||||
@@ -23,6 +23,19 @@ const SOURCE_LABEL_CONTEXT_OVERRIDES = Object.freeze({
|
||||
}),
|
||||
});
|
||||
|
||||
export function normalizeSourceKey(value, { trim = true } = {}) {
|
||||
const text = String(value ?? '');
|
||||
return (trim ? text.trim() : text).toLowerCase().replace(/-/g, '_');
|
||||
}
|
||||
|
||||
export function normalizeSourceList(sources = []) {
|
||||
return new Set(
|
||||
(Array.isArray(sources) ? sources : [sources])
|
||||
.map(source => String(source ?? '').trim())
|
||||
.filter(Boolean)
|
||||
);
|
||||
}
|
||||
|
||||
export function getSourceDisplayLabel(
|
||||
source,
|
||||
{
|
||||
@@ -32,7 +45,7 @@ export function getSourceDisplayLabel(
|
||||
} = {}
|
||||
) {
|
||||
const sourceKey = normalize
|
||||
? String(source ?? '').toLowerCase().replace(/-/g, '_')
|
||||
? normalizeSourceKey(source, { trim: false })
|
||||
: source;
|
||||
const contextLabels = SOURCE_LABEL_CONTEXT_OVERRIDES[context] || {};
|
||||
return contextLabels[sourceKey] || COMMON_SOURCE_LABELS[sourceKey] || fallback;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { normalizeSourceKey } from '../utils/source_labels.js';
|
||||
|
||||
export const missingModelStateMethods = {
|
||||
getBestLocalMatch(missing = {}, minConfidence = 0) {
|
||||
const matches = Array.isArray(missing.matches) ? missing.matches : [];
|
||||
@@ -44,10 +46,7 @@ export const missingModelStateMethods = {
|
||||
},
|
||||
|
||||
isLocalDatabaseDownloadSource(downloadSource = {}) {
|
||||
const source = String(downloadSource?.source || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/-/g, '_');
|
||||
const source = normalizeSourceKey(downloadSource?.source || '');
|
||||
return source === 'local' || source === 'model_list' || source === 'popular';
|
||||
},
|
||||
|
||||
@@ -78,7 +77,7 @@ export const missingModelStateMethods = {
|
||||
if (['huggingface', 'civitai', 'civarchive'].includes(source)) {
|
||||
const customResults = Array.isArray(results.custom) ? results.custom : [];
|
||||
customResults
|
||||
.filter(result => String(result?.source || '').toLowerCase().replace(/-/g, '_') === source)
|
||||
.filter(result => normalizeSourceKey(result?.source || '', { trim: false }) === source)
|
||||
.forEach(result => candidates.push(result));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { normalizeSha256 } from '../utils/hash_utils.js';
|
||||
import { getSourceDisplayLabel } from '../utils/source_labels.js';
|
||||
import { getSourceDisplayLabel, normalizeSourceKey } from '../utils/source_labels.js';
|
||||
|
||||
export const modelHashCompareMethods = {
|
||||
formatSha256Short(value = '') {
|
||||
@@ -113,7 +113,10 @@ export const modelHashCompareMethods = {
|
||||
const explicitLabel = result?.sourceLabel || result?.source_label || '';
|
||||
if (explicitLabel) return String(explicitLabel);
|
||||
|
||||
const rawSource = String(result?.source || result?.details_source || sourceKey || '').toLowerCase().replace(/-/g, '_');
|
||||
const rawSource = normalizeSourceKey(
|
||||
result?.source || result?.details_source || sourceKey || '',
|
||||
{ trim: false }
|
||||
);
|
||||
return getSourceDisplayLabel(rawSource, {
|
||||
context: 'hash_compare',
|
||||
fallback: String(sourceKey || 'Source').replace(/[_-]+/g, ' '),
|
||||
|
||||
Reference in New Issue
Block a user