diff --git a/web/resolver/actions/queue_methods.js b/web/resolver/actions/queue_methods.js index 304c1e0..2a4c90c 100644 --- a/web/resolver/actions/queue_methods.js +++ b/web/resolver/actions/queue_methods.js @@ -2290,7 +2290,7 @@ export const queueMethods = { }, normalizeResolvedSelectionToken(value = '', { basename = false, stem = false } = {}) { - let text = String(value || '').trim().replace(/\\/g, '/').toLowerCase(); + let text = this.normalizePathToForward(value).toLowerCase(); if (!text) return ''; if (basename) { text = text.split('/').pop() || text; diff --git a/web/resolver/search/download_target_methods.js b/web/resolver/search/download_target_methods.js index b839dc7..a1f94b7 100644 --- a/web/resolver/search/download_target_methods.js +++ b/web/resolver/search/download_target_methods.js @@ -672,8 +672,7 @@ export const downloadTargetMethods = { }, normalizeDownloadSubfolderPath(value = '') { - return String(value || '') - .replace(/\//g, '\\') + return this.normalizePathToBackward(value) .split('\\') .map(part => part.trim()) .filter(part => part && part !== '.' && part !== '..') @@ -910,8 +909,7 @@ export const downloadTargetMethods = { }, normalizeDownloadPathTemplate(template = '') { - return String(template || '') - .replace(/\\/g, '/') + return this.normalizePathToForward(template) .split('/') .map(part => part.trim()) .filter(part => part && part !== '.' && part !== '..') @@ -1027,8 +1025,7 @@ export const downloadTargetMethods = { }, normalizeTemplateSubfolder(value = '') { - return String(value || '') - .replace(/\\/g, '/') + return this.normalizePathToForward(value) .split('/') .map(part => this.sanitizeDownloadPathSegment(part)) .filter(part => part && part !== '.' && part !== '..') @@ -2004,8 +2001,7 @@ export const downloadTargetMethods = { }); }; - const normalizeSubfolderPath = (value = '') => String(value || '') - .replace(/\//g, '\\') + const normalizeSubfolderPath = (value = '') => this.normalizePathToBackward(value) .split('\\') .map(part => part.trim()) .filter(part => part && part !== '.') diff --git a/web/resolver/shell/workflow_update_methods.js b/web/resolver/shell/workflow_update_methods.js index ea1a462..5fe9af4 100644 --- a/web/resolver/shell/workflow_update_methods.js +++ b/web/resolver/shell/workflow_update_methods.js @@ -191,7 +191,7 @@ export const workflowUpdateMethods = { }, normalizeComfyCatalogValue(value = '') { - return String(value || '').trim().replace(/\\/g, '/'); + return this.normalizePathToForward(value); }, applyComfyNodeDefs(nodeDefs = {}) { diff --git a/web/resolver/utils/render_format_methods.js b/web/resolver/utils/render_format_methods.js index 4b566fa..645ded0 100644 --- a/web/resolver/utils/render_format_methods.js +++ b/web/resolver/utils/render_format_methods.js @@ -322,5 +322,13 @@ export const renderFormatMethods = { estimateTextWidth(value, charPx = 6, minPx = 40, maxPx = 180) { const length = String(value || '').length; return Math.max(minPx, Math.min(maxPx, Math.ceil(length * charPx))); + }, + + normalizePathToForward(value) { + return String(value || '').trim().replace(/\\/g, '/'); + }, + + normalizePathToBackward(value) { + return String(value || '').trim().replace(/\//g, '\\'); } }; diff --git a/web/resolver/views/missing_browser_methods.js b/web/resolver/views/missing_browser_methods.js index 3c1a924..1ca7561 100644 --- a/web/resolver/views/missing_browser_methods.js +++ b/web/resolver/views/missing_browser_methods.js @@ -140,7 +140,7 @@ export const missingBrowserMethods = { } const usesBackslash = cleanPath.includes('\\'); - const normalizedPath = cleanPath.replace(/\\/g, '/').replace(/\/+$/g, ''); + const normalizedPath = this.normalizePathToForward(cleanPath).replace(/\/+$/g, ''); const parts = normalizedPath.split('/').filter(Boolean); const leaf = (parts[parts.length - 1] || '').toLowerCase(); const parent = (parts[parts.length - 2] || '').toLowerCase(); @@ -151,7 +151,7 @@ export const missingBrowserMethods = { if (rawSuffix && leaf === rawSuffix && parent === canonicalLeaf) { const parentPath = parts.slice(0, -1).join('/'); - return usesBackslash ? parentPath.replace(/\//g, '\\') : parentPath; + return usesBackslash ? this.normalizePathToBackward(parentPath) : parentPath; } return cleanPath; }, @@ -1274,8 +1274,7 @@ export const missingBrowserMethods = { const getAllModels = () => Array.isArray(this.allModels) ? this.allModels : []; let localModelLoadToken = 0; - const normalizeModelPath = (value = '') => String(value || '') - .replace(/\//g, '\\') + const normalizeModelPath = (value = '') => this.normalizePathToBackward(value) .split('\\') .map(part => part.trim()) .filter(Boolean) diff --git a/web/resolver/views/model_info_methods.js b/web/resolver/views/model_info_methods.js index 0a7370e..c60822e 100644 --- a/web/resolver/views/model_info_methods.js +++ b/web/resolver/views/model_info_methods.js @@ -325,7 +325,7 @@ export const modelInfoMethods = { getContextMenuSourceLookupKey(model = {}) { const path = String(this.getContextMenuSourceLookupPath(model) || '').trim(); - return path ? path.replace(/\\/g, '/').toLowerCase() : ''; + return path ? this.normalizePathToForward(path).toLowerCase() : ''; }, getContextMenuSourceLookupModel(model = {}) { @@ -1296,14 +1296,14 @@ export const modelInfoMethods = { if (!location) { const fullPath = data.file_path || data.filePath || data.resolved_path || data.resolvedPath || data.full_path || data.fullPath || data.path || ''; - const normalizedFullPath = String(fullPath || '').replace(/\\/g, '/'); + const normalizedFullPath = this.normalizePathToForward(fullPath); const lastSlash = normalizedFullPath.lastIndexOf('/'); if (lastSlash > 0) { location = normalizedFullPath.slice(0, lastSlash + 1); } } - location = String(location || '').trim().replace(/\\/g, '/'); + location = this.normalizePathToForward(location); if (location && !location.endsWith('/')) { location += '/'; } diff --git a/web/resolver/views/options_methods.js b/web/resolver/views/options_methods.js index 4dd81bf..8f70647 100644 --- a/web/resolver/views/options_methods.js +++ b/web/resolver/views/options_methods.js @@ -1215,47 +1215,74 @@ export const optionsMethods = { } }; - const loadModelListStatus = async (checkRemote = false) => { + const executeOptionsTask = async ({ + setBusy, + stateEl, + messageEl, + loadingText, + startingMessageText, + endpoint, + fetchOptions = {}, + errorContext, + successCallback, + successAction, + errorFallbackText, + notificationErrorText + }) => { try { - setModelListBusy(true); - if (modelListStateEl) { - modelListStateEl.textContent = checkRemote ? 'Checking GitHub...' : 'Loading...'; + setBusy(true); + if (stateEl) stateEl.textContent = loadingText; + if (messageEl && startingMessageText) messageEl.textContent = startingMessageText; + const data = await this.fetchJson(endpoint, fetchOptions, errorContext); + if (successAction) { + await successAction(data); } - const data = await this.fetchJson(`/model_resolver/model-list/status${checkRemote ? '?check_remote=1' : ''}`, {}, 'Load model list status'); - renderModelListStatus(data, checkRemote); + successCallback(data); return data; } catch (error) { - console.error('Model Resolver: model-list status error:', error); - if (modelListStateEl) modelListStateEl.textContent = 'Error'; - if (modelListMessageEl) modelListMessageEl.textContent = error.message || 'Failed to read Local Database status.'; + console.error(`Model Resolver: ${errorContext} error:`, error); + if (stateEl) stateEl.textContent = 'Error'; + if (messageEl) messageEl.textContent = error.message || errorFallbackText; + if (notificationErrorText) { + this.showNotification(notificationErrorText, 'error'); + } return null; } finally { - setModelListBusy(false); + setBusy(false); } }; - const updateModelList = async () => { - try { - setModelListBusy(true); - if (modelListStateEl) modelListStateEl.textContent = 'Updating...'; - if (modelListMessageEl) modelListMessageEl.textContent = 'Downloading latest ComfyUI-Manager model-list.json...'; - const data = await this.fetchJson('/model_resolver/model-list/update', { - method: 'POST' - }, 'Update model list'); + const loadModelListStatus = (checkRemote = false) => executeOptionsTask({ + setBusy: setModelListBusy, + stateEl: modelListStateEl, + loadingText: checkRemote ? 'Checking GitHub...' : 'Loading...', + endpoint: `/model_resolver/model-list/status${checkRemote ? '?check_remote=1' : ''}`, + errorContext: 'Model-list status', + successCallback: (data) => renderModelListStatus(data, checkRemote), + errorFallbackText: 'Failed to read Local Database status.' + }); + + const updateModelList = () => executeOptionsTask({ + setBusy: setModelListBusy, + stateEl: modelListStateEl, + messageEl: modelListMessageEl, + loadingText: 'Updating...', + startingMessageText: 'Downloading latest ComfyUI-Manager model-list.json...', + endpoint: '/model_resolver/model-list/update', + fetchOptions: { method: 'POST' }, + errorContext: 'Update model list', + successAction: async () => { await this.clearSearchCaches(); + }, + successCallback: (data) => { renderModelListStatus(data, false); if (modelListStateEl) modelListStateEl.textContent = 'Updated'; if (modelListMessageEl) modelListMessageEl.textContent = `Local Database updated. ${Number(data.local_count || 0).toLocaleString()} models loaded.`; this.showNotification('Local Database updated', 'success'); - } catch (error) { - console.error('Model Resolver: model-list update error:', error); - if (modelListStateEl) modelListStateEl.textContent = 'Error'; - if (modelListMessageEl) modelListMessageEl.textContent = error.message || 'Failed to update Local Database.'; - this.showNotification('Local Database update failed', 'error'); - } finally { - setModelListBusy(false); - } - }; + }, + errorFallbackText: 'Failed to update Local Database.', + notificationErrorText: 'Local Database update failed' + }); const renderBaseModelsStatus = (data = {}, checkedRemote = false) => { if (baseModelsCountEl) { @@ -1286,24 +1313,15 @@ export const optionsMethods = { } }; - const loadBaseModelsStatus = async (checkRemote = false) => { - try { - setBaseModelsBusy(true); - if (baseModelsStateEl) { - baseModelsStateEl.textContent = checkRemote ? 'Checking CivitAI...' : 'Loading...'; - } - const data = await this.fetchJson(`/model_resolver/base-models/status${checkRemote ? '?check_remote=1' : ''}`, {}, 'Load base models status'); - renderBaseModelsStatus(data, checkRemote); - return data; - } catch (error) { - console.error('Model Resolver: base-models status error:', error); - if (baseModelsStateEl) baseModelsStateEl.textContent = 'Error'; - if (baseModelsMessageEl) baseModelsMessageEl.textContent = error.message || 'Failed to read Base Models status.'; - return null; - } finally { - setBaseModelsBusy(false); - } - }; + const loadBaseModelsStatus = (checkRemote = false) => executeOptionsTask({ + setBusy: setBaseModelsBusy, + stateEl: baseModelsStateEl, + loadingText: checkRemote ? 'Checking CivitAI...' : 'Loading...', + endpoint: `/model_resolver/base-models/status${checkRemote ? '?check_remote=1' : ''}`, + errorContext: 'Load base models status', + successCallback: (data) => renderBaseModelsStatus(data, checkRemote), + errorFallbackText: 'Failed to read Base Models status.' + }); const formatNewBaseModelsSummary = (models = [], maxVisible = 6) => { const names = Array.isArray(models) @@ -1317,19 +1335,21 @@ export const optionsMethods = { return `${visible.join(', ')}${suffix}`; }; - const updateBaseModels = async () => { - try { - setBaseModelsBusy(true); - if (baseModelsStateEl) baseModelsStateEl.textContent = 'Updating...'; - if (baseModelsMessageEl) baseModelsMessageEl.textContent = 'Downloading latest base models from CivitAI...'; - const data = await this.fetchJson('/model_resolver/base-models/update', { - method: 'POST' - }, 'Update base models'); + const updateBaseModels = () => executeOptionsTask({ + setBusy: setBaseModelsBusy, + stateEl: baseModelsStateEl, + messageEl: baseModelsMessageEl, + loadingText: 'Updating...', + startingMessageText: 'Downloading latest base models from CivitAI...', + endpoint: '/model_resolver/base-models/update', + fetchOptions: { method: 'POST' }, + errorContext: 'Update base models', + successAction: async () => { await this.clearSearchCaches(); - // Invalidate the in-memory cache so the base model dropdown - // reloads fresh data from the server next time it is opened. this.baseModels = null; await this.ensureBaseModelsLoaded(); + }, + successCallback: (data) => { renderBaseModelsStatus(data, false); if (baseModelsStateEl) baseModelsStateEl.textContent = 'Updated'; const newModelsSummary = formatNewBaseModelsSummary(data.new_models_added_list); @@ -1344,15 +1364,10 @@ export const optionsMethods = { : 'Base Models updated. No new models added.', 'success' ); - } catch (error) { - console.error('Model Resolver: base-models update error:', error); - if (baseModelsStateEl) baseModelsStateEl.textContent = 'Error'; - if (baseModelsMessageEl) baseModelsMessageEl.textContent = error.message || 'Failed to update Base Models.'; - this.showNotification('Base Models update failed', 'error'); - } finally { - setBaseModelsBusy(false); - } - }; + }, + errorFallbackText: 'Failed to update Base Models.', + notificationErrorText: 'Base Models update failed' + }); const renderHfIndexStatus = (data = {}) => { if (hfIndexCountEl) { @@ -1385,48 +1400,42 @@ export const optionsMethods = { } }; - const loadHfIndexStatus = async () => { - try { - setHfIndexBusy(true); - if (hfIndexStateEl) hfIndexStateEl.textContent = 'Loading...'; - const data = await this.fetchJson('/model_resolver/huggingface/author-index/status', {}, 'Load HuggingFace index status'); - renderHfIndexStatus(data); - return data; - } catch (error) { - console.error('Model Resolver: HuggingFace index status error:', error); - if (hfIndexStateEl) hfIndexStateEl.textContent = 'Error'; - if (hfIndexMessageEl) hfIndexMessageEl.textContent = error.message || 'Failed to read HuggingFace index status.'; - return null; - } finally { - setHfIndexBusy(false); - } - }; + const loadHfIndexStatus = () => executeOptionsTask({ + setBusy: setHfIndexBusy, + stateEl: hfIndexStateEl, + loadingText: 'Loading...', + endpoint: '/model_resolver/huggingface/author-index/status', + errorContext: 'Load HuggingFace index status', + successCallback: renderHfIndexStatus, + errorFallbackText: 'Failed to read HuggingFace index status.' + }); - const refreshHfIndex = async () => { - try { - setHfIndexBusy(true); - if (hfIndexStateEl) hfIndexStateEl.textContent = 'Refreshing...'; - if (hfIndexMessageEl) hfIndexMessageEl.textContent = 'Downloading Comfy-Org file index from HuggingFace...'; - const data = await this.fetchJson('/model_resolver/huggingface/author-index/refresh', { - method: 'POST', - body: JSON.stringify({ hf_token: hfInput?.value || '' }) - }, 'Refresh HuggingFace index'); + const refreshHfIndex = () => executeOptionsTask({ + setBusy: setHfIndexBusy, + stateEl: hfIndexStateEl, + messageEl: hfIndexMessageEl, + loadingText: 'Refreshing...', + startingMessageText: 'Downloading Comfy-Org file index from HuggingFace...', + endpoint: '/model_resolver/huggingface/author-index/refresh', + fetchOptions: { + method: 'POST', + body: JSON.stringify({ hf_token: hfInput?.value || '' }) + }, + errorContext: 'Refresh HuggingFace index', + successAction: async (data) => { if (data?.success === false) { throw new Error(data.error || 'Refresh failed'); } await this.clearSearchCaches(); + }, + successCallback: (data) => { renderHfIndexStatus(data); if (hfIndexStateEl) hfIndexStateEl.textContent = 'Refreshed'; this.showNotification('HuggingFace Comfy-Org index refreshed', 'success'); - } catch (error) { - console.error('Model Resolver: HuggingFace index refresh error:', error); - if (hfIndexStateEl) hfIndexStateEl.textContent = 'Error'; - if (hfIndexMessageEl) hfIndexMessageEl.textContent = error.message || 'Failed to refresh HuggingFace index.'; - this.showNotification('HuggingFace index refresh failed', 'error'); - } finally { - setHfIndexBusy(false); - } - }; + }, + errorFallbackText: 'Failed to refresh HuggingFace index.', + notificationErrorText: 'HuggingFace index refresh failed' + }); const clearAllCachesFromOptions = async () => { try {