From 57e6f8c61df995698b0ec0c78a3fed8147e2a38f Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Sun, 2 Aug 2026 13:34:02 +0200 Subject: [PATCH] refactor(frontend): extract search source policies --- tests/test_search_source_methods.mjs | 53 ++----- web/resolver/resolver_dialog.js | 2 + web/resolver/search/search_panel.js | 137 ------------------ web/resolver/search/search_source_methods.js | 142 +++++++++++++++++++ 4 files changed, 157 insertions(+), 177 deletions(-) create mode 100644 web/resolver/search/search_source_methods.js diff --git a/tests/test_search_source_methods.mjs b/tests/test_search_source_methods.mjs index e2aa123..a0ccc0e 100644 --- a/tests/test_search_source_methods.mjs +++ b/tests/test_search_source_methods.mjs @@ -1,46 +1,19 @@ -import fs from 'node:fs'; -import path from 'node:path'; import test from 'node:test'; import assert from 'node:assert/strict'; +import { searchSourceMethods } from '../web/resolver/search/search_source_methods.js'; -const projectRoot = path.resolve(import.meta.dirname, '..'); -const searchPanelMethodsSource = fs.readFileSync( - path.join(projectRoot, 'web/resolver/search/search_panel.js'), - 'utf8' -); - -function extractMethod(source, methodName, paramsPattern = '[^)]*') { - const signatureRegex = new RegExp(`\\n\\s+(async\\s+)?${methodName}\\s*\\(${paramsPattern}\\)\\s*\\{`); - const match = signatureRegex.exec(source); - assert.ok(match, `Could not find ${methodName}`); - const isAsync = Boolean(match[1]); - - const parenStart = source.indexOf('(', match.index); - const parenEnd = source.indexOf(')', parenStart); - const params = source.slice(parenStart + 1, parenEnd); - const braceStart = source.indexOf('{', parenEnd); - let depth = 0; - for (let i = braceStart; i < source.length; i += 1) { - const char = source[i]; - if (char === '{') depth += 1; - if (char === '}') depth -= 1; - if (depth === 0) { - return `${isAsync ? 'async ' : ''}function ${methodName}(${params}) ${source.slice(braceStart, i + 1)}`; - } - } - throw new Error(`Could not parse ${methodName}`); -} - -const getSearchSourceLabel = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchSourceLabel')})`); -const getSearchSourceDefinitions = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchSourceDefinitions')})`); -const getSearchResultKeysForSources = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchResultKeysForSources')})`); -const getHashLookupSourcesForSearchSources = eval(`(${extractMethod(searchPanelMethodsSource, 'getHashLookupSourcesForSearchSources')})`); -const clearSearchResultsForSources = eval(`(${extractMethod(searchPanelMethodsSource, 'clearSearchResultsForSources')})`); -const isSearchSourceEnabled = eval(`(${extractMethod(searchPanelMethodsSource, 'isSearchSourceEnabled')})`); -const getSearchSourceDefinition = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchSourceDefinition')})`); -const getSearchSourceEnabledMap = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchSourceEnabledMap')})`); -const getEnabledSearchSources = eval(`(${extractMethod(searchPanelMethodsSource, 'getEnabledSearchSources')})`); -const getSearchSourcesForSelection = eval(`(${extractMethod(searchPanelMethodsSource, 'getSearchSourcesForSelection')})`); +const { + getSearchSourceLabel, + getSearchSourceDefinitions, + getSearchResultKeysForSources, + getHashLookupSourcesForSearchSources, + clearSearchResultsForSources, + isSearchSourceEnabled, + getSearchSourceDefinition, + getSearchSourceEnabledMap, + getEnabledSearchSources, + getSearchSourcesForSelection, +} = searchSourceMethods; function installStorage(values = {}) { const previous = Object.getOwnPropertyDescriptor(globalThis, 'localStorage'); diff --git a/web/resolver/resolver_dialog.js b/web/resolver/resolver_dialog.js index f11d9cf..dcd43d2 100644 --- a/web/resolver/resolver_dialog.js +++ b/web/resolver/resolver_dialog.js @@ -12,6 +12,7 @@ import { optionsMethods } from "./views/options_methods.js"; import { tabsLoadedMethods } from "./views/tabs_loaded_methods.js"; import { downloadTargetMethods } from "./search/download_target_methods.js"; import { searchPanelMethods } from "./search/search_panel.js"; +import { searchSourceMethods } from "./search/search_source_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"; @@ -333,6 +334,7 @@ function applyDialogMethods(...sources) { } applyDialogMethods( + searchSourceMethods, searchPanelMethods, modelInfoMethods, renderFormatMethods, diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 86a2a71..f18fdfc 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -1005,143 +1005,6 @@ export const searchPanelMethods = { return this.hasRenderableSearchState?.(state || {}) || this.hasSearchResults(state?.results || {}); }, - getSearchResultKeysForSources(sources = []) { - const normalized = new Set((Array.isArray(sources) ? sources : [sources]) - .map(source => String(source || '').trim()) - .filter(Boolean)); - if (normalized.has('all')) { - return ['popular', 'model_list', 'huggingface', 'civitai', 'civarchive', 'lora_manager_archive']; - } - - const keys = new Set(); - for (const source of normalized) { - if (source === 'local') { - keys.add('popular'); - keys.add('model_list'); - } else if (source) { - keys.add(source); - } - } - return Array.from(keys); - }, - - getHashLookupSourcesForSearchSources(sources = []) { - const normalized = new Set((Array.isArray(sources) ? sources : [sources]) - .map(source => String(source || '').trim()) - .filter(Boolean)); - const hashSources = ['huggingface', 'civitai', 'civarchive']; - if (normalized.has('all')) return new Set(hashSources); - return new Set(hashSources.filter(source => normalized.has(source))); - }, - - clearSearchResultsForSources(results = {}, sources = []) { - const nextResults = { - popular: results.popular || null, - model_list: results.model_list || null, - huggingface: results.huggingface || null, - civitai: results.civitai || null, - civarchive: results.civarchive || null, - lora_manager_archive: results.lora_manager_archive || null, - custom: Array.isArray(results.custom) ? results.custom : [], - local_hash_matches: Array.isArray(results.local_hash_matches) ? results.local_hash_matches : [] - }; - for (const key of this.getSearchResultKeysForSources(sources)) { - if (key in nextResults) nextResults[key] = null; - } - - const hashSourcesToClear = this.getHashLookupSourcesForSearchSources(sources); - if (hashSourcesToClear.size) { - nextResults.local_hash_matches = nextResults.local_hash_matches.filter(match => { - const source = String(match?.hash_lookup_source || '').trim(); - return source && !hashSourcesToClear.has(source); - }); - } - return nextResults; - }, - - /** - * Convert source ids to readable labels - */ - getSearchSourceLabel(source) { - const labels = { - all: 'Everything', - local: 'Local Database', - huggingface: 'HuggingFace', - civitai: 'CivitAI', - civarchive: 'CivArchive', - lora_manager_archive: 'LoRA Manager Archive', - custom: 'Custom URL' - }; - return labels[source] || source; - }, - - getSearchSourceDefinitions() { - return [ - { - source: 'local', - storageKey: 'ModelResolver.searchSource.localEnabled', - tooltip: 'Searches bundled known-model data before online providers.' - }, - { - source: 'huggingface', - storageKey: 'ModelResolver.searchSource.huggingFaceEnabled', - tooltip: 'Searches Hugging Face when Everything is selected.' - }, - { - source: 'civitai', - storageKey: 'ModelResolver.searchSource.civitaiEnabled', - tooltip: 'Searches CivitAI when Everything is selected.' - }, - { - source: 'civarchive', - storageKey: 'ModelResolver.searchSource.civArchiveEnabled', - tooltip: 'Searches CivArchive when Everything is selected.' - }, - { - source: 'lora_manager_archive', - storageKey: 'ModelResolver.searchSource.loraManagerArchiveEnabled', - tooltip: 'Searches the local LoRA Manager archive when Everything is selected.' - } - ]; - }, - - getSearchSourceDefinition(source) { - return this.getSearchSourceDefinitions().find(def => def.source === source) || null; - }, - - isSearchSourceEnabled(source) { - if (!source || source === 'all') return true; - const definition = this.getSearchSourceDefinition(source); - if (!definition) return true; - return localStorage.getItem(definition.storageKey) !== 'false'; - }, - - isSearchSourceUsable(source) { - return this.isSourceAvailable(source) && this.isSearchSourceEnabled(source); - }, - - getEnabledSearchSources() { - const sources = this.getSearchSourceDefinitions() - .filter(def => this.isSearchSourceUsable(def.source)) - .map(def => def.source); - return sources.length ? sources : ['local']; - }, - - getSearchSourceEnabledMap() { - return this.getSearchSourceDefinitions().reduce((enabled, def) => { - enabled[def.source] = this.isSearchSourceEnabled(def.source); - return enabled; - }, {}); - }, - - getSearchSourcesForSelection(selectedSource, _missing = {}) { - if (selectedSource !== 'all') { - return this.isSearchSourceUsable(selectedSource) ? [selectedSource] : []; - } - - return this.getEnabledSearchSources(); - }, - setSourceProgress(state, source, patch = {}, missing = null, { workflowKey = this.getWorkflowScopedQueueKey() } = {}) { state.sourceProgress = { ...(state.sourceProgress || {}), diff --git a/web/resolver/search/search_source_methods.js b/web/resolver/search/search_source_methods.js new file mode 100644 index 0000000..b76a2e2 --- /dev/null +++ b/web/resolver/search/search_source_methods.js @@ -0,0 +1,142 @@ +import { safeStorage } from "../utils/html_utils.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)); + if (normalized.has('all')) { + return ['popular', 'model_list', 'huggingface', 'civitai', 'civarchive', 'lora_manager_archive']; + } + + const keys = new Set(); + for (const source of normalized) { + if (source === 'local') { + keys.add('popular'); + keys.add('model_list'); + } else if (source) { + keys.add(source); + } + } + return Array.from(keys); + }, + + getHashLookupSourcesForSearchSources(sources = []) { + const normalized = new Set((Array.isArray(sources) ? sources : [sources]) + .map(source => String(source || '').trim()) + .filter(Boolean)); + const hashSources = ['huggingface', 'civitai', 'civarchive']; + if (normalized.has('all')) return new Set(hashSources); + return new Set(hashSources.filter(source => normalized.has(source))); + }, + + clearSearchResultsForSources(results = {}, sources = []) { + const nextResults = { + popular: results.popular || null, + model_list: results.model_list || null, + huggingface: results.huggingface || null, + civitai: results.civitai || null, + civarchive: results.civarchive || null, + lora_manager_archive: results.lora_manager_archive || null, + custom: Array.isArray(results.custom) ? results.custom : [], + local_hash_matches: Array.isArray(results.local_hash_matches) ? results.local_hash_matches : [] + }; + for (const key of this.getSearchResultKeysForSources(sources)) { + if (key in nextResults) nextResults[key] = null; + } + + const hashSourcesToClear = this.getHashLookupSourcesForSearchSources(sources); + if (hashSourcesToClear.size) { + nextResults.local_hash_matches = nextResults.local_hash_matches.filter(match => { + const source = String(match?.hash_lookup_source || '').trim(); + return source && !hashSourcesToClear.has(source); + }); + } + return nextResults; + }, + + /** + * Convert source ids to readable labels + */ + getSearchSourceLabel(source) { + const labels = { + all: 'Everything', + local: 'Local Database', + huggingface: 'HuggingFace', + civitai: 'CivitAI', + civarchive: 'CivArchive', + lora_manager_archive: 'LoRA Manager Archive', + custom: 'Custom URL' + }; + return labels[source] || source; + }, + + getSearchSourceDefinitions() { + return [ + { + source: 'local', + storageKey: 'ModelResolver.searchSource.localEnabled', + tooltip: 'Searches bundled known-model data before online providers.' + }, + { + source: 'huggingface', + storageKey: 'ModelResolver.searchSource.huggingFaceEnabled', + tooltip: 'Searches Hugging Face when Everything is selected.' + }, + { + source: 'civitai', + storageKey: 'ModelResolver.searchSource.civitaiEnabled', + tooltip: 'Searches CivitAI when Everything is selected.' + }, + { + source: 'civarchive', + storageKey: 'ModelResolver.searchSource.civArchiveEnabled', + tooltip: 'Searches CivArchive when Everything is selected.' + }, + { + source: 'lora_manager_archive', + storageKey: 'ModelResolver.searchSource.loraManagerArchiveEnabled', + tooltip: 'Searches the local LoRA Manager archive when Everything is selected.' + } + ]; + }, + + getSearchSourceDefinition(source) { + return this.getSearchSourceDefinitions().find(def => def.source === source) || null; + }, + + isSearchSourceEnabled(source) { + if (!source || source === 'all') return true; + const definition = this.getSearchSourceDefinition(source); + if (!definition) return true; + return localStorage.getItem(definition.storageKey) !== 'false'; + }, + + isSearchSourceUsable(source) { + return this.isSourceAvailable(source) && this.isSearchSourceEnabled(source); + }, + + getEnabledSearchSources() { + const sources = this.getSearchSourceDefinitions() + .filter(def => this.isSearchSourceUsable(def.source)) + .map(def => def.source); + return sources.length ? sources : ['local']; + }, + + getSearchSourceEnabledMap() { + return this.getSearchSourceDefinitions().reduce((enabled, def) => { + enabled[def.source] = this.isSearchSourceEnabled(def.source); + return enabled; + }, {}); + }, + + getSearchSourcesForSelection(selectedSource, _missing = {}) { + if (selectedSource !== 'all') { + return this.isSearchSourceUsable(selectedSource) ? [selectedSource] : []; + } + + return this.getEnabledSearchSources(); + }, +};