import { modalManager } from './ModalManager.js'; import { showToast } from '../utils/uiHelpers.js'; import { state, createDefaultSettings } from '../state/index.js'; import { resetAndReload } from '../api/modelApiFactory.js'; import { DOWNLOAD_PATH_TEMPLATES, MAPPABLE_BASE_MODELS, PATH_TEMPLATE_PLACEHOLDERS, DEFAULT_PATH_TEMPLATES, DEFAULT_PRIORITY_TAG_CONFIG } from '../utils/constants.js'; import { translate } from '../utils/i18nHelpers.js'; import { i18n } from '../i18n/index.js'; import { configureModelCardVideo } from '../components/shared/ModelCard.js'; import { validatePriorityTagString, getPriorityTagSuggestionsMap, invalidatePriorityTagSuggestionsCache } from '../utils/priorityTagHelpers.js'; import { bannerService } from './BannerService.js'; import { sidebarManager } from '../components/SidebarManager.js'; export class SettingsManager { constructor() { this.initialized = false; this.isOpen = false; this.initializationPromise = null; this.availableLibraries = {}; this.activeLibrary = ''; this.settingsFilePath = null; this.registeredStartupBannerIds = new Set(); // Add initialization to sync with modal state 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 this.initializationPromise = this.initializeSettings(); this.initialize(); } // Add method to wait for initialization to complete async waitForInitialization() { if (this.initializationPromise) { await this.initializationPromise; } } async initializeSettings() { // Reset to defaults before syncing state.global.settings = createDefaultSettings(); // Sync settings from backend to frontend await this.syncSettingsFromBackend(); } async syncSettingsFromBackend() { try { const response = await fetch('/api/lm/settings'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.success && data.settings) { state.global.settings = this.mergeSettingsWithDefaults(data.settings); this.settingsFilePath = data.settings.settings_file || this.settingsFilePath; this.registerStartupMessages(data.messages); console.log('Settings synced from backend'); } else { console.error('Failed to sync settings from backend:', data.error); state.global.settings = this.mergeSettingsWithDefaults(); this.registerStartupMessages(data?.messages); } } catch (error) { console.error('Failed to sync settings from backend:', error); state.global.settings = this.mergeSettingsWithDefaults(); this.registerStartupMessages(); } 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); } } mergeSettingsWithDefaults(backendSettings = {}) { const defaults = createDefaultSettings(); const merged = { ...defaults, ...backendSettings }; const baseMappings = backendSettings?.base_model_path_mappings; if (baseMappings && typeof baseMappings === 'object' && !Array.isArray(baseMappings)) { 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; } } if (!templates || typeof templates !== 'object' || Array.isArray(templates)) { templates = {}; } merged.download_path_templates = { ...DEFAULT_PATH_TEMPLATES, ...templates }; const priorityTags = backendSettings?.priority_tags; const normalizedPriority = { ...DEFAULT_PRIORITY_TAG_CONFIG }; if (priorityTags && typeof priorityTags === 'object' && !Array.isArray(priorityTags)) { Object.entries(priorityTags).forEach(([modelType, configValue]) => { if (typeof configValue === 'string') { normalizedPriority[modelType] = configValue.trim(); } }); } merged.priority_tags = normalizedPriority; merged.auto_organize_exclusions = this.normalizePatternList( backendSettings?.auto_organize_exclusions ?? defaults.auto_organize_exclusions ); Object.keys(merged).forEach(key => this.backendSettingKeys.add(key)); return merged; } normalizePatternList(value) { if (Array.isArray(value)) { const sanitized = value .map(item => typeof item === 'string' ? item.trim() : '') .filter(Boolean); return [...new Set(sanitized)]; } if (typeof value === 'string') { const sanitized = value .replace(/\n/g, ',') .replace(/;/g, ',') .split(',') .map(part => part.trim()) .filter(Boolean); return [...new Set(sanitized)]; } return []; } registerStartupMessages(messages = []) { if (!Array.isArray(messages) || messages.length === 0) { return; } const severityPriority = { error: 90, warning: 60, info: 30, }; messages.forEach((message, index) => { if (!message || typeof message !== 'object') { return; } if (!this.settingsFilePath && typeof message.settings_file === 'string') { this.settingsFilePath = message.settings_file; } const bannerId = `startup-${message.code || index}`; if (this.registeredStartupBannerIds.has(bannerId)) { return; } const severity = (message.severity || 'info').toLowerCase(); const bannerTitle = message.title || 'Configuration notice'; const bannerContent = message.message || message.content || ''; const priority = typeof message.priority === 'number' ? message.priority : severityPriority[severity] || severityPriority.info; const dismissible = message.dismissible !== false; const normalizedActions = Array.isArray(message.actions) ? message.actions.map(action => ({ text: action.label || action.text || 'Review settings', icon: action.icon || 'fas fa-cog', action: action.action, type: action.type || 'primary', url: action.url, })) : []; bannerService.registerBanner(bannerId, { id: bannerId, title: bannerTitle, content: bannerContent, actions: normalizedActions, dismissible, priority, onRegister: (bannerElement) => { normalizedActions.forEach(action => { if (!action.action) { return; } const button = bannerElement.querySelector(`.banner-action[data-action="${action.action}"]`); if (button) { button.addEventListener('click', (event) => { event.preventDefault(); this.handleStartupBannerAction(action.action); }); } }); }, }); this.registeredStartupBannerIds.add(bannerId); }); } handleStartupBannerAction(action) { switch (action) { case 'open-settings-modal': modalManager.showModal('settingsModal'); break; case 'open-settings-location': this.openSettingsFileLocation(); break; default: console.warn('Unhandled startup banner action:', action); } } // Helper method to determine if a setting should be saved to backend isBackendSetting(settingKey) { return this.backendSettingKeys.has(settingKey); } // Helper method to save setting based on whether it's frontend or backend async saveSetting(settingKey, value) { // Update state state.global.settings[settingKey] = value; if (!this.isBackendSetting(settingKey)) { return; } // Save to backend try { const payload = {}; payload[settingKey] = value; const response = await fetch('/api/lm/settings', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error('Failed to save setting to backend'); } // Parse response and check for success 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; } } initialize() { if (this.initialized) return; // Add event listener to sync state when modal is closed via other means (like Escape key) const settingsModal = document.getElementById('settingsModal'); if (settingsModal) { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.attributeName === 'style') { this.isOpen = settingsModal.style.display === 'block'; // When modal is opened, update checkbox state from current settings if (this.isOpen) { this.loadSettingsToUI(); } } }); }); observer.observe(settingsModal, { attributes: true }); } // Add event listeners for all toggle-visibility buttons document.querySelectorAll('.toggle-visibility').forEach(button => { button.addEventListener('click', () => this.toggleInputVisibility(button)); }); const openSettingsLocationButton = document.querySelector('.settings-open-location-trigger'); if (openSettingsLocationButton) { if (openSettingsLocationButton.dataset.settingsPath) { this.settingsFilePath = openSettingsLocationButton.dataset.settingsPath; } openSettingsLocationButton.addEventListener('click', () => { const filePath = openSettingsLocationButton.dataset.settingsPath; this.openSettingsFileLocation(filePath); }); } ['lora', 'checkpoint', 'embedding'].forEach(modelType => { const customInput = document.getElementById(`${modelType}CustomTemplate`); if (customInput) { customInput.addEventListener('input', (e) => { const template = e.target.value; settingsManager.validateTemplate(modelType, template); settingsManager.updateTemplatePreview(modelType, template); }); customInput.addEventListener('blur', (e) => { const template = e.target.value; if (settingsManager.validateTemplate(modelType, template)) { settingsManager.updateTemplate(modelType, template); } }); customInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.target.blur(); } }); } }); const autoOrganizeInput = document.getElementById('autoOrganizeExclusions'); if (autoOrganizeInput) { autoOrganizeInput.addEventListener('keydown', (event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); this.saveAutoOrganizeExclusions(); } }); } this.setupPriorityTagInputs(); this.initialized = true; } async openSettingsFileLocation(filePath) { const targetPath = filePath || this.settingsFilePath || document.querySelector('.settings-open-location-trigger')?.dataset.settingsPath; if (!targetPath) { showToast('settings.openSettingsFileLocation.failed', {}, 'error'); return; } try { const response = await fetch('/api/lm/open-file-location', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ file_path: targetPath }), }); if (!response.ok) { throw new Error(`Request failed with status ${response.status}`); } this.settingsFilePath = targetPath; showToast('settings.openSettingsFileLocation.success', {}, 'success'); } catch (error) { console.error('Failed to open settings file location:', error); showToast('settings.openSettingsFileLocation.failed', {}, 'error'); } } async loadSettingsToUI() { // Set frontend settings from state const blurMatureContentCheckbox = document.getElementById('blurMatureContent'); if (blurMatureContentCheckbox) { blurMatureContentCheckbox.checked = state.global.settings.blur_mature_content ?? true; } const showOnlySFWCheckbox = document.getElementById('showOnlySFW'); if (showOnlySFWCheckbox) { showOnlySFWCheckbox.checked = state.global.settings.show_only_sfw ?? false; } const usePortableCheckbox = document.getElementById('usePortableSettings'); if (usePortableCheckbox) { usePortableCheckbox.checked = !!state.global.settings.use_portable_settings; } const autoOrganizeExclusionsInput = document.getElementById('autoOrganizeExclusions'); if (autoOrganizeExclusionsInput) { const patterns = this.normalizePatternList(state.global.settings.auto_organize_exclusions); autoOrganizeExclusionsInput.value = patterns.join(', '); } const autoOrganizeExclusionsError = document.getElementById('autoOrganizeExclusionsError'); if (autoOrganizeExclusionsError) { autoOrganizeExclusionsError.textContent = ''; } // Set video autoplay on hover setting const autoplayOnHoverCheckbox = document.getElementById('autoplayOnHover'); if (autoplayOnHoverCheckbox) { autoplayOnHoverCheckbox.checked = state.global.settings.autoplay_on_hover || false; } // Set display density setting const displayDensitySelect = document.getElementById('displayDensity'); if (displayDensitySelect) { displayDensitySelect.value = state.global.settings.display_density || 'default'; } // Set card info display setting const cardInfoDisplaySelect = document.getElementById('cardInfoDisplay'); if (cardInfoDisplaySelect) { cardInfoDisplaySelect.value = state.global.settings.card_info_display || 'always'; } const showFolderSidebarCheckbox = document.getElementById('showFolderSidebar'); if (showFolderSidebarCheckbox) { const showSidebarSetting = state.global.settings.show_folder_sidebar; showFolderSidebarCheckbox.checked = showSidebarSetting !== false; } // Set model card footer action const modelCardFooterActionSelect = document.getElementById('modelCardFooterAction'); if (modelCardFooterActionSelect) { modelCardFooterActionSelect.value = state.global.settings.model_card_footer_action || 'example_images'; } // Set model name display setting const modelNameDisplaySelect = document.getElementById('modelNameDisplay'); if (modelNameDisplaySelect) { modelNameDisplaySelect.value = state.global.settings.model_name_display || 'model_name'; } const updateFlagStrategySelect = document.getElementById('updateFlagStrategy'); if (updateFlagStrategySelect) { updateFlagStrategySelect.value = state.global.settings.update_flag_strategy || 'same_base'; } // Set optimize example images setting const optimizeExampleImagesCheckbox = document.getElementById('optimizeExampleImages'); if (optimizeExampleImagesCheckbox) { optimizeExampleImagesCheckbox.checked = state.global.settings.optimize_example_images ?? true; } // Set auto download example images setting const autoDownloadExampleImagesCheckbox = document.getElementById('autoDownloadExampleImages'); if (autoDownloadExampleImagesCheckbox) { autoDownloadExampleImagesCheckbox.checked = state.global.settings.auto_download_example_images || false; } // Load download path templates this.loadDownloadPathTemplates(); // Load priority tag settings this.loadPriorityTagSettings(); // Set include trigger words setting const includeTriggerWordsCheckbox = document.getElementById('includeTriggerWords'); if (includeTriggerWordsCheckbox) { includeTriggerWordsCheckbox.checked = state.global.settings.include_trigger_words || false; } // Load metadata archive settings await this.loadMetadataArchiveSettings(); // Load base model path mappings this.loadBaseModelMappings(); // Load library options await this.loadLibraries(); // Load default lora root await this.loadLoraRoots(); // Load default checkpoint root await this.loadCheckpointRoots(); // Load default embedding root await this.loadEmbeddingRoots(); // Load language setting const languageSelect = document.getElementById('languageSelect'); if (languageSelect) { const currentLanguage = state.global.settings.language || 'en'; languageSelect.value = currentLanguage; } this.loadProxySettings(); } setupPriorityTagInputs() { ['lora', 'checkpoint', 'embedding'].forEach((modelType) => { const textarea = document.getElementById(`${modelType}PriorityTagsInput`); if (!textarea) { return; } textarea.addEventListener('input', () => this.handlePriorityTagInput(modelType)); textarea.addEventListener('blur', () => this.handlePriorityTagSave(modelType)); textarea.addEventListener('keydown', (event) => this.handlePriorityTagKeyDown(event, modelType)); }); } loadPriorityTagSettings() { const priorityConfig = state.global.settings.priority_tags || {}; ['lora', 'checkpoint', 'embedding'].forEach((modelType) => { const textarea = document.getElementById(`${modelType}PriorityTagsInput`); if (!textarea) { return; } const storedValue = priorityConfig[modelType] ?? DEFAULT_PRIORITY_TAG_CONFIG[modelType] ?? ''; textarea.value = storedValue; this.displayPriorityTagValidation(modelType, true, []); }); } handlePriorityTagInput(modelType) { const textarea = document.getElementById(`${modelType}PriorityTagsInput`); if (!textarea) { return; } const validation = validatePriorityTagString(textarea.value); this.displayPriorityTagValidation(modelType, validation.valid, validation.errors); } handlePriorityTagKeyDown(event, modelType) { if (event.key !== 'Enter') { return; } if (event.shiftKey) { return; } event.preventDefault(); this.handlePriorityTagSave(modelType); } async handlePriorityTagSave(modelType) { const textarea = document.getElementById(`${modelType}PriorityTagsInput`); if (!textarea) { return; } const validation = validatePriorityTagString(textarea.value); if (!validation.valid) { this.displayPriorityTagValidation(modelType, false, validation.errors); return; } const sanitized = validation.formatted; const currentValue = state.global.settings.priority_tags?.[modelType] || ''; this.displayPriorityTagValidation(modelType, true, []); if (sanitized === currentValue) { textarea.value = sanitized; return; } const updatedConfig = { ...state.global.settings.priority_tags, [modelType]: sanitized, }; try { textarea.value = sanitized; await this.saveSetting('priority_tags', updatedConfig); showToast('settings.priorityTags.saveSuccess', {}, 'success'); await this.refreshPriorityTagSuggestions(); } catch (error) { console.error('Failed to save priority tag configuration:', error); showToast('settings.priorityTags.saveError', {}, 'error'); } } displayPriorityTagValidation(modelType, isValid, errors = []) { const textarea = document.getElementById(`${modelType}PriorityTagsInput`); const errorElement = document.getElementById(`${modelType}PriorityTagsError`); if (!textarea) { return; } if (isValid || errors.length === 0) { textarea.classList.remove('settings-input-error'); if (errorElement) { errorElement.textContent = ''; errorElement.style.display = 'none'; } return; } textarea.classList.add('settings-input-error'); if (errorElement) { const message = this.getPriorityTagErrorMessage(errors[0]); errorElement.textContent = message; errorElement.style.display = 'block'; } } getPriorityTagErrorMessage(error) { if (!error) { return ''; } const entryIndex = error.index ?? 0; switch (error.type) { case 'missingClosingParen': return translate('settings.priorityTags.validation.missingClosingParen', { index: entryIndex }, `Entry ${entryIndex} is missing a closing parenthesis.`); case 'missingCanonical': return translate('settings.priorityTags.validation.missingCanonical', { index: entryIndex }, `Entry ${entryIndex} must include a canonical tag.`); case 'duplicateCanonical': return translate('settings.priorityTags.validation.duplicateCanonical', { tag: error.canonical }, `The canonical tag "${error.canonical}" is duplicated.`); default: return translate('settings.priorityTags.validation.unknown', {}, 'Invalid priority tag configuration.'); } } async refreshPriorityTagSuggestions() { invalidatePriorityTagSuggestionsCache(); try { await getPriorityTagSuggestionsMap(); window.dispatchEvent(new CustomEvent('lm:priority-tags-updated')); } catch (error) { console.warn('Failed to refresh priority tag suggestions:', error); } } loadProxySettings() { // Load proxy enabled setting const proxyEnabledCheckbox = document.getElementById('proxyEnabled'); if (proxyEnabledCheckbox) { proxyEnabledCheckbox.checked = state.global.settings.proxy_enabled || false; // Add event listener for toggling proxy settings group visibility proxyEnabledCheckbox.addEventListener('change', () => { const proxySettingsGroup = document.getElementById('proxySettingsGroup'); if (proxySettingsGroup) { proxySettingsGroup.style.display = proxyEnabledCheckbox.checked ? 'block' : 'none'; } }); // Set initial visibility const proxySettingsGroup = document.getElementById('proxySettingsGroup'); if (proxySettingsGroup) { proxySettingsGroup.style.display = proxyEnabledCheckbox.checked ? 'block' : 'none'; } } // Load proxy type const proxyTypeSelect = document.getElementById('proxyType'); if (proxyTypeSelect) { proxyTypeSelect.value = state.global.settings.proxy_type || 'http'; } // Load proxy host const proxyHostInput = document.getElementById('proxyHost'); if (proxyHostInput) { proxyHostInput.value = state.global.settings.proxy_host || ''; } // Load proxy port const proxyPortInput = document.getElementById('proxyPort'); if (proxyPortInput) { proxyPortInput.value = state.global.settings.proxy_port || ''; } // Load proxy username const proxyUsernameInput = document.getElementById('proxyUsername'); if (proxyUsernameInput) { proxyUsernameInput.value = state.global.settings.proxy_username || ''; } // Load proxy password const proxyPasswordInput = document.getElementById('proxyPassword'); if (proxyPasswordInput) { proxyPasswordInput.value = state.global.settings.proxy_password || ''; } } async loadLibraries() { const librarySelect = document.getElementById('librarySelect'); if (!librarySelect) { return; } const setPlaceholderOption = (textKey, fallback) => { librarySelect.innerHTML = ''; const option = document.createElement('option'); option.value = ''; option.textContent = translate(textKey, {}, fallback); librarySelect.appendChild(option); }; setPlaceholderOption('settings.folderSettings.loadingLibraries', 'Loading libraries...'); librarySelect.disabled = true; try { const response = await fetch('/api/lm/settings/libraries'); if (!response.ok) { throw new Error('Failed to fetch library registry'); } const data = await response.json(); if (data.success === false) { throw new Error(data.error || 'Failed to fetch library registry'); } const libraries = data.libraries && typeof data.libraries === 'object' ? data.libraries : {}; this.availableLibraries = libraries; const entries = Object.entries(libraries); if (entries.length === 0) { this.activeLibrary = ''; setPlaceholderOption('settings.folderSettings.noLibraries', 'No libraries configured'); return; } const activeName = data.active_library && libraries[data.active_library] ? data.active_library : entries[0][0]; this.activeLibrary = activeName; librarySelect.innerHTML = ''; const fragment = document.createDocumentFragment(); entries .sort((a, b) => { const nameA = this.getLibraryDisplayName(a[0], a[1]).toLowerCase(); const nameB = this.getLibraryDisplayName(b[0], b[1]).toLowerCase(); return nameA.localeCompare(nameB); }) .forEach(([name, info]) => { const option = document.createElement('option'); option.value = name; option.textContent = this.getLibraryDisplayName(name, info); fragment.appendChild(option); }); librarySelect.appendChild(fragment); librarySelect.value = activeName; librarySelect.disabled = entries.length <= 1; } catch (error) { console.error('Error loading libraries:', error); setPlaceholderOption('settings.folderSettings.noLibraries', 'No libraries configured'); this.availableLibraries = {}; this.activeLibrary = ''; librarySelect.disabled = true; showToast('toast.settings.libraryLoadFailed', { message: error.message }, 'error'); } } getLibraryDisplayName(libraryName, libraryData = {}) { if (libraryData && typeof libraryData === 'object') { const metadata = libraryData.metadata; if (metadata && typeof metadata === 'object' && metadata.display_name) { return metadata.display_name; } if (libraryData.display_name) { return libraryData.display_name; } } return libraryName; } async handleLibraryChange() { const librarySelect = document.getElementById('librarySelect'); if (!librarySelect) { return; } const selectedLibrary = librarySelect.value; if (!selectedLibrary || selectedLibrary === this.activeLibrary) { librarySelect.value = this.activeLibrary; return; } librarySelect.disabled = true; try { state.loadingManager.showSimpleLoading('Activating library...'); await this.activateLibrary(selectedLibrary); // Add a short delay before reloading the page await new Promise(resolve => setTimeout(resolve, 300)); window.location.reload(); } catch (error) { console.error('Failed to activate library:', error); showToast('toast.settings.libraryActivateFailed', { message: error.message }, 'error'); await this.loadLibraries(); } finally { state.loadingManager.hide(); if (!document.hidden) { librarySelect.disabled = librarySelect.options.length <= 1; } } } async activateLibrary(libraryName) { const response = await fetch('/api/lm/settings/libraries/activate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ library: libraryName }), }); if (!response.ok) { throw new Error(`Request failed with status ${response.status}`); } const data = await response.json(); if (data.success === false) { throw new Error(data.error || 'Failed to activate library'); } const activeName = data.active_library || libraryName; this.activeLibrary = activeName; return data; } async loadLoraRoots() { try { const defaultLoraRootSelect = document.getElementById('defaultLoraRoot'); if (!defaultLoraRootSelect) return; // Fetch lora roots const response = await fetch('/api/lm/loras/roots'); if (!response.ok) { throw new Error('Failed to fetch LoRA roots'); } const data = await response.json(); if (!data.roots || data.roots.length === 0) { throw new Error('No LoRA roots found'); } // Clear existing options except the first one (No Default) const noDefaultOption = defaultLoraRootSelect.querySelector('option[value=""]'); defaultLoraRootSelect.innerHTML = ''; defaultLoraRootSelect.appendChild(noDefaultOption); // Add options for each root data.roots.forEach(root => { const option = document.createElement('option'); option.value = root; option.textContent = root; defaultLoraRootSelect.appendChild(option); }); // Set selected value from settings const defaultRoot = state.global.settings.default_lora_root || ''; defaultLoraRootSelect.value = defaultRoot; } catch (error) { console.error('Error loading LoRA roots:', error); showToast('toast.settings.loraRootsFailed', { message: error.message }, 'error'); } } async loadCheckpointRoots() { try { const defaultCheckpointRootSelect = document.getElementById('defaultCheckpointRoot'); if (!defaultCheckpointRootSelect) return; // Fetch checkpoint roots const response = await fetch('/api/lm/checkpoints/roots'); if (!response.ok) { throw new Error('Failed to fetch checkpoint roots'); } const data = await response.json(); if (!data.roots || data.roots.length === 0) { throw new Error('No checkpoint roots found'); } // Clear existing options except the first one (No Default) const noDefaultOption = defaultCheckpointRootSelect.querySelector('option[value=""]'); defaultCheckpointRootSelect.innerHTML = ''; defaultCheckpointRootSelect.appendChild(noDefaultOption); // Add options for each root data.roots.forEach(root => { const option = document.createElement('option'); option.value = root; option.textContent = root; defaultCheckpointRootSelect.appendChild(option); }); // Set selected value from settings const defaultRoot = state.global.settings.default_checkpoint_root || ''; defaultCheckpointRootSelect.value = defaultRoot; } catch (error) { console.error('Error loading checkpoint roots:', error); showToast('toast.settings.checkpointRootsFailed', { message: error.message }, 'error'); } } async loadEmbeddingRoots() { try { const defaultEmbeddingRootSelect = document.getElementById('defaultEmbeddingRoot'); if (!defaultEmbeddingRootSelect) return; // Fetch embedding roots const response = await fetch('/api/lm/embeddings/roots'); if (!response.ok) { throw new Error('Failed to fetch embedding roots'); } const data = await response.json(); if (!data.roots || data.roots.length === 0) { throw new Error('No embedding roots found'); } // Clear existing options except the first one (No Default) const noDefaultOption = defaultEmbeddingRootSelect.querySelector('option[value=""]'); defaultEmbeddingRootSelect.innerHTML = ''; defaultEmbeddingRootSelect.appendChild(noDefaultOption); // Add options for each root data.roots.forEach(root => { const option = document.createElement('option'); option.value = root; option.textContent = root; defaultEmbeddingRootSelect.appendChild(option); }); // Set selected value from settings const defaultRoot = state.global.settings.default_embedding_root || ''; defaultEmbeddingRootSelect.value = defaultRoot; } catch (error) { console.error('Error loading embedding roots:', error); showToast('toast.settings.embeddingRootsFailed', { message: error.message }, 'error'); } } loadBaseModelMappings() { const mappingsContainer = document.getElementById('baseModelMappingsContainer'); if (!mappingsContainer) return; const mappings = state.global.settings.base_model_path_mappings || {}; // Clear existing mappings mappingsContainer.innerHTML = ''; // Add existing mappings Object.entries(mappings).forEach(([baseModel, pathValue]) => { this.addMappingRow(baseModel, pathValue); }); // Add empty row for new mappings if none exist if (Object.keys(mappings).length === 0) { this.addMappingRow('', ''); } } addMappingRow(baseModel = '', pathValue = '') { const mappingsContainer = document.getElementById('baseModelMappingsContainer'); if (!mappingsContainer) return; const row = document.createElement('div'); row.className = 'mapping-row'; const availableModels = MAPPABLE_BASE_MODELS.filter(model => { const existingMappings = state.global.settings.base_model_path_mappings || {}; return !existingMappings.hasOwnProperty(model) || model === baseModel; }); row.innerHTML = `