refactor: consolidate shared utilities and relocate view-specific settings logic

- Moved formatting helpers (formatBytes, formatDuration, getDownloadEtaText,
  formatDownloadProgressMeta) and getLocalMatchDisplayInfo from
  resolve_download_methods.js and missing_browser_methods.js into
  render_format_methods.js for shared reuse.
- Relocated settings-related methods (getStoredTokens,
  applyFrontendLoggingPreference, loadSettingsFromServer) and their
  log_system imports from download_target_methods.js into
  options_methods.js, where they are actually consumed by the options UI.
- Simplified missing_browser_methods.js to use
  getLocalMatchDisplayInfo instead of inline local-match code.
This commit is contained in:
Dariusz L
2026-06-24 23:42:34 +02:00
parent 9d3f18f288
commit dcac1b7c8a
5 changed files with 205 additions and 199 deletions
@@ -2640,59 +2640,5 @@ export const resolveDownloadMethods = {
this.updateQueuePanel?.();
this.showNotification('Download failed: ' + error.message, 'error');
}
},
/**
* Format bytes to human readable string
*/
formatBytes(bytes) {
const value = Number(bytes) || 0;
if (!Number.isFinite(value) || value <= 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.min(sizes.length - 1, Math.floor(Math.log(value) / Math.log(k)));
return parseFloat((value / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
},
formatDuration(seconds) {
const totalSeconds = Math.max(0, Math.ceil(Number(seconds) || 0));
if (!Number.isFinite(totalSeconds) || totalSeconds <= 0) return '';
if (totalSeconds < 60) return `${totalSeconds}s`;
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const remainingSeconds = totalSeconds % 60;
if (hours > 0) {
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
}
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
},
getDownloadEtaText(progress = {}) {
const totalSize = Number(progress.total_size) || 0;
const downloaded = Number(progress.downloaded) || 0;
const speed = Number(progress.speed) || 0;
if (totalSize <= 0 || downloaded <= 0 || speed <= 0 || downloaded >= totalSize) return '';
const duration = this.formatDuration((totalSize - downloaded) / speed);
return duration ? `ETA ${duration}` : '';
},
formatDownloadProgressMeta(progress = {}) {
const parts = [];
const speed = Number(progress.speed) || 0;
if (speed > 0) {
parts.push(`${this.formatBytes(speed)}/s`);
}
const eta = this.getDownloadEtaText(progress);
if (eta) {
parts.push(eta);
}
return parts.join(' | ');
}
};
@@ -1,8 +1,6 @@
import { app } from "../../../../../scripts/app.js";
import { api } from "../../../../../scripts/api.js";
import { $el } from "../../../../../scripts/ui.js";
import { LOG_LEVEL as DEFAULT_FRONTEND_LOG_LEVEL } from "../../log_system/config.js";
import { logger as frontendLogger } from "../../log_system/logger.js";
import { getSvgIcon } from "../../utils/icon_utils.js";
import { createFloatingTreePicker } from "../utils/tree_picker.js";
export const downloadTargetMethods = {
@@ -2407,138 +2405,6 @@ export const downloadTargetMethods = {
.catch(() => refreshCategoryTooltip());
},
getStoredTokens() {
const civitaiCandidateLimitRaw = parseInt(localStorage.getItem('ModelResolver.civitaiCandidateLimit') || '5', 10);
const civitai_candidate_limit = Number.isFinite(civitaiCandidateLimitRaw)
? Math.min(20, Math.max(1, civitaiCandidateLimitRaw))
: 5;
const search_source_enabled = this.getSearchSourceEnabledMap();
const storedFrontendLogsEnabled = localStorage.getItem('ModelResolver.frontendLogsEnabled');
const storedBackendLogsEnabled = localStorage.getItem('ModelResolver.backendLogsEnabled');
const storedFrontendLogLevel = localStorage.getItem('ModelResolver.frontendLogLevel');
const storedBackendLogLevel = localStorage.getItem('ModelResolver.backendLogLevel');
return {
civitai_key: localStorage.getItem('ModelResolver.civitaiApiKey') || '',
civitai_session_token: localStorage.getItem('ModelResolver.civitaiSessionToken') || '',
hf_token: localStorage.getItem('ModelResolver.huggingFaceToken') || '',
brave_search_api_key: localStorage.getItem('ModelResolver.braveSearchApiKey') || '',
civitai_use_trpc_search: localStorage.getItem('ModelResolver.civitaiUseTrpcSearch') !== 'false',
civitai_use_html_fallback: localStorage.getItem('ModelResolver.civitaiUseHtmlFallback') !== 'false',
hf_use_api_search: localStorage.getItem('ModelResolver.hfUseApiSearch') !== 'false',
hf_use_comfy_org_fallback: localStorage.getItem('ModelResolver.hfUseComfyOrgFallback') !== 'false',
hf_use_brave_fallback: localStorage.getItem('ModelResolver.hfUseBraveFallback') !== 'false',
auto_fill_base_model: localStorage.getItem('ModelResolver.autoFillBaseModel') !== 'false',
auto_fill_subfolder: localStorage.getItem('ModelResolver.autoFillSubfolder') !== 'false',
auto_refresh_comfy_models_after_apply: localStorage.getItem('ModelResolver.autoRefreshComfyModelsAfterApply') !== 'false',
frontend_logs_enabled: storedFrontendLogsEnabled === null
? frontendLogger.enabled !== false
: storedFrontendLogsEnabled !== 'false',
backend_logs_enabled: storedBackendLogsEnabled === null
? true
: storedBackendLogsEnabled !== 'false',
frontend_log_level: storedFrontendLogLevel || DEFAULT_FRONTEND_LOG_LEVEL,
backend_log_level: storedBackendLogLevel || 'DEBUG',
civitai_candidate_limit,
search_source_enabled,
download_path_mode: this.getDownloadPathMode(),
download_path_templates: this.getDownloadPathTemplates(),
base_model_path_mappings: this.getBaseModelPathMappings(),
...this.getDefaultRootSettings()
};
},
applyFrontendLoggingPreference(enabled = true, levelName = 'DEBUG') {
frontendLogger.setEnabled(Boolean(enabled));
frontendLogger.setGlobalAndModuleLevel(frontendLogger.normalizeLevel(levelName));
},
/**
* Fetch settings saved on the server and sync them into localStorage.
* Call this once when the dialog initialises so every browser gets the
* same tokens without the user having to re-enter them.
*/
async loadSettingsFromServer() {
try {
const resp = await api.fetchApi('/model_resolver/settings');
if (!resp.ok) {
const tokens = this.getStoredTokens();
this.applyFrontendLoggingPreference(tokens.frontend_logs_enabled, tokens.frontend_log_level);
return;
}
const data = await resp.json();
if (!data || typeof data !== 'object') {
const tokens = this.getStoredTokens();
this.applyFrontendLoggingPreference(tokens.frontend_logs_enabled, tokens.frontend_log_level);
return;
}
// Helper: write to localStorage only when the value from server is
// non-empty, so we don't overwrite a key the user already has locally.
const sync = (localKey, serverValue) => {
if (serverValue !== undefined && serverValue !== null && serverValue !== '') {
localStorage.setItem(localKey, serverValue);
}
};
sync('ModelResolver.civitaiApiKey', data.civitai_key);
sync('ModelResolver.civitaiSessionToken', data.civitai_session_token);
sync('ModelResolver.huggingFaceToken', data.hf_token);
sync('ModelResolver.braveSearchApiKey', data.brave_search_api_key);
if (data.civitai_use_trpc_search !== undefined)
localStorage.setItem('ModelResolver.civitaiUseTrpcSearch', data.civitai_use_trpc_search ? 'true' : 'false');
if (data.civitai_use_html_fallback !== undefined)
localStorage.setItem('ModelResolver.civitaiUseHtmlFallback', data.civitai_use_html_fallback ? 'true' : 'false');
if (data.hf_use_api_search !== undefined)
localStorage.setItem('ModelResolver.hfUseApiSearch', data.hf_use_api_search ? 'true' : 'false');
if (data.hf_use_comfy_org_fallback !== undefined)
localStorage.setItem('ModelResolver.hfUseComfyOrgFallback', data.hf_use_comfy_org_fallback ? 'true' : 'false');
if (data.hf_use_brave_fallback !== undefined)
localStorage.setItem('ModelResolver.hfUseBraveFallback', data.hf_use_brave_fallback ? 'true' : 'false');
if (data.auto_fill_base_model !== undefined)
localStorage.setItem('ModelResolver.autoFillBaseModel', data.auto_fill_base_model ? 'true' : 'false');
if (data.auto_fill_subfolder !== undefined)
localStorage.setItem('ModelResolver.autoFillSubfolder', data.auto_fill_subfolder ? 'true' : 'false');
if (data.auto_refresh_comfy_models_after_apply !== undefined)
localStorage.setItem('ModelResolver.autoRefreshComfyModelsAfterApply', data.auto_refresh_comfy_models_after_apply ? 'true' : 'false');
if (data.download_path_mode !== undefined)
localStorage.setItem('ModelResolver.downloadPathMode', this.normalizeDownloadPathMode(data.download_path_mode));
if (data.download_path_templates !== undefined)
localStorage.setItem('ModelResolver.downloadPathTemplates', JSON.stringify(data.download_path_templates || {}));
if (data.base_model_path_mappings !== undefined)
localStorage.setItem('ModelResolver.baseModelPathMappings', JSON.stringify(data.base_model_path_mappings || {}));
this.getDefaultRootCategoryDefinitions().forEach((item) => {
if (data[item.settingKey] !== undefined) {
localStorage.setItem(item.storageKey, String(data[item.settingKey] || ''));
}
});
if (data.civitai_candidate_limit !== undefined)
localStorage.setItem('ModelResolver.civitaiCandidateLimit', `${data.civitai_candidate_limit}`);
if (data.frontend_logs_enabled !== undefined)
localStorage.setItem('ModelResolver.frontendLogsEnabled', data.frontend_logs_enabled ? 'true' : 'false');
if (data.backend_logs_enabled !== undefined)
localStorage.setItem('ModelResolver.backendLogsEnabled', data.backend_logs_enabled ? 'true' : 'false');
if (data.frontend_log_level !== undefined)
localStorage.setItem('ModelResolver.frontendLogLevel', String(data.frontend_log_level || 'DEBUG').toUpperCase());
if (data.backend_log_level !== undefined)
localStorage.setItem('ModelResolver.backendLogLevel', String(data.backend_log_level || 'DEBUG').toUpperCase());
// Source-enabled flags stored as a nested object
if (data.search_source_enabled && typeof data.search_source_enabled === 'object') {
Object.entries(data.search_source_enabled).forEach(([key, val]) => {
if (key) localStorage.setItem(key, val ? 'true' : 'false');
});
}
const tokens = this.getStoredTokens();
this.applyFrontendLoggingPreference(tokens.frontend_logs_enabled, tokens.frontend_log_level);
} catch (err) {
console.warn('Model Resolver: could not load settings from server, using localStorage only.', err);
const tokens = this.getStoredTokens();
this.applyFrontendLoggingPreference(tokens.frontend_logs_enabled, tokens.frontend_log_level);
}
},
clearFrontendSearchCaches() {
for (const state of this.searchResultCache.values()) {
state.activeSearchRunId = null;
+69 -1
View File
@@ -1,4 +1,4 @@
import { app } from "../../../../../scripts/app.js";
import { app } from "../../../../../scripts/app.js";
import { api } from "../../../../../scripts/api.js";
import { $el } from "../../../../../scripts/ui.js";
import { getSvgIcon } from "../../utils/icon_utils.js";
@@ -242,5 +242,73 @@ export const renderFormatMethods = {
await new Promise(resolve => setTimeout(resolve, 250));
}
},
/**
* Get display information for the best local match of a missing model
* @param {object} missing - The missing model object
* @returns {object} Match display metadata
*/
getLocalMatchDisplayInfo(missing) {
const bestMatch = this.getBestLocalMatch?.(missing, 70) || null;
const confidence = bestMatch ? Number(bestMatch.confidence || 0) : 0;
const matchName = bestMatch?.model?.relative_path || bestMatch?.filename || bestMatch?.path || '';
const matchDisplay = matchName || 'No local match';
const matchClass = confidence === 100 ? 'exact' : (bestMatch ? 'partial' : 'none');
return { bestMatch, confidence, matchDisplay, matchClass };
},
/**
* Format bytes to human readable string
*/
formatBytes(bytes) {
const value = Number(bytes) || 0;
if (!Number.isFinite(value) || value <= 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.min(sizes.length - 1, Math.floor(Math.log(value) / Math.log(k)));
return parseFloat((value / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
},
formatDuration(seconds) {
const totalSeconds = Math.max(0, Math.ceil(Number(seconds) || 0));
if (!Number.isFinite(totalSeconds) || totalSeconds <= 0) return '';
if (totalSeconds < 60) return `${totalSeconds}s`;
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const remainingSeconds = totalSeconds % 60;
if (hours > 0) {
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
}
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
},
getDownloadEtaText(progress = {}) {
const totalSize = Number(progress.total_size) || 0;
const downloaded = Number(progress.downloaded) || 0;
const speed = Number(progress.speed) || 0;
if (totalSize <= 0 || downloaded <= 0 || speed <= 0 || downloaded >= totalSize) return '';
const duration = this.formatDuration((totalSize - downloaded) / speed);
return duration ? `ETA ${duration}` : '';
},
formatDownloadProgressMeta(progress = {}) {
const parts = [];
const speed = Number(progress.speed) || 0;
if (speed > 0) {
parts.push(`${this.formatBytes(speed)}/s`);
}
const eta = this.getDownloadEtaText(progress);
if (eta) {
parts.push(eta);
}
return parts.join(' | ');
}
};
+2 -10
View File
@@ -386,11 +386,7 @@ export const missingBrowserMethods = {
const isBatchSelected = this.batchSelectedMissingKeys?.has(key);
const isResolved = this.isMissingModelResolved(missing);
const filename = this.getMissingFilename(missing);
const bestMatch = this.getBestLocalMatch(missing, 70);
const confidence = bestMatch ? Number(bestMatch.confidence || 0) : 0;
const matchName = bestMatch?.model?.relative_path || bestMatch?.filename || bestMatch?.path || '';
const matchDisplay = matchName || 'No local match';
const matchClass = confidence === 100 ? 'exact' : (bestMatch ? 'partial' : 'none');
const { bestMatch, confidence, matchDisplay, matchClass } = this.getLocalMatchDisplayInfo(missing);
const typeLabel = missing.category ? this.getCategoryDisplayName(missing.category) : 'unknown';
const typeColorClass = this.getModelTypeColorClass(missing.category || typeLabel);
const nodeDisplay = this.getMissingNodeDisplay(missing);
@@ -1609,11 +1605,7 @@ export const missingBrowserMethods = {
.find(item => item.dataset.missingKey === missingKey);
if (!row) return;
const bestMatch = this.getBestLocalMatch(missing, 70);
const confidence = bestMatch ? Number(bestMatch.confidence || 0) : 0;
const matchName = bestMatch?.model?.relative_path || bestMatch?.filename || bestMatch?.path || '';
const matchDisplay = matchName || 'No local match';
const matchClass = confidence === 100 ? 'exact' : (bestMatch ? 'partial' : 'none');
const { bestMatch, confidence, matchDisplay, matchClass } = this.getLocalMatchDisplayInfo(missing);
const bestEl = row.querySelector('.mr-missing-row-best');
if (bestEl) {
+134
View File
@@ -2,6 +2,8 @@ import { app } from "../../../../../scripts/app.js";
import { api } from "../../../../../scripts/api.js";
import { $el } from "../../../../../scripts/ui.js";
import { getSvgIcon } from "../../utils/icon_utils.js";
import { LOG_LEVEL as DEFAULT_FRONTEND_LOG_LEVEL } from "../../log_system/config.js";
import { logger as frontendLogger } from "../../log_system/logger.js";
export const optionsMethods = {
displayOptions() {
if (!this.contentElement) return;
@@ -1693,5 +1695,137 @@ export const optionsMethods = {
});
}
},
getStoredTokens() {
const civitaiCandidateLimitRaw = parseInt(localStorage.getItem('ModelResolver.civitaiCandidateLimit') || '5', 10);
const civitai_candidate_limit = Number.isFinite(civitaiCandidateLimitRaw)
? Math.min(20, Math.max(1, civitaiCandidateLimitRaw))
: 5;
const search_source_enabled = this.getSearchSourceEnabledMap();
const storedFrontendLogsEnabled = localStorage.getItem('ModelResolver.frontendLogsEnabled');
const storedBackendLogsEnabled = localStorage.getItem('ModelResolver.backendLogsEnabled');
const storedFrontendLogLevel = localStorage.getItem('ModelResolver.frontendLogLevel');
const storedBackendLogLevel = localStorage.getItem('ModelResolver.backendLogLevel');
return {
civitai_key: localStorage.getItem('ModelResolver.civitaiApiKey') || '',
civitai_session_token: localStorage.getItem('ModelResolver.civitaiSessionToken') || '',
hf_token: localStorage.getItem('ModelResolver.huggingFaceToken') || '',
brave_search_api_key: localStorage.getItem('ModelResolver.braveSearchApiKey') || '',
civitai_use_trpc_search: localStorage.getItem('ModelResolver.civitaiUseTrpcSearch') !== 'false',
civitai_use_html_fallback: localStorage.getItem('ModelResolver.civitaiUseHtmlFallback') !== 'false',
hf_use_api_search: localStorage.getItem('ModelResolver.hfUseApiSearch') !== 'false',
hf_use_comfy_org_fallback: localStorage.getItem('ModelResolver.hfUseComfyOrgFallback') !== 'false',
hf_use_brave_fallback: localStorage.getItem('ModelResolver.hfUseBraveFallback') !== 'false',
auto_fill_base_model: localStorage.getItem('ModelResolver.autoFillBaseModel') !== 'false',
auto_fill_subfolder: localStorage.getItem('ModelResolver.autoFillSubfolder') !== 'false',
auto_refresh_comfy_models_after_apply: localStorage.getItem('ModelResolver.autoRefreshComfyModelsAfterApply') !== 'false',
frontend_logs_enabled: storedFrontendLogsEnabled === null
? frontendLogger.enabled !== false
: storedFrontendLogsEnabled !== 'false',
backend_logs_enabled: storedBackendLogsEnabled === null
? true
: storedBackendLogsEnabled !== 'false',
frontend_log_level: storedFrontendLogLevel || DEFAULT_FRONTEND_LOG_LEVEL,
backend_log_level: storedBackendLogLevel || 'DEBUG',
civitai_candidate_limit,
search_source_enabled,
download_path_mode: this.getDownloadPathMode(),
download_path_templates: this.getDownloadPathTemplates(),
base_model_path_mappings: this.getBaseModelPathMappings(),
...this.getDefaultRootSettings()
};
},
applyFrontendLoggingPreference(enabled = true, levelName = 'DEBUG') {
frontendLogger.setEnabled(Boolean(enabled));
frontendLogger.setGlobalAndModuleLevel(frontendLogger.normalizeLevel(levelName));
},
/**
* Fetch settings saved on the server and sync them into localStorage.
* Call this once when the dialog initialises so every browser gets the
* same tokens without the user having to re-enter them.
*/
async loadSettingsFromServer() {
try {
const resp = await api.fetchApi('/model_resolver/settings');
if (!resp.ok) {
const tokens = this.getStoredTokens();
this.applyFrontendLoggingPreference(tokens.frontend_logs_enabled, tokens.frontend_log_level);
return;
}
const data = await resp.json();
if (!data || typeof data !== 'object') {
const tokens = this.getStoredTokens();
this.applyFrontendLoggingPreference(tokens.frontend_logs_enabled, tokens.frontend_log_level);
return;
}
// Helper: write to localStorage only when the value from server is
// non-empty, so we don't overwrite a key the user already has locally.
const sync = (localKey, serverValue) => {
if (serverValue !== undefined && serverValue !== null && serverValue !== '') {
localStorage.setItem(localKey, serverValue);
}
};
sync('ModelResolver.civitaiApiKey', data.civitai_key);
sync('ModelResolver.civitaiSessionToken', data.civitai_session_token);
sync('ModelResolver.huggingFaceToken', data.hf_token);
sync('ModelResolver.braveSearchApiKey', data.brave_search_api_key);
if (data.civitai_use_trpc_search !== undefined)
localStorage.setItem('ModelResolver.civitaiUseTrpcSearch', data.civitai_use_trpc_search ? 'true' : 'false');
if (data.civitai_use_html_fallback !== undefined)
localStorage.setItem('ModelResolver.civitaiUseHtmlFallback', data.civitai_use_html_fallback ? 'true' : 'false');
if (data.hf_use_api_search !== undefined)
localStorage.setItem('ModelResolver.hfUseApiSearch', data.hf_use_api_search ? 'true' : 'false');
if (data.hf_use_comfy_org_fallback !== undefined)
localStorage.setItem('ModelResolver.hfUseComfyOrgFallback', data.hf_use_comfy_org_fallback ? 'true' : 'false');
if (data.hf_use_brave_fallback !== undefined)
localStorage.setItem('ModelResolver.hfUseBraveFallback', data.hf_use_brave_fallback ? 'true' : 'false');
if (data.auto_fill_base_model !== undefined)
localStorage.setItem('ModelResolver.autoFillBaseModel', data.auto_fill_base_model ? 'true' : 'false');
if (data.auto_fill_subfolder !== undefined)
localStorage.setItem('ModelResolver.autoFillSubfolder', data.auto_fill_subfolder ? 'true' : 'false');
if (data.auto_refresh_comfy_models_after_apply !== undefined)
localStorage.setItem('ModelResolver.autoRefreshComfyModelsAfterApply', data.auto_refresh_comfy_models_after_apply ? 'true' : 'false');
if (data.download_path_mode !== undefined)
localStorage.setItem('ModelResolver.downloadPathMode', this.normalizeDownloadPathMode(data.download_path_mode));
if (data.download_path_templates !== undefined)
localStorage.setItem('ModelResolver.downloadPathTemplates', JSON.stringify(data.download_path_templates || {}));
if (data.base_model_path_mappings !== undefined)
localStorage.setItem('ModelResolver.baseModelPathMappings', JSON.stringify(data.base_model_path_mappings || {}));
this.getDefaultRootCategoryDefinitions().forEach((item) => {
if (data[item.settingKey] !== undefined) {
localStorage.setItem(item.storageKey, String(data[item.settingKey] || ''));
}
});
if (data.civitai_candidate_limit !== undefined)
localStorage.setItem('ModelResolver.civitaiCandidateLimit', `${data.civitai_candidate_limit}`);
if (data.frontend_logs_enabled !== undefined)
localStorage.setItem('ModelResolver.frontendLogsEnabled', data.frontend_logs_enabled ? 'true' : 'false');
if (data.backend_logs_enabled !== undefined)
localStorage.setItem('ModelResolver.backendLogsEnabled', data.backend_logs_enabled ? 'true' : 'false');
if (data.frontend_log_level !== undefined)
localStorage.setItem('ModelResolver.frontendLogLevel', String(data.frontend_log_level || 'DEBUG').toUpperCase());
if (data.backend_log_level !== undefined)
localStorage.setItem('ModelResolver.backendLogLevel', String(data.backend_log_level || 'DEBUG').toUpperCase());
// Source-enabled flags stored as a nested object
if (data.search_source_enabled && typeof data.search_source_enabled === 'object') {
Object.entries(data.search_source_enabled).forEach(([key, val]) => {
if (key) localStorage.setItem(key, val ? 'true' : 'false');
});
}
const tokens = this.getStoredTokens();
this.applyFrontendLoggingPreference(tokens.frontend_logs_enabled, tokens.frontend_log_level);
} catch (err) {
console.warn('Model Resolver: could not load settings from server, using localStorage only.', err);
const tokens = this.getStoredTokens();
this.applyFrontendLoggingPreference(tokens.frontend_logs_enabled, tokens.frontend_log_level);
}
}
};