feat(logging): add frontend/backend log controls, unify console logging

- Add frontend_logs_enabled/frontend_log_level and backend_logs_enabled/backend_log_level
  toggles and selectors in the Options panel (web/resolver/views/options_methods.js)
- Sync frontend logging settings from localStorage / server settings into the
  frontend logger on startup and settings load (download_target_methods.js)
- Apply backend logging settings from server settings in the Python backend
  (__init__.py), loading control state on startup and after saves
- Make frontend Logger resilient to stored configs that lack an 'enabled' flag
  and to legacy/invalid moduleSettings levels (logger.js)
- Guard default module logger level initialization against undefined settings
  (log_funcs.js)
- Replace scattered console.log/usages with module-level log.debug calls across
  resolve_download_methods.js, model_resolver.js, and workflow_state_methods.js
- Bump log system package version to 1.5.6 (core + web)
This commit is contained in:
Dariusz L
2026-06-17 01:51:29 +02:00
parent fa2002fc2a
commit a4d5bd4c5c
14 changed files with 249 additions and 38 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
<img src="https://api.visitorbadge.io/api/combined?path=https%3A%2F%2Fgithub.com%2FAzornes%2FComfyui-Model-Resolver&countColor=%2337d67a&style=for-the-badge&labelStyle=none&labelColor=111827" /></a>
<img alt="Python 3.10+" src="https://img.shields.io/badge/Python-3.10+-4B8BBE?labelColor=111827&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTAiIGhlaWdodD0iMTEwIiB2aWV3Qm94PSIwLjIxIC0wLjA3NyAxMTAgMTEwIj48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iNjMuODE1OSIgeTE9IjU2LjY4MjkiIHgyPSIxMTguNDkzNCIgeTI9IjEuODIyNSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAtMSAtNTMuMjk3NCA2Ni40MzIxKSI%2BPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMzg3RUI4Ii8%2BPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzY2OTk0Ii8%2BPC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImIiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iOTcuMDQ0NCIgeTE9IjIxLjYzMjEiIHgyPSIxNTUuNjY2NSIgeTI9Ii0zNC41MzA4IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIC0xIC01My4yOTc0IDY2LjQzMjEpIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNGRkUwNTIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRkMzMzEiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cGF0aCBmaWxsPSJ1cmwoI2EpIiBkPSJNNTUuMDIzLTAuMDc3Yy0yNS45NzEsMC0yNi4yNSwxMC4wODEtMjYuMjUsMTIuMTU2djEyLjU5NGgyNi43NXYzLjc4MUgxOC4xNDhjLTcuOTQ5LDAtMTcuOTM4LDQuODMzLTE3LjkzOCwyNi4yNSwwLDE5LjY3Myw3Ljc5MiwyNy4yODEsMTUuNjU2LDI3LjI4MWg5LjM0NFY2OC44NmMwLTUuNDkxLDIuNzIxLTE1LjY1NiwxNS40MDYtMTUuNjU2aDI2LjUzMWMzLjkwMiwwLDE0LjkwNi0xLjY5NiwxNC45MDYtMTQuNDA2VjE0LjU3OWMuMDAxLTMuMTUzLS41MzgtMTQuNjU2LTI3LjAzLTE0LjY1NnpNNDAuMjczLDguMzkyYzIuNjYyLDAsNC44MTMsMi4xNSw0LjgxMyw0LjgxMywwLDIuNjYxLTIuMTUxLDQuODEzLTQuODEzLDQuODEzcy00LjgxMy0yLjE1MS00LjgxMy00LjgxM2MwLTIuNjYzLDIuMTUxLTQuODEzLDQuODEzLTQuODEzeiIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik01NS4zOTcsMTA5LjkyM2MyNS45NTksMCwyNi4yODItMTAuMjcxLDI2LjI4Mi0xMi4xNTZWODUuMTczSDU0Ljg5N3YtMy43ODFoMzcuMzc1YzguMDA5LDAsMTcuOTM4LTQuOTU0LDE3LjkzOC0yNi4yNSwwLTIzLjMyMi0xMC41MzgtMjcuMjgxLTE1LjY1Ni0yNy4yODFIODUuMjF2MTMuMTI1YzAsNS40OTEtMi42MzEsMTUuNjU2LTE1LjQwNiwxNS42NTZINDMuMjcyYy0zLjg5MiwwLTE0LjkwNiwxLjg5Ni0xNC45MDYsMTQuNDA2djI0LjIxOWMwLDUuMjMsMy4xOTYsMTQuNjU2LDI3LjAzMSwxNC42NTZ6TTcwLjE0OCwxMDEuNDU0Yy0yLjY2MiwwLTQuODEzLTIuMTUxLTQuODEzLTQuODEzczIuMTUtNC44MTMsNC44MTMtNC44MTNjMi42NjEsMCw0LjgxMywyLjE1MSw0LjgxMyw0LjgxM3MtMi4xNTIsNC44MTMtNC44MTMsNC44MTN6Ii8%2BPC9zdmc%2B&style=for-the-badge">
<img alt="JavaScript" src="https://img.shields.io/badge/-JavaScript-000000?logo=javascript&logoColor=F7DF1E&style=for-the-badge&logoWidth=20">
<a href="https://github.com/sponsors/Azornes"><img src="https://img.shields.io/badge/Sponsor-EA4AAA?style=for-the-badge&logo=githubsponsors&logoColor=rede&labelColor=111827" alt="Sponsor"><img src="https://img.shields.io/badge/-%20-181717?style=for-the-badge&logo=github&logoColor=white" alt="GitHub"></a>
<a href="https://github.com/sponsors/Azornes"><img src="https://img.shields.io/badge/Sponsor-EA4AAA?style=for-the-badge&logo=githubsponsors&logoColor=magento&labelColor=111827" alt="Sponsor"><img src="https://img.shields.io/badge/-%20-181717?style=for-the-badge&logo=github&logoColor=white" alt="GitHub"></a>
</p>
<p align="center">
<strong>🔹 <a href="#%EF%B8%8F-installation">Quick Start</a></strong>
+34 -2
View File
@@ -15,6 +15,8 @@ from .core.log_system.log_funcs import (
log_exception,
log_warn,
)
from .core.log_system.config import LOG_LEVEL as BACKEND_DEFAULT_LOG_LEVEL
from .core.log_system.logger import LogLevel, logger as backend_log_controller
# Web directory for JavaScript interface
WEB_DIRECTORY = "./web"
@@ -2091,13 +2093,42 @@ class ModelResolverExtension:
self.logger.warning(f"Model Resolver: could not read settings file: {exc}")
return {}
def _save_settings(payload: dict) -> None:
def _bool_setting(value, default: bool = True) -> bool:
if value is None:
return default
if isinstance(value, bool):
return value
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in {"1", "true", "yes", "on"}:
return True
if normalized in {"0", "false", "no", "off"}:
return False
return bool(value)
def _log_level_setting(value, default: str = BACKEND_DEFAULT_LOG_LEVEL) -> LogLevel:
normalized = str(value or default or "INFO").strip().upper()
if hasattr(LogLevel, normalized):
return getattr(LogLevel, normalized)
fallback = str(default or "INFO").strip().upper()
return getattr(LogLevel, fallback, LogLevel.INFO)
def _apply_backend_logging_settings(settings: dict) -> None:
enabled = _bool_setting(settings.get("backend_logs_enabled"), True)
level = _log_level_setting(settings.get("backend_log_level"))
backend_log_controller.set_enabled(enabled)
backend_log_controller.set_global_level(level)
def _save_settings(payload: dict) -> dict:
# Merge with existing settings so we never lose keys not sent
# by the current request.
current = _load_settings()
current.update({k: v for k, v in payload.items() if k})
with open(_SETTINGS_FILE, "w", encoding="utf-8") as f:
_json.dump(current, f, indent=2, ensure_ascii=False)
return current
_apply_backend_logging_settings(_load_settings())
@routes.get("/model_resolver/settings")
async def get_settings_route(request):
@@ -2116,7 +2147,8 @@ class ModelResolverExtension:
payload = await request.json()
if not isinstance(payload, dict):
return web.json_response({"error": "Expected JSON object"}, status=400)
await asyncio.to_thread(_save_settings, payload)
settings = await asyncio.to_thread(_save_settings, payload)
_apply_backend_logging_settings(settings)
return web.json_response({"success": True})
except Exception as e:
self.logger.error(f"Model Resolver settings POST error: {e}", exc_info=True)
+1 -1
View File
@@ -1,7 +1,7 @@
"""
@author: Azornes
@title: AzLogs
@version: 1.5.2
@version: 1.5.6
@description: Logging Configuration
"""
# Log level for development.
+1 -1
View File
@@ -1,7 +1,7 @@
"""
@author: Azornes
@title: AzLogs
@version: 1.5.2
@version: 1.5.6
@description: Logging Initializator
"""
# ruff: noqa: T201
+1 -1
View File
@@ -1,7 +1,7 @@
"""
@author: Azornes
@title: AzLogs
@version: 1.5.2
@version: 1.5.6
@description: Logging Setup - Central logging system
Features:
+4 -2
View File
@@ -4670,7 +4670,8 @@
min-width: 0;
padding-right: 8px;
}
.mr-options-input {
.mr-options-input,
#model-resolver-modal select.mr-options-input {
width: 100%;
min-width: 0;
min-height: 42px;
@@ -4683,7 +4684,8 @@
outline: none;
transition: border-color 0.16s ease, background 0.16s ease, box-shadow 0.16s ease;
}
.mr-options-input:focus {
.mr-options-input:focus,
#model-resolver-modal select.mr-options-input:focus {
background: #0f1013;
border-color: rgba(78,161,255,0.45);
box-shadow: none;
+1 -1
View File
@@ -1,7 +1,7 @@
/**
author: Azornes
title: AzLogs
version: 1.5.5
version: 1.5.6
description: Logging Configuration
*/
// Log level for development.
+5 -2
View File
@@ -1,7 +1,7 @@
/**
author: Azornes
title: AzLogs
version: 1.5.5
version: 1.5.6
description: Logging Initializator
Features:
@@ -17,7 +17,10 @@ import { LOG_LEVEL } from './config.js';
* @returns {Logger} Object with logging methods
*/
export function createModuleLogger(moduleName) {
logger.setModuleLevel(moduleName, LogLevel[LOG_LEVEL]);
logger.config.moduleSettings = logger.config.moduleSettings || {};
if (logger.config.moduleSettings[moduleName] === undefined) {
logger.setModuleLevel(moduleName, logger.normalizeLevel(logger.config.globalLevel, LogLevel[LOG_LEVEL]));
}
return {
debug: (...args) => logger.debug(moduleName, ...args),
info: (...args) => logger.info(moduleName, ...args),
+46 -5
View File
@@ -13,7 +13,7 @@ logger - Central logging system
- Ability to export logs
*/
import { DEFAULT_LOGGER_NAME, LOG_MODULE_NAME, USE_COLORS } from './config.js';
import { DEFAULT_LOGGER_NAME, LOG_LEVEL, LOG_MODULE_NAME, USE_COLORS } from './config.js';
function padStart(str, targetLength, padString) {
targetLength = targetLength >> 0;
@@ -53,7 +53,8 @@ export const LogLevel = {
NONE: 4
};
const DEFAULT_CONFIG = {
globalLevel: LogLevel.INFO,
enabled: true,
globalLevel: LogLevel[LOG_LEVEL] ?? LogLevel.INFO,
moduleSettings: {},
useColors: USE_COLORS,
saveToStorage: false,
@@ -90,6 +91,7 @@ class Logger {
this.logs = [];
this.enabled = true;
this.loadConfig();
this.enabled = this.config.enabled !== false;
this.loadLogs();
}
/**
@@ -98,6 +100,17 @@ class Logger {
*/
configure(config) {
this.config = { ...this.config, ...config };
if (Object.prototype.hasOwnProperty.call(config, 'enabled')) {
this.enabled = config.enabled !== false;
}
this.config.globalLevel = this.normalizeLevel(this.config.globalLevel, DEFAULT_CONFIG.globalLevel);
if (!this.config.moduleSettings || typeof this.config.moduleSettings !== 'object') {
this.config.moduleSettings = {};
} else {
Object.entries(this.config.moduleSettings).forEach(([module, level]) => {
this.config.moduleSettings[module] = this.normalizeLevel(level, this.config.globalLevel);
});
}
this.saveConfig();
return this;
}
@@ -106,7 +119,9 @@ class Logger {
* @param {boolean} enabled - Whether the logger should be enabled
*/
setEnabled(enabled) {
this.enabled = enabled;
this.enabled = Boolean(enabled);
this.config.enabled = this.enabled;
this.saveConfig();
return this;
}
/**
@@ -114,7 +129,32 @@ class Logger {
* @param {LogLevels} level - Logging level
*/
setGlobalLevel(level) {
this.config.globalLevel = level;
this.config.globalLevel = this.normalizeLevel(level, DEFAULT_CONFIG.globalLevel);
this.saveConfig();
return this;
}
normalizeLevel(level, fallback = this.config.globalLevel) {
if (typeof level === 'number' && Object.values(LogLevel).includes(level)) {
return level;
}
if (typeof level === 'string') {
const normalized = level.trim().toUpperCase();
if (Object.prototype.hasOwnProperty.call(LogLevel, normalized)) {
return LogLevel[normalized];
}
}
return fallback;
}
getLevelName(level = this.config.globalLevel) {
const normalizedLevel = this.normalizeLevel(level, this.config.globalLevel);
return Object.entries(LogLevel).find(([, value]) => value === normalizedLevel)?.[0] || 'INFO';
}
setGlobalAndModuleLevel(level) {
const normalizedLevel = this.normalizeLevel(level, this.config.globalLevel);
this.config.globalLevel = normalizedLevel;
Object.keys(this.config.moduleSettings || {}).forEach((module) => {
this.config.moduleSettings[module] = normalizedLevel;
});
this.saveConfig();
return this;
}
@@ -124,7 +164,7 @@ class Logger {
* @param {LogLevels} level - Logging level
*/
setModuleLevel(module, level) {
this.config.moduleSettings[module] = level;
this.config.moduleSettings[module] = this.normalizeLevel(level, this.config.globalLevel);
this.saveConfig();
return this;
}
@@ -597,6 +637,7 @@ class Logger {
const storedConfig = localStorage.getItem(LOGGER_CONFIG_KEY);
if (storedConfig) {
this.config = { ...this.config, ...JSON.parse(storedConfig) };
this.configure(this.config);
}
}
catch (e) {
@@ -1,13 +1,17 @@
import { app } from "../../../../../scripts/app.js";
import { api } from "../../../../../scripts/api.js";
import { $el } from "../../../../../scripts/ui.js";
import { createModuleLogger } from "../../log_system/log_funcs.js";
import { getSvgIcon } from "../../utils/icon_utils.js";
const log = createModuleLogger('resolve_download_methods');
export const resolveDownloadMethods = {
/**
* Resolve a model - resolves ALL nodes that reference this model
*/
async resolveModel(missing, resolvedModel) {
console.log('resolveModel called:', missing?.original_path, '->', resolvedModel?.filename);
log.debug('resolveModel called:', missing?.original_path, '->', resolvedModel?.filename);
if (!resolvedModel) {
this.showNotification('No resolved model selected', 'error');
@@ -23,7 +27,7 @@ export const resolveDownloadMethods = {
// Resolve ALL nodes that need this model (all_node_refs contains deduplicated refs)
const nodeRefs = missing.all_node_refs || [missing];
console.log('nodeRefs count:', nodeRefs?.length, 'is_lora_v2:', nodeRefs?.[0]?.is_lora_v2);
log.debug('nodeRefs count:', nodeRefs?.length, 'is_lora_v2:', nodeRefs?.[0]?.is_lora_v2);
const resolutions = nodeRefs.map(ref => ({
node_id: ref.node_id,
@@ -51,7 +55,7 @@ export const resolveDownloadMethods = {
}
const data = await response.json();
console.log('Resolve response: success=', data.success, ' missing count:', data.workflow?.nodes?.length);
log.debug('Resolve response: success=', data.success, ' missing count:', data.workflow?.nodes?.length);
if (data.success) {
// Update workflow in ComfyUI
@@ -1366,7 +1370,7 @@ export const resolveDownloadMethods = {
};
try {
console.log('Model Resolver: Search request:', JSON.stringify(searchData));
log.debug('Model Resolver: Search request:', JSON.stringify(searchData));
const response = await api.fetchApi('/model_resolver/search', {
method: 'POST',
@@ -1379,7 +1383,7 @@ export const resolveDownloadMethods = {
}
const data = await response.json();
console.log('Model Resolver: Search response:', JSON.stringify(data));
log.debug('Model Resolver: Search response:', JSON.stringify(data));
if (!this.isBackgroundSearchRunActive(workflowKey, missingSearchKey, searchRunId)) {
return { source, stale: true };
@@ -1511,9 +1515,9 @@ export const resolveDownloadMethods = {
* Resolve URN asynchronously - fetch CivitAI info and update UI
*/
async resolveUrnAsync(modelId, versionId, loadingElementId, modelUrl) {
console.log('resolveUrnAsync called:', modelId, versionId);
log.debug('resolveUrnAsync called:', modelId, versionId);
if (!modelId || !versionId) {
console.log('resolveUrnAsync: missing modelId or versionId');
log.debug('resolveUrnAsync: missing modelId or versionId');
return;
}
@@ -1537,7 +1541,7 @@ export const resolveDownloadMethods = {
version_id: versionId,
civitai_candidate_limit: tokens.civitai_candidate_limit
};
console.log('resolveUrnAsync payload:', JSON.stringify(payload));
log.debug('resolveUrnAsync payload:', JSON.stringify(payload));
const response = await api.fetchApi('/model_resolver/search', {
method: 'POST',
@@ -1545,7 +1549,7 @@ export const resolveDownloadMethods = {
body: JSON.stringify(payload)
});
console.log('resolveUrnAsync response status:', response.status);
log.debug('resolveUrnAsync response status:', response.status);
if (!response.ok) {
throw new Error(`URN resolve failed: ${response.status}`);
}
+25 -9
View File
@@ -1,9 +1,24 @@
import { app } from "../../../../scripts/app.js";
import { api } from "../../../../scripts/api.js";
import { $el } from "../../../../scripts/ui.js";
import { createModuleLogger } from "../log_system/log_funcs.js";
import { logger as frontendLogger } from "../log_system/logger.js";
import { loadStylesWhenNeeded } from "../utils/css_loader.js";
import { ResolverManagerDialog } from "./resolver_dialog.js";
const log = createModuleLogger('model_resolver');
function applyStoredFrontendLoggingPreference() {
const stored = localStorage.getItem('ModelResolver.frontendLogsEnabled');
if (stored !== null) {
frontendLogger.setEnabled(stored !== 'false');
}
const storedLevel = localStorage.getItem('ModelResolver.frontendLogLevel');
if (storedLevel) {
frontendLogger.setGlobalAndModuleLevel(frontendLogger.normalizeLevel(storedLevel));
}
}
// Main extension class
export class ModelResolver {
constructor() {
@@ -19,6 +34,7 @@ export class ModelResolver {
}
setup = async () => {
applyStoredFrontendLoggingPreference();
loadStylesWhenNeeded();
// Remove any existing button
@@ -176,7 +192,7 @@ export class ModelResolver {
app.menu?.settingsGroup.element.before(this.buttonGroup.element);
} catch (e) {
// Fallback for older ComfyUI versions without the new button system
console.log('Model Resolver: New button system not available, using floating button fallback.');
log.debug('Model Resolver: New button system not available, using floating button fallback.');
this.createFloatingButton();
}
}
@@ -188,7 +204,7 @@ export class ModelResolver {
// Watch for ComfyUI's Missing Models popup and inject our button
this.setupMissingModelsPopupObserver();
console.log('Model Resolver: Missing models popup button injection enabled');
log.debug('Model Resolver: Missing models popup button injection enabled');
}
setupActiveWorkflowChangeListeners() {
@@ -405,7 +421,7 @@ export class ModelResolver {
}
}
console.log('Model Resolver: Injected buttons into Missing Models popup');
log.debug('Model Resolver: Injected buttons into Missing Models popup');
};
// Small delay to ensure popup is fully rendered
@@ -450,7 +466,7 @@ export class ModelResolver {
* Mark resolved model items in the popup as linked (green) and hide download buttons
*/
removeResolvedFromPopup(dialog, resolvedFilenames) {
console.log('Model Resolver: Looking for resolved filenames:', resolvedFilenames);
log.debug('Model Resolver: Looking for resolved filenames:', resolvedFilenames);
// Strategy: For each filename, find text nodes containing it,
// then find the nearest Download button and mark that row
@@ -477,7 +493,7 @@ export class ModelResolver {
!btn.id?.includes('model-resolver'));
if (downloadBtn) {
console.log('Model Resolver: Found entry for', filename);
log.debug('Model Resolver: Found entry for', filename);
this.markEntryAsResolved(parent, downloadBtn);
break;
}
@@ -501,7 +517,7 @@ export class ModelResolver {
if (container.dataset.resolved === 'true') return;
container.dataset.resolved = 'true';
console.log('Model Resolver: Marking entry as resolved', container);
log.debug('Model Resolver: Marking entry as resolved', container);
// Add green background/styling to the container
container.classList.add('mr-resolved-entry');
@@ -574,11 +590,11 @@ export class ModelResolver {
widget.callback(resolvedPath, app.graph, node, null, null);
}
console.log(`Model Resolver: Updated node ${nodeId} widget ${widgetIndex} to ${resolvedPath}`);
log.debug(`Model Resolver: Updated node ${nodeId} widget ${widgetIndex} to ${resolvedPath}`);
} else if (node.widgets_values) {
// Fallback: update widgets_values array directly
node.widgets_values[widgetIndex] = resolvedPath;
console.log(`Model Resolver: Updated node ${nodeId} widgets_values[${widgetIndex}] to ${resolvedPath}`);
log.debug(`Model Resolver: Updated node ${nodeId} widgets_values[${widgetIndex}] to ${resolvedPath}`);
}
// Mark node as dirty to trigger redraw
@@ -658,7 +674,7 @@ export class ModelResolver {
// Auto-open dialog if there are missing models
if (data.total_missing > 0) {
console.log(`Model Resolver: Found ${data.total_missing} missing model(s), opening dialog...`);
log.debug(`Model Resolver: Found ${data.total_missing} missing model(s), opening dialog...`);
this.openResolverManager();
}
+41 -2
View File
@@ -1,6 +1,8 @@
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";
export const downloadTargetMethods = {
/**
@@ -860,6 +862,10 @@ export const downloadTargetMethods = {
? 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') || '',
@@ -873,11 +879,24 @@ export const downloadTargetMethods = {
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',
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
};
},
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
@@ -886,9 +905,17 @@ export const downloadTargetMethods = {
async loadSettingsFromServer() {
try {
const resp = await api.fetchApi('/model_resolver/settings');
if (!resp.ok) return;
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') return;
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.
@@ -919,6 +946,14 @@ export const downloadTargetMethods = {
localStorage.setItem('ModelResolver.autoFillSubfolder', data.auto_fill_subfolder ? 'true' : 'false');
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') {
@@ -926,8 +961,12 @@ export const downloadTargetMethods = {
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);
}
},
+5 -1
View File
@@ -1,7 +1,11 @@
import { app } from "../../../../../scripts/app.js";
import { api } from "../../../../../scripts/api.js";
import { $el } from "../../../../../scripts/ui.js";
import { createModuleLogger } from "../../log_system/log_funcs.js";
import { getSvgIcon } from "../../utils/icon_utils.js";
const log = createModuleLogger('workflow_state_methods');
export const workflowStateMethods = {
getWorkflowSignature(workflow) {
if (!workflow) return null;
@@ -564,7 +568,7 @@ export const workflowStateMethods = {
return;
}
console.log('Model Resolver: active workflow changed, refreshing current tab', {
log.debug('Model Resolver: active workflow changed, refreshing current tab', {
reason,
route: currentRoute,
tab: this.activeTab
+71 -1
View File
@@ -31,6 +31,17 @@ export const optionsMethods = {
`;
})
.join('');
const logLevelValues = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE'];
const normalizeLogLevel = (value) => {
const normalized = String(value || '').trim().toUpperCase();
return logLevelValues.includes(normalized) ? normalized : 'DEBUG';
};
const renderLogLevelOptions = (selectedValue) => {
const selected = normalizeLogLevel(selectedValue);
return logLevelValues
.map(level => `<option value="${level}" ${level === selected ? 'selected' : ''}>${level}</option>`)
.join('');
};
this.contentElement.innerHTML = `
<div class="mr-options-wrap">
<div class="mr-options-shell">
@@ -374,6 +385,48 @@ export const optionsMethods = {
<h4 class="mr-options-section-title">Maintenance</h4>
</div>
<div class="mr-options-grid">
<div class="mr-options-panel">
<div class="mr-options-toggle-list">
<label class="mr-options-toggle-row">
<div class="mr-options-toggle-copy">
<span class="mr-options-toggle-title">Frontend logs <span class="mr-tooltip-badge" data-tooltip="When enabled, Model Resolver writes frontend diagnostic logs to the browser console.">?</span></span>
</div>
<span class="mr-options-toggle-control">
<input id="mr-options-frontend-logs-enabled" class="mr-options-switch-input" type="checkbox" ${tokens.frontend_logs_enabled ? 'checked' : ''}>
<span class="mr-options-switch"></span>
</span>
</label>
<div class="mr-options-dependent-block">
<div class="mr-options-number-row">
<div class="mr-options-number-copy">
<span class="mr-options-label">Frontend log level <span class="mr-tooltip-badge" data-tooltip="Minimum frontend log level shown in the browser console. NONE hides all frontend logger output.">?</span></span>
</div>
<select id="mr-options-frontend-log-level" class="mr-options-input">
${renderLogLevelOptions(tokens.frontend_log_level)}
</select>
</div>
</div>
<label class="mr-options-toggle-row">
<div class="mr-options-toggle-copy">
<span class="mr-options-toggle-title">Backend logs <span class="mr-tooltip-badge" data-tooltip="When enabled, Model Resolver writes backend logs to the ComfyUI console and plugin log file.">?</span></span>
</div>
<span class="mr-options-toggle-control">
<input id="mr-options-backend-logs-enabled" class="mr-options-switch-input" type="checkbox" ${tokens.backend_logs_enabled ? 'checked' : ''}>
<span class="mr-options-switch"></span>
</span>
</label>
<div class="mr-options-dependent-block">
<div class="mr-options-number-row">
<div class="mr-options-number-copy">
<span class="mr-options-label">Backend log level <span class="mr-tooltip-badge" data-tooltip="Minimum backend log level written by Model Resolver. NONE hides all backend logger output.">?</span></span>
</div>
<select id="mr-options-backend-log-level" class="mr-options-input">
${renderLogLevelOptions(tokens.backend_log_level)}
</select>
</div>
</div>
</div>
</div>
<div class="mr-options-panel">
<div class="mr-options-stack">
<div class="mr-options-db-message">
@@ -412,6 +465,10 @@ export const optionsMethods = {
const hfUseBraveFallbackInput = this.contentElement.querySelector('#mr-options-hf-use-brave-fallback');
const autoFillBaseModelInput = this.contentElement.querySelector('#mr-options-auto-fill-base-model');
const autoFillSubfolderInput = this.contentElement.querySelector('#mr-options-auto-fill-subfolder');
const frontendLogsEnabledInput = this.contentElement.querySelector('#mr-options-frontend-logs-enabled');
const backendLogsEnabledInput = this.contentElement.querySelector('#mr-options-backend-logs-enabled');
const frontendLogLevelInput = this.contentElement.querySelector('#mr-options-frontend-log-level');
const backendLogLevelInput = this.contentElement.querySelector('#mr-options-backend-log-level');
const hfTokenCheckBtn = this.contentElement.querySelector('#mr-options-hf-token-check');
const hfTokenCheckStatus = this.contentElement.querySelector('#mr-options-hf-token-check-status');
const braveKeyCheckBtn = this.contentElement.querySelector('#mr-options-brave-key-check');
@@ -453,6 +510,10 @@ export const optionsMethods = {
hfUseBraveFallbackInput,
autoFillBaseModelInput,
autoFillSubfolderInput,
frontendLogsEnabledInput,
backendLogsEnabledInput,
frontendLogLevelInput,
backendLogLevelInput,
...sourceEnabledInputs,
].filter(Boolean);
@@ -836,7 +897,7 @@ export const optionsMethods = {
setVisibleSection('mr-options-section-sources');
trackedInputs.forEach((input) => {
const eventName = input.type === 'checkbox' ? 'change' : 'input';
const eventName = input.type === 'checkbox' || input.tagName === 'SELECT' ? 'change' : 'input';
input.addEventListener(eventName, () => {
setStatus('You have unsaved changes.', 'is-dirty');
});
@@ -982,6 +1043,10 @@ export const optionsMethods = {
hf_use_brave_fallback: Boolean(hfUseBraveFallbackInput?.checked),
auto_fill_base_model: Boolean(autoFillBaseModelInput?.checked),
auto_fill_subfolder: Boolean(autoFillSubfolderInput?.checked),
frontend_logs_enabled: Boolean(frontendLogsEnabledInput?.checked),
backend_logs_enabled: Boolean(backendLogsEnabledInput?.checked),
frontend_log_level: normalizeLogLevel(frontendLogLevelInput?.value || tokens.frontend_log_level),
backend_log_level: normalizeLogLevel(backendLogLevelInput?.value || tokens.backend_log_level),
brave_search_api_key: braveInput?.value || '',
search_source_enabled: sourceEnabled,
};
@@ -998,11 +1063,16 @@ export const optionsMethods = {
localStorage.setItem('ModelResolver.hfUseBraveFallback', newSettings.hf_use_brave_fallback ? 'true' : 'false');
localStorage.setItem('ModelResolver.autoFillBaseModel', newSettings.auto_fill_base_model ? 'true' : 'false');
localStorage.setItem('ModelResolver.autoFillSubfolder', newSettings.auto_fill_subfolder ? 'true' : 'false');
localStorage.setItem('ModelResolver.frontendLogsEnabled', newSettings.frontend_logs_enabled ? 'true' : 'false');
localStorage.setItem('ModelResolver.backendLogsEnabled', newSettings.backend_logs_enabled ? 'true' : 'false');
localStorage.setItem('ModelResolver.frontendLogLevel', newSettings.frontend_log_level);
localStorage.setItem('ModelResolver.backendLogLevel', newSettings.backend_log_level);
localStorage.setItem('ModelResolver.civitaiCandidateLimit', `${civitaiCandidateLimit}`);
Object.entries(sourceEnabled).forEach(([key, val]) => {
localStorage.setItem(key, val ? 'true' : 'false');
});
if (civitaiLimitInput) civitaiLimitInput.value = `${civitaiCandidateLimit}`;
this.applyFrontendLoggingPreference(newSettings.frontend_logs_enabled, newSettings.frontend_log_level);
// 2. Persist to server (survives new browsers / profiles)
try {