Merge pull request #467 from willmiao/codex/migrate-frontend-settings-to-backend

feat(settings): centralize settings loading and snake_case keys
This commit is contained in:
pixelpaws
2025-09-23 22:16:10 +08:00
committed by GitHub
14 changed files with 255 additions and 313 deletions

View File

@@ -193,8 +193,15 @@ class MiscRoutes:
'proxy_username', 'proxy_username',
'proxy_password', 'proxy_password',
'example_images_path', 'example_images_path',
'optimizeExampleImages', 'optimize_example_images',
'autoDownloadExampleImages' 'auto_download_example_images',
'blur_mature_content',
'autoplay_on_hover',
'display_density',
'card_info_display',
'include_trigger_words',
'show_only_sfw',
'compact_mode'
] ]
# Build response with only the keys that should be synced # Build response with only the keys that should be synced

View File

@@ -5,10 +5,41 @@ from typing import Any, Dict
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
DEFAULT_SETTINGS: Dict[str, Any] = {
"civitai_api_key": "",
"language": "en",
"show_only_sfw": False,
"enable_metadata_archive_db": False,
"proxy_enabled": False,
"proxy_host": "",
"proxy_port": "",
"proxy_username": "",
"proxy_password": "",
"proxy_type": "http",
"default_lora_root": "",
"default_checkpoint_root": "",
"default_embedding_root": "",
"base_model_path_mappings": {},
"download_path_templates": {},
"example_images_path": "",
"optimize_example_images": True,
"auto_download_example_images": False,
"blur_mature_content": True,
"autoplay_on_hover": False,
"display_density": "default",
"card_info_display": "always",
"include_trigger_words": False,
"compact_mode": False,
}
class SettingsManager: class SettingsManager:
def __init__(self): def __init__(self):
self.settings_file = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'settings.json') self.settings_file = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'settings.json')
self.settings = self._load_settings() self.settings = self._load_settings()
self._migrate_setting_keys()
self._ensure_default_settings()
self._migrate_download_path_template() self._migrate_download_path_template()
self._auto_set_default_roots() self._auto_set_default_roots()
self._check_environment_variables() self._check_environment_variables()
@@ -23,6 +54,44 @@ class SettingsManager:
logger.error(f"Error loading settings: {e}") logger.error(f"Error loading settings: {e}")
return self._get_default_settings() return self._get_default_settings()
def _ensure_default_settings(self) -> None:
"""Ensure all default settings keys exist"""
updated = False
for key, value in self._get_default_settings().items():
if key not in self.settings:
if isinstance(value, dict):
self.settings[key] = value.copy()
else:
self.settings[key] = value
updated = True
if updated:
self._save_settings()
def _migrate_setting_keys(self) -> None:
"""Migrate legacy camelCase setting keys to snake_case"""
key_migrations = {
'optimizeExampleImages': 'optimize_example_images',
'autoDownloadExampleImages': 'auto_download_example_images',
'blurMatureContent': 'blur_mature_content',
'autoplayOnHover': 'autoplay_on_hover',
'displayDensity': 'display_density',
'cardInfoDisplay': 'card_info_display',
'includeTriggerWords': 'include_trigger_words',
'compactMode': 'compact_mode',
}
updated = False
for old_key, new_key in key_migrations.items():
if old_key in self.settings:
if new_key not in self.settings:
self.settings[new_key] = self.settings[old_key]
del self.settings[old_key]
updated = True
if updated:
logger.info("Migrated legacy setting keys to snake_case")
self._save_settings()
def _migrate_download_path_template(self): def _migrate_download_path_template(self):
"""Migrate old download_path_template to new download_path_templates""" """Migrate old download_path_template to new download_path_templates"""
old_template = self.settings.get('download_path_template') old_template = self.settings.get('download_path_template')
@@ -78,18 +147,11 @@ class SettingsManager:
def _get_default_settings(self) -> Dict[str, Any]: def _get_default_settings(self) -> Dict[str, Any]:
"""Return default settings""" """Return default settings"""
return { defaults = DEFAULT_SETTINGS.copy()
"civitai_api_key": "", # Ensure nested dicts are independent copies
"language": "en", defaults['base_model_path_mappings'] = {}
"show_only_sfw": False, # Show only SFW content defaults['download_path_templates'] = {}
"enable_metadata_archive_db": False, # Enable metadata archive database return defaults
"proxy_enabled": False, # Enable app-level proxy
"proxy_host": "", # Proxy host
"proxy_port": "", # Proxy port
"proxy_username": "", # Proxy username (optional)
"proxy_password": "", # Proxy password (optional)
"proxy_type": "http" # Proxy type: http, https, socks4, socks5
}
def get(self, key: str, default: Any = None) -> Any: def get(self, key: str, default: Any = None) -> Any:
"""Get setting value""" """Get setting value"""

