869 lines
26 KiB
JavaScript
869 lines
26 KiB
JavaScript
/**
|
|
* Settings Dialog
|
|
* Provides a UI for configuring SageUtils settings
|
|
* Currently focuses on LLM provider configuration (REST providers, OpenAI, and Native)
|
|
*/
|
|
|
|
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';
|
|
import { javascriptTimer, uiTimer, printTimingReport } from '../shared/performanceTimer.js';
|
|
import {
|
|
TAB_VISIBILITY_CONFIG,
|
|
hasTabVisibilityUpdates,
|
|
normalizeTabVisibilitySettings
|
|
} from '../shared/sidebarVisibility.js';
|
|
|
|
/**
|
|
* Mapping between backend setting keys and ComfyUI setting IDs
|
|
*/
|
|
const SETTING_KEY_TO_ID_MAP = {
|
|
'enable_lmstudio_rest': 'SageUtils.LLM Providers.enable_lmstudio_rest',
|
|
'enable_ollama_rest': 'SageUtils.LLM Providers.enable_ollama_rest',
|
|
'enable_openai': 'SageUtils.LLM Providers.enable_openai',
|
|
'openai_api_key': 'SageUtils.OpenAI.openai_api_key',
|
|
'openai_use_custom_url': 'SageUtils.OpenAI.openai_use_custom_url',
|
|
'openai_base_url': 'SageUtils.OpenAI.openai_base_url',
|
|
'ollama_custom_url': 'SageUtils.Local Custom Ollama URL.ollama_custom_url',
|
|
'ollama_use_custom_url': 'SageUtils.Local Custom Ollama URL.ollama_use_custom_url',
|
|
'ollama_api_key': 'SageUtils.Ollama.ollama_api_key',
|
|
'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',
|
|
'lmstudio_api_token': 'SageUtils.LM Studio.lmstudio_api_token'
|
|
};
|
|
|
|
/**
|
|
* 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);
|
|
|
|
// Performance & Telemetry Section (frontend-only)
|
|
const perfSection = createPerformanceSection();
|
|
settingsContainer.appendChild(perfSection);
|
|
|
|
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_rest', 'ollama');
|
|
section.appendChild(ollamaGroup);
|
|
|
|
// Separator
|
|
const separator = document.createElement('div');
|
|
separator.style.cssText = `
|
|
height: 1px;
|
|
background: #444;
|
|
margin: 15px 0;
|
|
`;
|
|
section.appendChild(separator);
|
|
|
|
// OpenAI settings
|
|
const openaiGroup = createOpenAIProviderGroup(settings);
|
|
section.appendChild(openaiGroup);
|
|
|
|
// Separator
|
|
const openaiSeparator = document.createElement('div');
|
|
openaiSeparator.style.cssText = `
|
|
height: 1px;
|
|
background: #444;
|
|
margin: 15px 0;
|
|
`;
|
|
section.appendChild(openaiSeparator);
|
|
|
|
// LM Studio settings
|
|
const lmstudioGroup = createProviderGroup('LM Studio', settings, 'lmstudio_rest', '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 in the LLM tab and provider-switching LLM v3 nodes';
|
|
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 : 'lmstudio_rest';
|
|
|
|
// Create radio button group
|
|
const radioItems = [
|
|
{ value: 'lmstudio_rest', label: 'LM Studio' },
|
|
{ value: 'ollama_rest', label: 'Ollama' },
|
|
{ value: 'openai', label: 'OpenAI' },
|
|
{ value: 'native', label: 'Native (CLIP)' }
|
|
];
|
|
|
|
const { container: radioContainer } = createRadioGroup('default_llm_provider', radioItems, {
|
|
selectedValue: currentValue,
|
|
layout: 'horizontal',
|
|
style: {
|
|
display: 'flex',
|
|
gap: '20px',
|
|
marginLeft: '10px'
|
|
}
|
|
});
|
|
|
|
// 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;
|
|
`;
|
|
|
|
const visibilitySettings = normalizeTabVisibilitySettings(settings);
|
|
|
|
// Create checkbox for each tab
|
|
TAB_VISIBILITY_CONFIG.forEach((tab) => {
|
|
const { container, checkbox } = createCheckbox(tab.label, {
|
|
checked: visibilitySettings[tab.settingKey],
|
|
id: `setting-${tab.settingKey}`
|
|
});
|
|
|
|
// Add setting key to checkbox for saving later
|
|
checkbox.dataset.settingKey = tab.settingKey;
|
|
|
|
container.style.marginBottom = '5px';
|
|
tabsGrid.appendChild(container);
|
|
});
|
|
|
|
section.appendChild(tabsGrid);
|
|
|
|
return section;
|
|
}
|
|
|
|
/**
|
|
* Create settings group for a provider
|
|
* @param {string} providerName - Display name of the provider
|
|
* @param {Object} settings - Current settings
|
|
* @param {string} providerKey - Enable flag key suffix (e.g., ollama_rest)
|
|
* @param {string} urlKeyPrefix - URL key prefix (ollama or lmstudio)
|
|
* @returns {HTMLElement} Provider group element
|
|
*/
|
|
function createProviderGroup(providerName, settings, providerKey, urlKeyPrefix) {
|
|
const group = document.createElement('div');
|
|
group.style.cssText = 'margin-bottom: 15px;';
|
|
|
|
// Enable checkbox
|
|
const enableKey = `enable_${providerKey}`;
|
|
const enableSetting = settings[enableKey];
|
|
const { container: enableContainer, checkbox: enableCheckbox } = createCheckbox(
|
|
`Enable ${providerName}`,
|
|
{
|
|
checked: enableSetting ? enableSetting.current_value : true,
|
|
id: `setting-${enableKey}`
|
|
}
|
|
);
|
|
enableCheckbox.dataset.settingKey = enableKey;
|
|
group.appendChild(enableContainer);
|
|
|
|
// Custom URL checkbox
|
|
const useCustomUrlKey = `${urlKeyPrefix}_use_custom_url`;
|
|
const useCustomUrlSetting = settings[useCustomUrlKey];
|
|
const { container: customUrlCheckboxContainer, checkbox: customUrlCheckbox } = createCheckbox(
|
|
`Use custom URL for ${providerName}`,
|
|
{
|
|
checked: useCustomUrlSetting ? useCustomUrlSetting.current_value : false,
|
|
id: `setting-${useCustomUrlKey}`
|
|
}
|
|
);
|
|
customUrlCheckbox.dataset.settingKey = useCustomUrlKey;
|
|
customUrlCheckboxContainer.style.marginLeft = '20px';
|
|
group.appendChild(customUrlCheckboxContainer);
|
|
|
|
// Custom URL input
|
|
const customUrlKey = `${urlKeyPrefix}_custom_url`;
|
|
const customUrlSetting = settings[customUrlKey];
|
|
const { container: customUrlInputContainer, input: customUrlInput } = createTextInput(
|
|
`${providerName} URL`,
|
|
customUrlSetting ? customUrlSetting.current_value : '',
|
|
customUrlKey,
|
|
`e.g., http://localhost:${urlKeyPrefix === 'ollama' ? '11434' : '1234'}`
|
|
);
|
|
customUrlInputContainer.style.marginLeft = '40px';
|
|
customUrlInputContainer.style.marginTop = '10px';
|
|
|
|
// Show/hide URL input based on checkbox
|
|
const updateUrlInputVisibility = () => {
|
|
customUrlInputContainer.style.display =
|
|
customUrlCheckbox.checked ? 'block' : 'none';
|
|
};
|
|
updateUrlInputVisibility();
|
|
customUrlCheckbox.addEventListener('change', updateUrlInputVisibility);
|
|
|
|
group.appendChild(customUrlInputContainer);
|
|
|
|
if (urlKeyPrefix === 'lmstudio' || urlKeyPrefix === 'ollama') {
|
|
const tokenKey = urlKeyPrefix === 'ollama' ? 'ollama_api_key' : 'lmstudio_api_token';
|
|
const tokenLabel = urlKeyPrefix === 'ollama' ? 'Ollama API Key' : 'LM Studio API Token';
|
|
const tokenPlaceholder = urlKeyPrefix === 'ollama' ? 'Optional bearer key' : 'Optional bearer token';
|
|
const tokenSetting = settings[tokenKey];
|
|
const { container: tokenInputContainer, input: tokenInput } = createTextInput(
|
|
tokenLabel,
|
|
tokenSetting ? tokenSetting.current_value : '',
|
|
tokenKey,
|
|
tokenPlaceholder
|
|
);
|
|
tokenInput.type = 'password';
|
|
tokenInput.autocomplete = 'off';
|
|
tokenInputContainer.style.marginLeft = '20px';
|
|
tokenInputContainer.style.marginTop = '10px';
|
|
group.appendChild(tokenInputContainer);
|
|
}
|
|
|
|
return group;
|
|
}
|
|
|
|
/**
|
|
* Create OpenAI settings group with API key and custom URL fields
|
|
* @param {Object} settings - Current settings
|
|
* @returns {HTMLElement} OpenAI group element
|
|
*/
|
|
function createOpenAIProviderGroup(settings) {
|
|
const group = document.createElement('div');
|
|
group.style.cssText = 'margin-bottom: 15px;';
|
|
|
|
const enableKey = 'enable_openai';
|
|
const enableSetting = settings[enableKey];
|
|
const { container: enableContainer, checkbox: enableCheckbox } = createCheckbox(
|
|
'Enable OpenAI',
|
|
{
|
|
checked: enableSetting ? enableSetting.current_value : false,
|
|
id: `setting-${enableKey}`
|
|
}
|
|
);
|
|
enableCheckbox.dataset.settingKey = enableKey;
|
|
group.appendChild(enableContainer);
|
|
|
|
const useCustomUrlKey = 'openai_use_custom_url';
|
|
const useCustomUrlSetting = settings[useCustomUrlKey];
|
|
const { container: customUrlCheckboxContainer, checkbox: customUrlCheckbox } = createCheckbox(
|
|
'Use custom URL for OpenAI',
|
|
{
|
|
checked: useCustomUrlSetting ? useCustomUrlSetting.current_value : false,
|
|
id: `setting-${useCustomUrlKey}`
|
|
}
|
|
);
|
|
customUrlCheckbox.dataset.settingKey = useCustomUrlKey;
|
|
customUrlCheckboxContainer.style.marginLeft = '20px';
|
|
customUrlCheckboxContainer.style.marginTop = '10px';
|
|
group.appendChild(customUrlCheckboxContainer);
|
|
|
|
const baseUrlKey = 'openai_base_url';
|
|
const baseUrlSetting = settings[baseUrlKey];
|
|
const { container: baseUrlInputContainer } = createTextInput(
|
|
'OpenAI Base URL',
|
|
baseUrlSetting ? baseUrlSetting.current_value : '',
|
|
baseUrlKey,
|
|
'e.g., https://api.openai.com'
|
|
);
|
|
baseUrlInputContainer.style.marginLeft = '40px';
|
|
baseUrlInputContainer.style.marginTop = '10px';
|
|
|
|
const updateUrlInputVisibility = () => {
|
|
baseUrlInputContainer.style.display = customUrlCheckbox.checked ? 'block' : 'none';
|
|
};
|
|
updateUrlInputVisibility();
|
|
customUrlCheckbox.addEventListener('change', updateUrlInputVisibility);
|
|
|
|
group.appendChild(baseUrlInputContainer);
|
|
|
|
const apiKeyKey = 'openai_api_key';
|
|
const apiKeySetting = settings[apiKeyKey];
|
|
const { container: apiKeyInputContainer, input: apiKeyInput } = createTextInput(
|
|
'OpenAI API Key',
|
|
apiKeySetting ? apiKeySetting.current_value : '',
|
|
apiKeyKey,
|
|
'sk-...'
|
|
);
|
|
apiKeyInput.type = 'password';
|
|
apiKeyInput.autocomplete = 'off';
|
|
apiKeyInputContainer.style.marginLeft = '20px';
|
|
apiKeyInputContainer.style.marginTop = '10px';
|
|
group.appendChild(apiKeyInputContainer);
|
|
|
|
return group;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
style: {
|
|
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.htmlFor = `setting-${settingKey}`;
|
|
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 tabVisibilityChanged = hasTabVisibilityUpdates(updates);
|
|
|
|
// 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();
|
|
|
|
// Apply local-only performance settings (handled after dialog close for snappy UI)
|
|
try {
|
|
const localInputs = container.querySelectorAll('[data-local-setting]');
|
|
const localUpdates = {};
|
|
localInputs.forEach(input => {
|
|
const key = input.dataset.localSetting;
|
|
if (!key) return;
|
|
const value = input.type === 'checkbox' ? !!input.checked : input.value;
|
|
localUpdates[key] = value;
|
|
});
|
|
|
|
// Persist to localStorage and apply immediately
|
|
if (Object.keys(localUpdates).length > 0) {
|
|
if (typeof localUpdates.perf_monitoring === 'boolean') {
|
|
localStorage.setItem('sageutils_perf_monitoring', String(localUpdates.perf_monitoring));
|
|
if (localUpdates.perf_monitoring) {
|
|
javascriptTimer.enable?.();
|
|
uiTimer.enable?.();
|
|
} else {
|
|
javascriptTimer.disable?.();
|
|
uiTimer.disable?.();
|
|
}
|
|
}
|
|
if (typeof localUpdates.send_timing === 'boolean') {
|
|
localStorage.setItem('sageutils_send_timing', String(localUpdates.send_timing));
|
|
}
|
|
if (typeof localUpdates.print_timing === 'boolean') {
|
|
localStorage.setItem('sageutils_print_timing', String(localUpdates.print_timing));
|
|
if (localUpdates.print_timing) {
|
|
// Optional: print a short report immediately for confirmation
|
|
try { printTimingReport(javascriptTimer); } catch {}
|
|
}
|
|
}
|
|
console.log('[Settings] Updated local performance settings:', localUpdates);
|
|
}
|
|
} catch (e) {
|
|
console.warn('[Settings] Failed to apply local performance settings:', e);
|
|
}
|
|
|
|
} 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');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create Performance & Telemetry settings section (frontend-only)
|
|
* Uses localStorage flags and immediate runtime toggles; does not hit backend
|
|
*/
|
|
function createPerformanceSection() {
|
|
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 = 'Performance & Telemetry';
|
|
title.style.cssText = `
|
|
margin: 0 0 10px 0;
|
|
color: #4CAF50;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
`;
|
|
section.appendChild(title);
|
|
|
|
const note = document.createElement('div');
|
|
note.textContent = 'These options affect local performance monitoring and telemetry upload.';
|
|
note.style.cssText = `
|
|
margin: 0 0 12px 0;
|
|
color: #888;
|
|
font-size: 12px;
|
|
font-style: italic;
|
|
`;
|
|
section.appendChild(note);
|
|
|
|
const grid = document.createElement('div');
|
|
grid.style.cssText = `
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 10px 16px;
|
|
align-items: center;
|
|
`;
|
|
|
|
// Current values from localStorage
|
|
const perfMonitoring = localStorage.getItem('sageutils_perf_monitoring') === 'true';
|
|
const sendTiming = localStorage.getItem('sageutils_send_timing') === 'true';
|
|
const printTiming = localStorage.getItem('sageutils_print_timing') === 'true';
|
|
|
|
// Enable performance monitoring
|
|
{
|
|
const { container, checkbox } = createCheckbox('Enable performance monitoring', {
|
|
checked: perfMonitoring,
|
|
id: 'perf-monitoring-checkbox'
|
|
});
|
|
checkbox.dataset.localSetting = 'perf_monitoring';
|
|
grid.appendChild(container);
|
|
}
|
|
|
|
// Send timing to server
|
|
{
|
|
const { container, checkbox } = createCheckbox('Send timing telemetry to server', {
|
|
checked: sendTiming,
|
|
id: 'perf-sendtiming-checkbox'
|
|
});
|
|
checkbox.dataset.localSetting = 'send_timing';
|
|
grid.appendChild(container);
|
|
}
|
|
|
|
// Print timing to console
|
|
{
|
|
const { container, checkbox } = createCheckbox('Print timing report to console', {
|
|
checked: printTiming,
|
|
id: 'perf-printtiming-checkbox'
|
|
});
|
|
checkbox.dataset.localSetting = 'print_timing';
|
|
grid.appendChild(container);
|
|
}
|
|
|
|
section.appendChild(grid);
|
|
|
|
// Action buttons row
|
|
const actions = document.createElement('div');
|
|
actions.style.cssText = `
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 12px;
|
|
flex-wrap: wrap;
|
|
`;
|
|
|
|
const btn = (label, onClick, variant = 'default') => {
|
|
const b = document.createElement('button');
|
|
b.textContent = label;
|
|
b.style.cssText = `
|
|
padding: 6px 10px;
|
|
background: ${variant === 'primary' ? '#4CAF50' : '#2a2a2a'};
|
|
color: #fff;
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
`;
|
|
b.addEventListener('click', onClick);
|
|
return b;
|
|
};
|
|
|
|
// Send timing now
|
|
actions.appendChild(btn('Send timing now', async () => {
|
|
try {
|
|
await javascriptTimer.sendTimingDataToServer?.();
|
|
notifications?.show?.('Timing data sent', 'success');
|
|
} catch (e) {
|
|
notifications?.show?.('Failed to send timing data', 'error');
|
|
}
|
|
}, 'primary'));
|
|
|
|
// Reset collected timing
|
|
actions.appendChild(btn('Reset collected timing', () => {
|
|
try {
|
|
javascriptTimer.reset?.();
|
|
uiTimer.reset?.();
|
|
notifications?.show?.('Timing data reset', 'info');
|
|
} catch (e) {
|
|
notifications?.show?.('Failed to reset timing data', 'error');
|
|
}
|
|
}));
|
|
|
|
// Print report now
|
|
actions.appendChild(btn('Print timing report', () => {
|
|
try {
|
|
printTimingReport(javascriptTimer);
|
|
} catch (e) {
|
|
console.warn('Failed to print timing report', e);
|
|
}
|
|
}));
|
|
|
|
section.appendChild(actions);
|
|
|
|
return section;
|
|
}
|
|
|
|
/**
|
|
* 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');
|
|
}
|
|
}
|
|
}
|