Files
arcum42-ComfyUI_SageUtils/js/dialogs/settingsDialog.js
T

622 lines
18 KiB
JavaScript

/**
* Settings Dialog
* Provides a UI for configuring SageUtils settings
* Currently focuses on LLM provider configuration (Ollama and LM Studio)
*/
import { api } from '../../../../scripts/api.js';
import { createDialog } from '../components/dialogManager.js';
import { handleError } from '../shared/errorHandler.js';
import { notifications } from '../shared/notifications.js';
import { app } from '../../../../scripts/app.js';
import { createCheckbox, createRadioGroup, createInput } from '../components/formElements.js';
/**
* Mapping between backend setting keys and ComfyUI setting IDs
*/
const SETTING_KEY_TO_ID_MAP = {
'enable_lmstudio': 'SageUtils.LLM Providers.enable_lmstudio',
'enable_ollama': 'SageUtils.LLM Providers.enable_ollama',
'ollama_custom_url': 'SageUtils.Local Custom Ollama URL.ollama_custom_url',
'ollama_use_custom_url': 'SageUtils.Local Custom Ollama URL.ollama_use_custom_url',
'lmstudio_custom_url': 'SageUtils.Local Custom LM Studio URL.lmstudio_custom_url',
'lmstudio_use_custom_url': 'SageUtils.Local Custom LM Studio URL.lmstudio_use_custom_url'
};
/**
* Show the settings dialog
*/
export async function showSettingsDialog() {
const content = document.createElement('div');
content.style.cssText = `
min-width: 600px;
max-width: 700px;
min-height: 400px;
`;
const dialog = createDialog({
title: 'SageUtils Settings',
content: content,
width: '650px',
height: 'auto',
showFooter: true,
closeOnOverlayClick: false
});
// Create loading indicator
const loadingSection = document.createElement('div');
loadingSection.style.cssText = `
text-align: center;
padding: 40px;
color: #888;
`;
loadingSection.innerHTML = `
<div style="font-size: 16px; margin-bottom: 10px;">Loading settings...</div>
<div style="font-size: 12px;">Please wait</div>
`;
content.appendChild(loadingSection);
// Show the dialog
dialog.show();
try {
// Load current settings
const response = await api.fetchApi('/sage_utils/settings');
if (!response.ok) {
throw new Error(`Failed to load settings: ${response.statusText}`);
}
const result = await response.json();
console.log('Loaded settings from API:', result);
if (!result.success) {
throw new Error(result.error || 'Failed to load settings');
}
const settings = result.settings;
console.log('Settings object:', settings);
// Clear loading and build settings UI
content.innerHTML = '';
buildSettingsUI(content, settings, dialog);
} catch (error) {
content.innerHTML = `
<div style="color: #f44336; padding: 20px; text-align: center;">
<div style="font-size: 16px; margin-bottom: 10px;">Error loading settings</div>
<div style="font-size: 14px; opacity: 0.8;">${error.message}</div>
</div>
`;
handleError(error, 'Failed to load settings');
}
}
/**
* Build the settings UI
* @param {HTMLElement} container - Container element
* @param {Object} settings - Current settings data
* @param {Object} dialog - Dialog instance
*/
function buildSettingsUI(container, settings, dialog) {
// Create main settings container
const settingsContainer = document.createElement('div');
settingsContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 20px;
`;
// LLM Integration Section
const llmSection = createLLMSection(settings);
settingsContainer.appendChild(llmSection);
// Tab Visibility Section
const tabVisibilitySection = createTabVisibilitySection(settings);
settingsContainer.appendChild(tabVisibilitySection);
container.appendChild(settingsContainer);
// Add footer buttons
dialog.addFooterButton('Save', async () => {
await saveSettings(dialog, settingsContainer, settings);
}, 'primary');
dialog.addFooterButton('Reset to Defaults', async () => {
if (confirm('Are you sure you want to reset all settings to their default values?')) {
await resetSettings(dialog);
}
});
dialog.addFooterButton('Cancel', () => {
dialog.close();
});
}
/**
* Create LLM Integration settings section
* @param {Object} settings - Current settings
* @returns {HTMLElement} Section element
*/
function createLLMSection(settings) {
const section = document.createElement('div');
section.style.cssText = `
padding: 15px;
background: #1e1e1e;
border-radius: 6px;
border: 1px solid #444;
`;
const title = document.createElement('h3');
title.textContent = 'LLM Integration';
title.style.cssText = `
margin: 0 0 15px 0;
color: #4CAF50;
font-size: 16px;
font-weight: 600;
`;
section.appendChild(title);
// Default provider selection
const defaultProviderGroup = createDefaultProviderSelector(settings);
section.appendChild(defaultProviderGroup);
// Separator after default provider
const topSeparator = document.createElement('div');
topSeparator.style.cssText = `
height: 1px;
background: #444;
margin: 15px 0;
`;
section.appendChild(topSeparator);
// Ollama settings
const ollamaGroup = createProviderGroup('Ollama', settings, 'ollama');
section.appendChild(ollamaGroup);
// Separator
const separator = document.createElement('div');
separator.style.cssText = `
height: 1px;
background: #444;
margin: 15px 0;
`;
section.appendChild(separator);
// LM Studio settings
const lmstudioGroup = createProviderGroup('LM Studio', settings, 'lmstudio');
section.appendChild(lmstudioGroup);
return section;
}
/**
* Create default LLM provider selector
* @param {Object} settings - Current settings
* @returns {HTMLElement} Default provider selector group
*/
function createDefaultProviderSelector(settings) {
const group = document.createElement('div');
group.style.cssText = 'margin-bottom: 15px;';
const label = document.createElement('label');
label.textContent = 'Default LLM Provider:';
label.style.cssText = `
display: block;
color: #ccc;
font-size: 14px;
margin-bottom: 8px;
font-weight: 500;
`;
group.appendChild(label);
const description = document.createElement('div');
description.textContent = 'Select which provider to use by default when opening the LLM tab';
description.style.cssText = `
color: #888;
font-size: 12px;
margin-bottom: 10px;
font-style: italic;
`;
group.appendChild(description);
// Get current setting
const defaultProviderSetting = settings['default_llm_provider'];
const currentValue = defaultProviderSetting ? defaultProviderSetting.current_value : 'ollama';
// Create radio button group
const radioItems = [
{ value: 'ollama', label: 'Ollama' },
{ value: 'lmstudio', label: 'LM Studio' }
];
const { container: radioContainer } = createRadioGroup('default_llm_provider', radioItems, {
value: currentValue,
layout: 'horizontal',
styles: {
display: 'flex',
gap: '20px',
marginLeft: '10px'
},
itemStyles: {
color: '#ccc',
fontSize: '14px',
cursor: 'pointer',
userSelect: 'none'
}
});
// Add data attributes to radios for settings management
const radios = radioContainer.querySelectorAll('input[type="radio"]');
radios.forEach(radio => {
radio.dataset.settingKey = 'default_llm_provider';
});
group.appendChild(radioContainer);
return group;
}
/**
* Create Tab Visibility settings section
* @param {Object} settings - Current settings
* @returns {HTMLElement} Section element
*/
function createTabVisibilitySection(settings) {
const section = document.createElement('div');
section.style.cssText = `
padding: 15px;
background: #1e1e1e;
border-radius: 6px;
border: 1px solid #444;
`;
const title = document.createElement('h3');
title.textContent = 'Sidebar Tab Visibility';
title.style.cssText = `
margin: 0 0 15px 0;
color: #4CAF50;
font-size: 16px;
font-weight: 600;
`;
section.appendChild(title);
const description = document.createElement('p');
description.textContent = 'Control which tabs are visible in the sidebar. Changes take effect immediately.';
description.style.cssText = `
margin: 0 0 15px 0;
color: #888;
font-size: 13px;
font-style: italic;
`;
section.appendChild(description);
// Create a grid for tab checkboxes
const tabsGrid = document.createElement('div');
tabsGrid.style.cssText = `
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
`;
// Define tabs with their settings keys and display names
const tabs = [
{ key: 'show_models_tab', label: 'Models' },
{ key: 'show_files_tab', label: 'Files' },
{ key: 'show_search_tab', label: 'Search (Civitai)' },
{ key: 'show_gallery_tab', label: 'Gallery' },
{ key: 'show_prompts_tab', label: 'Prompts' },
{ key: 'show_llm_tab', label: 'LLM' }
];
// Create checkbox for each tab
tabs.forEach(tab => {
const setting = settings[tab.key];
const checkbox = createCheckbox(
tab.label,
setting ? setting.current_value : true,
tab.key
);
checkbox.container.style.marginBottom = '5px';
tabsGrid.appendChild(checkbox.container);
});
section.appendChild(tabsGrid);
return section;
}
/**
* Create settings group for a provider (Ollama or LM Studio)
* @param {string} providerName - Display name of the provider
* @param {Object} settings - Current settings
* @param {string} providerKey - Key prefix (ollama or lmstudio)
* @returns {HTMLElement} Provider group element
*/
function createProviderGroup(providerName, settings, providerKey) {
const group = document.createElement('div');
group.style.cssText = 'margin-bottom: 15px;';
// Enable checkbox
const enableKey = `enable_${providerKey}`;
const enableSetting = settings[enableKey];
const enableCheckbox = createCheckbox(
`Enable ${providerName}`,
enableSetting ? enableSetting.current_value : true,
enableKey
);
group.appendChild(enableCheckbox.container);
// Custom URL checkbox
const useCustomUrlKey = `${providerKey}_use_custom_url`;
const useCustomUrlSetting = settings[useCustomUrlKey];
const customUrlCheckbox = createCheckbox(
`Use custom URL for ${providerName}`,
useCustomUrlSetting ? useCustomUrlSetting.current_value : false,
useCustomUrlKey
);
customUrlCheckbox.container.style.marginLeft = '20px';
group.appendChild(customUrlCheckbox.container);
// Custom URL input
const customUrlKey = `${providerKey}_custom_url`;
const customUrlSetting = settings[customUrlKey];
const customUrlInput = createTextInput(
`${providerName} URL`,
customUrlSetting ? customUrlSetting.current_value : '',
customUrlKey,
`e.g., http://localhost:${providerKey === 'ollama' ? '11434' : '1234'}`
);
customUrlInput.container.style.marginLeft = '40px';
customUrlInput.container.style.marginTop = '10px';
// Show/hide URL input based on checkbox
const updateUrlInputVisibility = () => {
customUrlInput.container.style.display =
customUrlCheckbox.checkbox.checked ? 'block' : 'none';
};
updateUrlInputVisibility();
customUrlCheckbox.checkbox.addEventListener('change', updateUrlInputVisibility);
group.appendChild(customUrlInput.container);
return group;
}
/**
* Create a checkbox input
* @param {string} label - Label text
* @param {boolean} checked - Initial checked state
* @param {string} settingKey - Setting key for data attribute
* @returns {Object} Object with container and checkbox elements
*/
function createCheckboxInput(label, checked, settingKey) {
const { label: labelElement, checkbox } = createCheckbox(label, {
id: `setting-${settingKey}`,
checked: checked,
className: '',
labelClass: '',
styles: {
cursor: 'pointer'
},
labelStyles: {
color: '#ccc',
fontSize: '14px',
cursor: 'pointer',
userSelect: 'none'
}
});
checkbox.dataset.settingKey = settingKey;
const container = document.createElement('div');
container.style.cssText = `
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
`;
container.appendChild(labelElement);
return { container, checkbox };
}
/**
* Create a text input field
* @param {string} label - Label text
* @param {string} value - Initial value
* @param {string} settingKey - Setting key
* @param {string} placeholder - Placeholder text
* @returns {Object} Object with container and input elements
*/
function createTextInput(label, value, settingKey, placeholder = '') {
const input = createInput({
type: 'text',
id: `setting-${settingKey}`,
value: value || '',
placeholder: placeholder,
styles: {
width: '100%',
padding: '8px',
background: '#2a2a2a',
border: '1px solid #555',
borderRadius: '4px',
color: '#ccc',
fontSize: '13px',
boxSizing: 'border-box'
}
});
input.dataset.settingKey = settingKey;
const container = document.createElement('div');
container.style.cssText = 'margin-bottom: 10px;';
const labelElement = document.createElement('label');
labelElement.textContent = label;
labelElement.style.cssText = `
display: block;
color: #ccc;
font-size: 13px;
margin-bottom: 5px;
`;
container.appendChild(labelElement);
container.appendChild(input);
return { container, input };
}
/**
* Save settings
* @param {Object} dialog - Dialog instance
* @param {HTMLElement} container - Settings container
* @param {Object} originalSettings - Original settings for reference
*/
async function saveSettings(dialog, container, originalSettings) {
try {
// Collect all setting inputs
const inputs = container.querySelectorAll('[data-setting-key]');
const updates = {};
inputs.forEach(input => {
const key = input.dataset.settingKey;
let value;
if (input.type === 'checkbox') {
value = input.checked;
} else if (input.type === 'radio') {
// Only get value from checked radio button
if (input.checked) {
value = input.value;
} else {
// Skip unchecked radio buttons
return;
}
} else {
value = input.value;
}
updates[key] = value;
});
console.log('Saving settings:', updates);
// Send update request
const response = await api.fetchApi('/sage_utils/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
if (!response.ok) {
throw new Error(`Failed to save settings: ${response.statusText}`);
}
const result = await response.json();
console.log('Save result:', result);
if (!result.success) {
throw new Error(result.error || 'Failed to save settings');
}
// Update ComfyUI settings to reflect the changes
for (const [key, value] of Object.entries(updates)) {
const settingId = SETTING_KEY_TO_ID_MAP[key];
if (settingId && app.extensionManager && app.extensionManager.setting) {
try {
await app.extensionManager.setting.set(settingId, value);
console.log(`Updated ComfyUI setting ${settingId} to:`, value);
} catch (error) {
console.warn(`Could not update ComfyUI setting ${settingId}:`, error);
}
}
}
// Check if any tab visibility settings changed
const tabVisibilityKeys = [
'show_models_tab',
'show_files_tab',
'show_search_tab',
'show_gallery_tab',
'show_prompts_tab',
'show_llm_tab'
];
const tabVisibilityChanged = Object.keys(updates).some(key => tabVisibilityKeys.includes(key));
// If tab visibility changed, reload the sidebar
if (tabVisibilityChanged) {
try {
const { reloadCacheSidebar } = await import('../sidebar/cacheSidebar.js');
await reloadCacheSidebar();
console.log('Sidebar reloaded due to tab visibility changes');
} catch (error) {
console.error('Failed to reload sidebar:', error);
if (notifications && notifications.show) {
notifications.show('Settings saved but sidebar reload failed. Please refresh the page.', 'warning');
}
}
}
// Show success notification
if (notifications && notifications.show) {
notifications.show('Settings saved successfully', 'success');
}
// Close dialog
dialog.close();
} catch (error) {
console.error('Error saving settings:', error);
handleError(error, 'Failed to save settings');
if (notifications && notifications.show) {
notifications.show('Failed to save settings: ' + error.message, 'error');
}
}
}
/**
* Reset all settings to defaults
* @param {Object} dialog - Dialog instance
*/
async function resetSettings(dialog) {
try {
const response = await api.fetchApi('/sage_utils/settings/reset', {
method: 'POST'
});
if (!response.ok) {
throw new Error(`Failed to reset settings: ${response.statusText}`);
}
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Failed to reset settings');
}
// Reload the sidebar since tab visibility may have changed
try {
const { reloadCacheSidebar } = await import('../sidebar/cacheSidebar.js');
await reloadCacheSidebar();
console.log('Sidebar reloaded after settings reset');
} catch (error) {
console.error('Failed to reload sidebar:', error);
}
// Show success notification
if (notifications && notifications.show) {
notifications.show('Settings reset to defaults', 'success');
}
// Close and reopen dialog to show new values
dialog.close();
setTimeout(() => showSettingsDialog(), 100);
} catch (error) {
handleError(error, 'Failed to reset settings');
if (notifications && notifications.show) {
notifications.show('Failed to reset settings: ' + error.message, 'error');
}
}
}