View File

@@ -945,7 +945,7 @@ export class BaseModelApiClient {
} }
// Determine optimize setting // Determine optimize setting
const optimize = state.global?.settings?.optimizeExampleImages ?? true; const optimize = state.global?.settings?.optimize_example_images ?? true;
// Make the API request to start the download process // Make the API request to start the download process
const response = await fetch(DOWNLOAD_ENDPOINTS.exampleImages, { const response = await fetch(DOWNLOAD_ENDPOINTS.exampleImages, {

View File

@@ -46,7 +46,7 @@ class RecipeCard {
// NSFW blur logic - similar to LoraCard // NSFW blur logic - similar to LoraCard
const nsfwLevel = this.recipe.preview_nsfw_level !== undefined ? this.recipe.preview_nsfw_level : 0; const nsfwLevel = this.recipe.preview_nsfw_level !== undefined ? this.recipe.preview_nsfw_level : 0;
const shouldBlur = state.settings.blurMatureContent && nsfwLevel > NSFW_LEVELS.PG13; const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
if (shouldBlur) { if (shouldBlur) {
card.classList.add('nsfw-content'); card.classList.add('nsfw-content');

View File

@@ -405,7 +405,7 @@ export function createModelCard(model, modelType) {
card.dataset.nsfwLevel = nsfwLevel; card.dataset.nsfwLevel = nsfwLevel;
// Determine if the preview should be blurred based on NSFW level and user settings // Determine if the preview should be blurred based on NSFW level and user settings
const shouldBlur = state.settings.blurMatureContent && nsfwLevel > NSFW_LEVELS.PG13; const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
if (shouldBlur) { if (shouldBlur) {
card.classList.add('nsfw-content'); card.classList.add('nsfw-content');
} }
@@ -433,7 +433,7 @@ export function createModelCard(model, modelType) {
} }
// Check if autoplayOnHover is enabled for video previews // Check if autoplayOnHover is enabled for video previews
const autoplayOnHover = state.global?.settings?.autoplayOnHover || false; const autoplayOnHover = state.global?.settings?.autoplay_on_hover || false;
const isVideo = previewUrl.endsWith('.mp4'); const isVideo = previewUrl.endsWith('.mp4');
const videoAttrs = autoplayOnHover ? 'controls muted loop' : 'controls autoplay muted loop'; const videoAttrs = autoplayOnHover ? 'controls muted loop' : 'controls autoplay muted loop';

View File

@@ -155,7 +155,7 @@ function renderMediaItem(img, index, exampleFiles) {
// Check if media should be blurred // Check if media should be blurred
const nsfwLevel = img.nsfwLevel !== undefined ? img.nsfwLevel : 0; const nsfwLevel = img.nsfwLevel !== undefined ? img.nsfwLevel : 0;
const shouldBlur = state.settings.blurMatureContent && nsfwLevel > NSFW_LEVELS.PG13; const shouldBlur = state.settings.blur_mature_content && nsfwLevel > NSFW_LEVELS.PG13;
// Determine NSFW warning text based on level // Determine NSFW warning text based on level
let nsfwText = "Mature Content"; let nsfwText = "Mature Content";

View File

@@ -74,7 +74,7 @@ export class AppCore {
// Initialize the help manager // Initialize the help manager
helpManager.initialize(); helpManager.initialize();
const cardInfoDisplay = state.global.settings.cardInfoDisplay || 'always'; const cardInfoDisplay = state.global.settings.card_info_display || 'always';
document.body.classList.toggle('hover-reveal', cardInfoDisplay === 'hover'); document.body.classList.toggle('hover-reveal', cardInfoDisplay === 'hover');
initializeEventManagement(); initializeEventManagement();

View File

@@ -1,3 +1,5 @@
import { state } from '../state/index.js';
/** /**
* Internationalization (i18n) system for LoRA Manager * Internationalization (i18n) system for LoRA Manager
* Uses user-selected language from settings with fallback to English * Uses user-selected language from settings with fallback to English
@@ -123,26 +125,12 @@ class I18nManager {
* @returns {string} Language code * @returns {string} Language code
*/ */
getLanguageFromSettings() { getLanguageFromSettings() {
// Check localStorage for user-selected language const language = state?.global?.settings?.language;
const STORAGE_PREFIX = 'lora_manager_';
let userLanguage = null;
try { if (language && this.availableLocales[language]) {
const settings = localStorage.getItem(STORAGE_PREFIX + 'settings'); return language;
if (settings) {
const parsedSettings = JSON.parse(settings);
userLanguage = parsedSettings.language;
}
} catch (e) {
console.warn('Failed to parse settings from localStorage:', e);
} }
// If user has selected a language, use it
if (userLanguage && this.availableLocales[userLanguage]) {
return userLanguage;
}
// Fallback to English
return 'en'; return 'en';
} }
@@ -165,18 +153,10 @@ class I18nManager {
this.readyPromise = this.initializeWithLocale(languageCode); this.readyPromise = this.initializeWithLocale(languageCode);
await this.readyPromise; await this.readyPromise;
// Save to localStorage if (state?.global?.settings) {
const STORAGE_PREFIX = 'lora_manager_'; state.global.settings.language = languageCode;
const currentSettings = localStorage.getItem(STORAGE_PREFIX + 'settings');
let settings = {};
if (currentSettings) {
settings = JSON.parse(currentSettings);
} }
settings.language = languageCode;
localStorage.setItem(STORAGE_PREFIX + 'settings', JSON.stringify(settings));
console.log(`Language changed to: ${languageCode}`); console.log(`Language changed to: ${languageCode}`);
// Dispatch event to notify components of language change // Dispatch event to notify components of language change

View File

@@ -63,7 +63,7 @@ export class ExampleImagesManager {
} }
// Setup auto download if enabled // Setup auto download if enabled
if (state.global.settings.autoDownloadExampleImages) { if (state.global.settings.auto_download_example_images) {
this.setupAutoDownload(); this.setupAutoDownload();
} }
@@ -106,7 +106,7 @@ export class ExampleImagesManager {
showToast('toast.exampleImages.pathUpdateFailed', { message: error.message }, 'error'); showToast('toast.exampleImages.pathUpdateFailed', { message: error.message }, 'error');
} }
// Setup or clear auto download based on path availability // Setup or clear auto download based on path availability
if (state.global.settings.autoDownloadExampleImages) { if (state.global.settings.auto_download_example_images) {
if (hasPath) { if (hasPath) {
this.setupAutoDownload(); this.setupAutoDownload();
} else { } else {
@@ -225,7 +225,7 @@ export class ExampleImagesManager {
} }
try { try {
const optimize = state.global.settings.optimizeExampleImages; const optimize = state.global.settings.optimize_example_images;
const response = await fetch('/api/lm/download-example-images', { const response = await fetch('/api/lm/download-example-images', {
method: 'POST', method: 'POST',
@@ -677,7 +677,7 @@ export class ExampleImagesManager {
canAutoDownload() { canAutoDownload() {
// Check if auto download is enabled // Check if auto download is enabled
if (!state.global.settings.autoDownloadExampleImages) { if (!state.global.settings.auto_download_example_images) {
return false; return false;
} }
@@ -713,7 +713,7 @@ export class ExampleImagesManager {
try { try {
console.log('Performing auto download check...'); console.log('Performing auto download check...');
const optimize = state.global.settings.optimizeExampleImages; const optimize = state.global.settings.optimize_example_images;
const response = await fetch('/api/lm/download-example-images', { const response = await fetch('/api/lm/download-example-images', {
method: 'POST', method: 'POST',

View File

@@ -182,9 +182,6 @@ export class OnboardingManager {
// Update state // Update state
state.global.settings.language = languageCode; state.global.settings.language = languageCode;
// Save to localStorage
setStorageItem('settings', state.global.settings);
// Save to backend // Save to backend
const response = await fetch('/api/lm/settings', { const response = await fetch('/api/lm/settings', {
method: 'POST', method: 'POST',

View File

@@ -1,10 +1,10 @@
import { modalManager } from './ModalManager.js'; import { modalManager } from './ModalManager.js';
import { showToast } from '../utils/uiHelpers.js'; import { showToast } from '../utils/uiHelpers.js';
import { state } from '../state/index.js'; import { state, createDefaultSettings } from '../state/index.js';
import { resetAndReload } from '../api/modelApiFactory.js'; import { resetAndReload } from '../api/modelApiFactory.js';
import { setStorageItem, getStorageItem } from '../utils/storageHelpers.js';
import { DOWNLOAD_PATH_TEMPLATES, MAPPABLE_BASE_MODELS, PATH_TEMPLATE_PLACEHOLDERS, DEFAULT_PATH_TEMPLATES } from '../utils/constants.js'; import { DOWNLOAD_PATH_TEMPLATES, MAPPABLE_BASE_MODELS, PATH_TEMPLATE_PLACEHOLDERS, DEFAULT_PATH_TEMPLATES } from '../utils/constants.js';
import { translate } from '../utils/i18nHelpers.js'; import { translate } from '../utils/i18nHelpers.js';
import { i18n } from '../i18n/index.js';
export class SettingsManager { export class SettingsManager {
constructor() { constructor() {
@@ -15,6 +15,8 @@ export class SettingsManager {
// Add initialization to sync with modal state // Add initialization to sync with modal state
this.currentPage = document.body.dataset.page || 'loras'; this.currentPage = document.body.dataset.page || 'loras';
this.backendSettingKeys = new Set(Object.keys(createDefaultSettings()));
// Start initialization but don't await here to avoid blocking constructor // Start initialization but don't await here to avoid blocking constructor
this.initializationPromise = this.initializeSettings(); this.initializationPromise = this.initializeSettings();
@@ -29,71 +31,13 @@ export class SettingsManager {
} }
async initializeSettings() { async initializeSettings() {
// Load frontend-only settings from localStorage // Reset to defaults before syncing
this.loadFrontendSettingsFromStorage(); state.global.settings = createDefaultSettings();
// Sync settings from backend to frontend // Sync settings from backend to frontend
await this.syncSettingsFromBackend(); await this.syncSettingsFromBackend();
} }
loadFrontendSettingsFromStorage() {
// Get saved settings from localStorage
const savedSettings = getStorageItem('settings');
// Frontend-only settings that should be stored in localStorage
const frontendOnlyKeys = [
'blurMatureContent',
'autoplayOnHover',
'displayDensity',
'cardInfoDisplay',
'includeTriggerWords'
];
// Apply saved frontend settings to state if available
if (savedSettings) {
const frontendSettings = {};
frontendOnlyKeys.forEach(key => {
if (savedSettings[key] !== undefined) {
frontendSettings[key] = savedSettings[key];
}
});
state.global.settings = { ...state.global.settings, ...frontendSettings };
}
// Initialize default values for frontend settings if they don't exist
if (state.global.settings.blurMatureContent === undefined) {
state.global.settings.blurMatureContent = true;
}
if (state.global.settings.show_only_sfw === undefined) {
state.global.settings.show_only_sfw = false;
}
if (state.global.settings.autoplayOnHover === undefined) {
state.global.settings.autoplayOnHover = false;
}
if (state.global.settings.cardInfoDisplay === undefined) {
state.global.settings.cardInfoDisplay = 'always';
}
if (state.global.settings.displayDensity === undefined) {
// Migrate legacy compactMode if it exists
if (state.global.settings.compactMode === true) {
state.global.settings.displayDensity = 'compact';
} else {
state.global.settings.displayDensity = 'default';
}
}
if (state.global.settings.includeTriggerWords === undefined) {
state.global.settings.includeTriggerWords = false;
}
// Save updated frontend settings to localStorage
this.saveFrontendSettingsToStorage();
}
async syncSettingsFromBackend() { async syncSettingsFromBackend() {
try { try {
const response = await fetch('/api/lm/settings'); const response = await fetch('/api/lm/settings');
@@ -103,103 +47,75 @@ export class SettingsManager {
const data = await response.json(); const data = await response.json();
if (data.success && data.settings) { if (data.success && data.settings) {
// Merge backend settings with current state state.global.settings = this.mergeSettingsWithDefaults(data.settings);
state.global.settings = { ...state.global.settings, ...data.settings };
// Set defaults for backend settings if they're null/undefined
this.setBackendSettingDefaults();
console.log('Settings synced from backend'); console.log('Settings synced from backend');
} else { } else {
console.error('Failed to sync settings from backend:', data.error); console.error('Failed to sync settings from backend:', data.error);
state.global.settings = this.mergeSettingsWithDefaults();
} }
} catch (error) { } catch (error) {
console.error('Failed to sync settings from backend:', error); console.error('Failed to sync settings from backend:', error);
// Set defaults if backend sync fails state.global.settings = this.mergeSettingsWithDefaults();
this.setBackendSettingDefaults(); }
await this.applyLanguageSetting();
this.applyFrontendSettings();
}
async applyLanguageSetting() {
const desiredLanguage = state?.global?.settings?.language;
if (!desiredLanguage) {
return;
}
try {
if (i18n.getCurrentLocale() !== desiredLanguage) {
await i18n.setLanguage(desiredLanguage);
}
} catch (error) {
console.warn('Failed to apply language from settings:', error);
} }
} }
setBackendSettingDefaults() { mergeSettingsWithDefaults(backendSettings = {}) {
// Set defaults for backend settings const defaults = createDefaultSettings();
const backendDefaults = { const merged = { ...defaults, ...backendSettings };
civitai_api_key: '',
default_lora_root: '',
default_checkpoint_root: '',
default_embedding_root: '',
base_model_path_mappings: {},
download_path_templates: { ...DEFAULT_PATH_TEMPLATES },
enable_metadata_archive_db: false,
language: 'en',
show_only_sfw: false,
proxy_enabled: false,
proxy_type: 'http',
proxy_host: '',
proxy_port: '',
proxy_username: '',
proxy_password: '',
example_images_path: '',
optimizeExampleImages: true,
autoDownloadExampleImages: false
};
Object.keys(backendDefaults).forEach(key => { const baseMappings = backendSettings?.base_model_path_mappings;
if (state.global.settings[key] === undefined || state.global.settings[key] === null) { if (baseMappings && typeof baseMappings === 'object' && !Array.isArray(baseMappings)) {
state.global.settings[key] = backendDefaults[key]; merged.base_model_path_mappings = baseMappings;
} else {
merged.base_model_path_mappings = defaults.base_model_path_mappings;
}
let templates = backendSettings?.download_path_templates;
if (typeof templates === 'string') {
try {
const parsed = JSON.parse(templates);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
templates = parsed;
}
} catch (parseError) {
console.warn('Failed to parse download_path_templates string from backend, using defaults');
templates = null;
} }
}); }
// Ensure all model types have templates if (!templates || typeof templates !== 'object' || Array.isArray(templates)) {
Object.keys(DEFAULT_PATH_TEMPLATES).forEach(modelType => { templates = {};
if (!state.global.settings.download_path_templates[modelType]) { }
state.global.settings.download_path_templates[modelType] = DEFAULT_PATH_TEMPLATES[modelType];
}
});
}
saveFrontendSettingsToStorage() { merged.download_path_templates = { ...DEFAULT_PATH_TEMPLATES, ...templates };
// Save only frontend-specific settings to localStorage
const frontendOnlyKeys = [
'blurMatureContent',
'autoplayOnHover',
'displayDensity',
'cardInfoDisplay',
'includeTriggerWords'
];
const frontendSettings = {}; Object.keys(merged).forEach(key => this.backendSettingKeys.add(key));
frontendOnlyKeys.forEach(key => {
if (state.global.settings[key] !== undefined) {
frontendSettings[key] = state.global.settings[key];
}
});
setStorageItem('settings', frontendSettings); return merged;
} }
// Helper method to determine if a setting should be saved to backend // Helper method to determine if a setting should be saved to backend
isBackendSetting(settingKey) { isBackendSetting(settingKey) {
const backendKeys = [ return this.backendSettingKeys.has(settingKey);
'civitai_api_key',
'default_lora_root',
'default_checkpoint_root',
'default_embedding_root',
'base_model_path_mappings',
'download_path_templates',
'enable_metadata_archive_db',
'language',
'show_only_sfw',
'proxy_enabled',
'proxy_type',
'proxy_host',
'proxy_port',
'proxy_username',
'proxy_password',
'example_images_path',
'optimizeExampleImages',
'autoDownloadExampleImages'
];
return backendKeys.includes(settingKey);
} }
// Helper method to save setting based on whether it's frontend or backend // Helper method to save setting based on whether it's frontend or backend
@@ -207,36 +123,35 @@ export class SettingsManager {
// Update state // Update state
state.global.settings[settingKey] = value; state.global.settings[settingKey] = value;
if (this.isBackendSetting(settingKey)) { if (!this.isBackendSetting(settingKey)) {
// Save to backend return;
try { }
const payload = {};
payload[settingKey] = value;
const response = await fetch('/api/lm/settings', { // Save to backend
method: 'POST', try {
headers: { const payload = {};
'Content-Type': 'application/json', payload[settingKey] = value;
},
body: JSON.stringify(payload)
});
if (!response.ok) { const response = await fetch('/api/lm/settings', {
throw new Error('Failed to save setting to backend'); method: 'POST',
} headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
});
// Parse response and check for success if (!response.ok) {
const data = await response.json(); throw new Error('Failed to save setting to backend');
if (data.success === false) {
throw new Error(data.error || 'Failed to save setting to backend');
}
} catch (error) {
console.error(`Failed to save backend setting ${settingKey}:`, error);
throw error;
} }
} else {
// Save frontend settings to localStorage // Parse response and check for success
this.saveFrontendSettingsToStorage(); const data = await response.json();
if (data.success === false) {
throw new Error(data.error || 'Failed to save setting to backend');
}
} catch (error) {
console.error(`Failed to save backend setting ${settingKey}:`, error);
throw error;
} }
} }
@@ -298,43 +213,42 @@ export class SettingsManager {
// Set frontend settings from state // Set frontend settings from state
const blurMatureContentCheckbox = document.getElementById('blurMatureContent'); const blurMatureContentCheckbox = document.getElementById('blurMatureContent');
if (blurMatureContentCheckbox) { if (blurMatureContentCheckbox) {
blurMatureContentCheckbox.checked = state.global.settings.blurMatureContent; blurMatureContentCheckbox.checked = state.global.settings.blur_mature_content ?? true;
} }
const showOnlySFWCheckbox = document.getElementById('showOnlySFW'); const showOnlySFWCheckbox = document.getElementById('showOnlySFW');
if (showOnlySFWCheckbox) { if (showOnlySFWCheckbox) {
// Sync with state (backend will set this via template) showOnlySFWCheckbox.checked = state.global.settings.show_only_sfw ?? false;
state.global.settings.show_only_sfw = showOnlySFWCheckbox.checked;
} }
// Set video autoplay on hover setting // Set video autoplay on hover setting
const autoplayOnHoverCheckbox = document.getElementById('autoplayOnHover'); const autoplayOnHoverCheckbox = document.getElementById('autoplayOnHover');
if (autoplayOnHoverCheckbox) { if (autoplayOnHoverCheckbox) {
autoplayOnHoverCheckbox.checked = state.global.settings.autoplayOnHover || false; autoplayOnHoverCheckbox.checked = state.global.settings.autoplay_on_hover || false;
} }
// Set display density setting // Set display density setting
const displayDensitySelect = document.getElementById('displayDensity'); const displayDensitySelect = document.getElementById('displayDensity');
if (displayDensitySelect) { if (displayDensitySelect) {
displayDensitySelect.value = state.global.settings.displayDensity || 'default'; displayDensitySelect.value = state.global.settings.display_density || 'default';
} }
// Set card info display setting // Set card info display setting
const cardInfoDisplaySelect = document.getElementById('cardInfoDisplay'); const cardInfoDisplaySelect = document.getElementById('cardInfoDisplay');
if (cardInfoDisplaySelect) { if (cardInfoDisplaySelect) {
cardInfoDisplaySelect.value = state.global.settings.cardInfoDisplay || 'always'; cardInfoDisplaySelect.value = state.global.settings.card_info_display || 'always';
} }
// Set optimize example images setting // Set optimize example images setting
const optimizeExampleImagesCheckbox = document.getElementById('optimizeExampleImages'); const optimizeExampleImagesCheckbox = document.getElementById('optimizeExampleImages');
if (optimizeExampleImagesCheckbox) { if (optimizeExampleImagesCheckbox) {
optimizeExampleImagesCheckbox.checked = state.global.settings.optimizeExampleImages || false; optimizeExampleImagesCheckbox.checked = state.global.settings.optimize_example_images ?? true;
} }
// Set auto download example images setting // Set auto download example images setting
const autoDownloadExampleImagesCheckbox = document.getElementById('autoDownloadExampleImages'); const autoDownloadExampleImagesCheckbox = document.getElementById('autoDownloadExampleImages');
if (autoDownloadExampleImagesCheckbox) { if (autoDownloadExampleImagesCheckbox) {
autoDownloadExampleImagesCheckbox.checked = state.global.settings.autoDownloadExampleImages || false; autoDownloadExampleImagesCheckbox.checked = state.global.settings.auto_download_example_images || false;
} }
// Load download path templates // Load download path templates
@@ -343,7 +257,7 @@ export class SettingsManager {
// Set include trigger words setting // Set include trigger words setting
const includeTriggerWordsCheckbox = document.getElementById('includeTriggerWords'); const includeTriggerWordsCheckbox = document.getElementById('includeTriggerWords');
if (includeTriggerWordsCheckbox) { if (includeTriggerWordsCheckbox) {
includeTriggerWordsCheckbox.checked = state.global.settings.includeTriggerWords || false; includeTriggerWordsCheckbox.checked = state.global.settings.include_trigger_words || false;
} }
// Load metadata archive settings // Load metadata archive settings
@@ -885,34 +799,13 @@ export class SettingsManager {
const value = element.checked; const value = element.checked;
try { try {
// Update frontend state with mapped keys await this.saveSetting(settingKey, value);
if (settingKey === 'blur_mature_content') {
await this.saveSetting('blurMatureContent', value);
} else if (settingKey === 'show_only_sfw') {
await this.saveSetting('show_only_sfw', value);
} else if (settingKey === 'autoplay_on_hover') {
await this.saveSetting('autoplayOnHover', value);
} else if (settingKey === 'optimize_example_images') {
await this.saveSetting('optimizeExampleImages', value);
} else if (settingKey === 'auto_download_example_images') {
await this.saveSetting('autoDownloadExampleImages', value);
} else if (settingKey === 'compact_mode') {
await this.saveSetting('compactMode', value);
} else if (settingKey === 'include_trigger_words') {
await this.saveSetting('includeTriggerWords', value);
} else if (settingKey === 'enable_metadata_archive_db') {
await this.saveSetting('enable_metadata_archive_db', value);
} else if (settingKey === 'proxy_enabled') {
await this.saveSetting('proxy_enabled', value);
// Toggle visibility of proxy settings group if (settingKey === 'proxy_enabled') {
const proxySettingsGroup = document.getElementById('proxySettingsGroup'); const proxySettingsGroup = document.getElementById('proxySettingsGroup');
if (proxySettingsGroup) { if (proxySettingsGroup) {
proxySettingsGroup.style.display = value ? 'block' : 'none'; proxySettingsGroup.style.display = value ? 'block' : 'none';
} }
} else {
// For any other settings that might be added in the future
await this.saveSetting(settingKey, value);
} }
// Refresh metadata archive status when enable setting changes // Refresh metadata archive status when enable setting changes
@@ -946,11 +839,6 @@ export class SettingsManager {
}, 'success'); }, 'success');
} }
// Special handling for metadata archive settings
if (settingKey === 'enable_metadata_archive_db') {
await this.updateMetadataArchiveStatus();
}
} catch (error) { } catch (error) {
showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error'); showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error');
} }
@@ -964,22 +852,7 @@ export class SettingsManager {
try { try {
// Update frontend state with mapped keys // Update frontend state with mapped keys
if (settingKey === 'default_lora_root') { await this.saveSetting(settingKey, value);
await this.saveSetting('default_lora_root', value);
} else if (settingKey === 'default_checkpoint_root') {
await this.saveSetting('default_checkpoint_root', value);
} else if (settingKey === 'default_embedding_root') {
await this.saveSetting('default_embedding_root', value);
} else if (settingKey === 'display_density') {
await this.saveSetting('displayDensity', value);
} else if (settingKey === 'card_info_display') {
await this.saveSetting('cardInfoDisplay', value);
} else if (settingKey === 'proxy_type') {
await this.saveSetting('proxy_type', value);
} else {
// For any other settings that might be added in the future
await this.saveSetting(settingKey, value);
}
// Apply frontend settings immediately // Apply frontend settings immediately
this.applyFrontendSettings(); this.applyFrontendSettings();
@@ -1347,7 +1220,7 @@ export class SettingsManager {
applyFrontendSettings() { applyFrontendSettings() {
// Apply autoplay setting to existing videos in card previews // Apply autoplay setting to existing videos in card previews
const autoplayOnHover = state.global.settings.autoplayOnHover; const autoplayOnHover = state.global.settings.autoplay_on_hover;
document.querySelectorAll('.card-preview video').forEach(video => { document.querySelectorAll('.card-preview video').forEach(video => {
// Remove previous event listeners by cloning and replacing the element // Remove previous event listeners by cloning and replacing the element
const videoParent = video.parentElement; const videoParent = video.parentElement;
@@ -1377,7 +1250,7 @@ export class SettingsManager {
// Apply display density class to grid // Apply display density class to grid
const grid = document.querySelector('.card-grid'); const grid = document.querySelector('.card-grid');
if (grid) { if (grid) {
const density = state.global.settings.displayDensity || 'default'; const density = state.global.settings.display_density || 'default';
// Remove all density classes first // Remove all density classes first
grid.classList.remove('default-density', 'medium-density', 'compact-density'); grid.classList.remove('default-density', 'medium-density', 'compact-density');
@@ -1387,7 +1260,7 @@ export class SettingsManager {
} }
// Apply card info display setting // Apply card info display setting
const cardInfoDisplay = state.global.settings.cardInfoDisplay || 'always'; const cardInfoDisplay = state.global.settings.card_info_display || 'always';
document.body.classList.toggle('hover-reveal', cardInfoDisplay === 'hover'); document.body.classList.toggle('hover-reveal', cardInfoDisplay === 'hover');
} }
} }

View File

@@ -1,20 +1,43 @@
// Create the new hierarchical state structure // Create the new hierarchical state structure
import { getStorageItem, getMapFromStorage } from '../utils/storageHelpers.js'; import { getStorageItem, getMapFromStorage } from '../utils/storageHelpers.js';
import { MODEL_TYPES } from '../api/apiConfig.js'; import { MODEL_TYPES } from '../api/apiConfig.js';
import { DEFAULT_PATH_TEMPLATES } from '../utils/constants.js';
// Load only frontend settings from localStorage with defaults const DEFAULT_SETTINGS_BASE = Object.freeze({
// Backend settings will be loaded by SettingsManager from the backend civitai_api_key: '',
const savedSettings = getStorageItem('settings', { language: 'en',
blurMatureContent: true,
show_only_sfw: false, show_only_sfw: false,
cardInfoDisplay: 'always', enable_metadata_archive_db: false,
autoplayOnHover: false, proxy_enabled: false,
displayDensity: 'default', proxy_type: 'http',
optimizeExampleImages: true, proxy_host: '',
autoDownloadExampleImages: true, proxy_port: '',
includeTriggerWords: false proxy_username: '',
proxy_password: '',
default_lora_root: '',
default_checkpoint_root: '',
default_embedding_root: '',
base_model_path_mappings: {},
download_path_templates: {},
example_images_path: '',
optimize_example_images: true,
auto_download_example_images: false,
blur_mature_content: true,
autoplay_on_hover: false,
display_density: 'default',
card_info_display: 'always',
include_trigger_words: false,
compact_mode: false,
}); });
export function createDefaultSettings() {
return {
...DEFAULT_SETTINGS_BASE,
base_model_path_mappings: {},
download_path_templates: { ...DEFAULT_PATH_TEMPLATES },
};
}
// Load preview versions from localStorage for each model type // Load preview versions from localStorage for each model type
const loraPreviewVersions = getMapFromStorage('loras_preview_versions'); const loraPreviewVersions = getMapFromStorage('loras_preview_versions');
const checkpointPreviewVersions = getMapFromStorage('checkpoints_preview_versions'); const checkpointPreviewVersions = getMapFromStorage('checkpoints_preview_versions');
@@ -23,7 +46,7 @@ const embeddingPreviewVersions = getMapFromStorage('embeddings_preview_versions'
export const state = { export const state = {
// Global state // Global state
global: { global: {
settings: savedSettings, settings: createDefaultSettings(),
loadingManager: null, loadingManager: null,
observer: null, observer: null,
}, },

View File

@@ -102,7 +102,7 @@ export class VirtualScroller {
const availableContentWidth = containerWidth - paddingLeft - paddingRight; const availableContentWidth = containerWidth - paddingLeft - paddingRight;
// Get display density setting // Get display density setting
const displayDensity = state.global.settings?.displayDensity || 'default'; const displayDensity = state.global.settings?.display_density || 'default';
// Set exact column counts and grid widths to match CSS container widths // Set exact column counts and grid widths to match CSS container widths
let maxColumns, maxGridWidth; let maxColumns, maxGridWidth;

View File

@@ -339,7 +339,7 @@ export function copyLoraSyntax(card) {
const baseSyntax = buildLoraSyntax(card.dataset.file_name, usageTips); const baseSyntax = buildLoraSyntax(card.dataset.file_name, usageTips);
// Check if trigger words should be included // Check if trigger words should be included
const includeTriggerWords = state.global.settings.includeTriggerWords; const includeTriggerWords = state.global.settings.include_trigger_words;
if (!includeTriggerWords) { if (!includeTriggerWords) {
const message = translate('uiHelpers.lora.syntaxCopied', {}, 'LoRA syntax copied to clipboard'); const message = translate('uiHelpers.lora.syntaxCopied', {}, 'LoRA syntax copied to clipboard');