bolt.diy/app/lib/stores/settings.ts
2025-02-03 01:04:23 +01:00

322 lines
10 KiB
TypeScript

import { atom, map } from 'nanostores';
import { workbenchStore } from './workbench';
import { PROVIDER_LIST } from '~/utils/constants';
import type { IProviderConfig } from '~/types/model';
import type {
TabVisibilityConfig,
TabWindowConfig,
UserTabConfig,
DevTabConfig,
} from '~/components/@settings/core/types';
import { DEFAULT_TAB_CONFIG } from '~/components/@settings/core/constants';
import Cookies from 'js-cookie';
import { toggleTheme } from './theme';
import { chatStore } from './chat';
export interface Shortcut {
key: string;
ctrlKey?: boolean;
shiftKey?: boolean;
altKey?: boolean;
metaKey?: boolean;
ctrlOrMetaKey?: boolean;
action: () => void;
description?: string; // Description of what the shortcut does
isPreventDefault?: boolean; // Whether to prevent default browser behavior
}
export interface Shortcuts {
toggleTerminal: Shortcut;
toggleTheme: Shortcut;
toggleChat: Shortcut;
toggleSettings: Shortcut;
}
export const URL_CONFIGURABLE_PROVIDERS = ['Ollama', 'LMStudio', 'OpenAILike'];
export const LOCAL_PROVIDERS = ['OpenAILike', 'LMStudio', 'Ollama'];
export type ProviderSetting = Record<string, IProviderConfig>;
// Define safer shortcuts that don't conflict with browser defaults
export const shortcutsStore = map<Shortcuts>({
toggleTerminal: {
key: '`',
ctrlOrMetaKey: true,
action: () => workbenchStore.toggleTerminal(),
description: 'Toggle terminal',
isPreventDefault: true,
},
toggleTheme: {
key: 'd',
metaKey: true,
altKey: true,
shiftKey: true,
action: () => toggleTheme(),
description: 'Toggle theme',
isPreventDefault: true,
},
toggleChat: {
key: 'j', // Changed from 'k' to 'j' to avoid conflicts
ctrlOrMetaKey: true,
altKey: true, // Added alt key to make it more unique
action: () => chatStore.setKey('showChat', !chatStore.get().showChat),
description: 'Toggle chat',
isPreventDefault: true,
},
toggleSettings: {
key: 's',
ctrlOrMetaKey: true,
altKey: true,
action: () => {
document.dispatchEvent(new CustomEvent('toggle-settings'));
},
description: 'Toggle settings',
isPreventDefault: true,
},
});
// Create a single key for provider settings
const PROVIDER_SETTINGS_KEY = 'provider_settings';
// Add this helper function at the top of the file
const isBrowser = typeof window !== 'undefined';
// Initialize provider settings from both localStorage and defaults
const getInitialProviderSettings = (): ProviderSetting => {
const initialSettings: ProviderSetting = {};
// Start with default settings
PROVIDER_LIST.forEach((provider) => {
initialSettings[provider.name] = {
...provider,
settings: {
// Local providers should be disabled by default
enabled: !LOCAL_PROVIDERS.includes(provider.name),
},
};
});
// Only try to load from localStorage in the browser
if (isBrowser) {
const savedSettings = localStorage.getItem(PROVIDER_SETTINGS_KEY);
if (savedSettings) {
try {
const parsed = JSON.parse(savedSettings);
Object.entries(parsed).forEach(([key, value]) => {
if (initialSettings[key]) {
initialSettings[key].settings = (value as IProviderConfig).settings;
}
});
} catch (error) {
console.error('Error parsing saved provider settings:', error);
}
}
}
return initialSettings;
};
export const providersStore = map<ProviderSetting>(getInitialProviderSettings());
// Create a function to update provider settings that handles both store and persistence
export const updateProviderSettings = (provider: string, settings: ProviderSetting) => {
const currentSettings = providersStore.get();
// Create new provider config with updated settings
const updatedProvider = {
...currentSettings[provider],
settings: {
...currentSettings[provider].settings,
...settings,
},
};
// Update the store with new settings
providersStore.setKey(provider, updatedProvider);
// Save to localStorage
const allSettings = providersStore.get();
localStorage.setItem(PROVIDER_SETTINGS_KEY, JSON.stringify(allSettings));
};
export const isDebugMode = atom(false);
// Define keys for localStorage
const SETTINGS_KEYS = {
LATEST_BRANCH: 'isLatestBranch',
AUTO_SELECT_TEMPLATE: 'autoSelectTemplate',
CONTEXT_OPTIMIZATION: 'contextOptimizationEnabled',
EVENT_LOGS: 'isEventLogsEnabled',
LOCAL_MODELS: 'isLocalModelsEnabled',
PROMPT_ID: 'promptId',
DEVELOPER_MODE: 'isDeveloperMode',
} as const;
// Initialize settings from localStorage or defaults
const getInitialSettings = () => {
const getStoredBoolean = (key: string, defaultValue: boolean): boolean => {
if (!isBrowser) {
return defaultValue;
}
const stored = localStorage.getItem(key);
if (stored === null) {
return defaultValue;
}
try {
return JSON.parse(stored);
} catch {
return defaultValue;
}
};
return {
latestBranch: getStoredBoolean(SETTINGS_KEYS.LATEST_BRANCH, false),
autoSelectTemplate: getStoredBoolean(SETTINGS_KEYS.AUTO_SELECT_TEMPLATE, false),
contextOptimization: getStoredBoolean(SETTINGS_KEYS.CONTEXT_OPTIMIZATION, false),
eventLogs: getStoredBoolean(SETTINGS_KEYS.EVENT_LOGS, true),
localModels: getStoredBoolean(SETTINGS_KEYS.LOCAL_MODELS, true),
promptId: isBrowser ? localStorage.getItem(SETTINGS_KEYS.PROMPT_ID) || 'default' : 'default',
developerMode: getStoredBoolean(SETTINGS_KEYS.DEVELOPER_MODE, false),
};
};
// Initialize stores with persisted values
const initialSettings = getInitialSettings();
export const latestBranchStore = atom<boolean>(initialSettings.latestBranch);
export const autoSelectStarterTemplate = atom<boolean>(initialSettings.autoSelectTemplate);
export const enableContextOptimizationStore = atom<boolean>(initialSettings.contextOptimization);
export const isEventLogsEnabled = atom<boolean>(initialSettings.eventLogs);
export const isLocalModelsEnabled = atom<boolean>(initialSettings.localModels);
export const promptStore = atom<string>(initialSettings.promptId);
// Helper functions to update settings with persistence
export const updateLatestBranch = (enabled: boolean) => {
latestBranchStore.set(enabled);
localStorage.setItem(SETTINGS_KEYS.LATEST_BRANCH, JSON.stringify(enabled));
};
export const updateAutoSelectTemplate = (enabled: boolean) => {
autoSelectStarterTemplate.set(enabled);
localStorage.setItem(SETTINGS_KEYS.AUTO_SELECT_TEMPLATE, JSON.stringify(enabled));
};
export const updateContextOptimization = (enabled: boolean) => {
enableContextOptimizationStore.set(enabled);
localStorage.setItem(SETTINGS_KEYS.CONTEXT_OPTIMIZATION, JSON.stringify(enabled));
};
export const updateEventLogs = (enabled: boolean) => {
isEventLogsEnabled.set(enabled);
localStorage.setItem(SETTINGS_KEYS.EVENT_LOGS, JSON.stringify(enabled));
};
export const updateLocalModels = (enabled: boolean) => {
isLocalModelsEnabled.set(enabled);
localStorage.setItem(SETTINGS_KEYS.LOCAL_MODELS, JSON.stringify(enabled));
};
export const updatePromptId = (id: string) => {
promptStore.set(id);
localStorage.setItem(SETTINGS_KEYS.PROMPT_ID, id);
};
// Initialize tab configuration from localStorage or defaults
const getInitialTabConfiguration = (): TabWindowConfig => {
const defaultConfig: TabWindowConfig = {
userTabs: DEFAULT_TAB_CONFIG.filter((tab): tab is UserTabConfig => tab.window === 'user'),
developerTabs: DEFAULT_TAB_CONFIG.filter((tab): tab is DevTabConfig => tab.window === 'developer'),
};
if (!isBrowser) {
return defaultConfig;
}
try {
const saved = localStorage.getItem('bolt_tab_configuration');
if (!saved) {
return defaultConfig;
}
const parsed = JSON.parse(saved);
if (!parsed?.userTabs || !parsed?.developerTabs) {
return defaultConfig;
}
// Ensure proper typing of loaded configuration
return {
userTabs: parsed.userTabs.filter((tab: TabVisibilityConfig): tab is UserTabConfig => tab.window === 'user'),
developerTabs: parsed.developerTabs.filter(
(tab: TabVisibilityConfig): tab is DevTabConfig => tab.window === 'developer',
),
};
} catch (error) {
console.warn('Failed to parse tab configuration:', error);
return defaultConfig;
}
};
console.log('Initial tab configuration:', getInitialTabConfiguration());
export const tabConfigurationStore = map<TabWindowConfig>(getInitialTabConfiguration());
// Helper function to update tab configuration
export const updateTabConfiguration = (config: TabVisibilityConfig) => {
const currentConfig = tabConfigurationStore.get();
console.log('Current tab configuration before update:', currentConfig);
const isUserTab = config.window === 'user';
const targetArray = isUserTab ? 'userTabs' : 'developerTabs';
// Only update the tab in its respective window
const updatedTabs = currentConfig[targetArray].map((tab) => (tab.id === config.id ? { ...config } : tab));
// If tab doesn't exist in this window yet, add it
if (!updatedTabs.find((tab) => tab.id === config.id)) {
updatedTabs.push(config);
}
// Create new config, only updating the target window's tabs
const newConfig: TabWindowConfig = {
...currentConfig,
[targetArray]: updatedTabs,
};
console.log('New tab configuration after update:', newConfig);
tabConfigurationStore.set(newConfig);
Cookies.set('tabConfiguration', JSON.stringify(newConfig), {
expires: 365, // Set cookie to expire in 1 year
path: '/',
sameSite: 'strict',
});
};
// Helper function to reset tab configuration
export const resetTabConfiguration = () => {
const defaultConfig: TabWindowConfig = {
userTabs: DEFAULT_TAB_CONFIG.filter((tab): tab is UserTabConfig => tab.window === 'user'),
developerTabs: DEFAULT_TAB_CONFIG.filter((tab): tab is DevTabConfig => tab.window === 'developer'),
};
tabConfigurationStore.set(defaultConfig);
localStorage.setItem('bolt_tab_configuration', JSON.stringify(defaultConfig));
};
// Developer mode store with persistence
export const developerModeStore = atom<boolean>(initialSettings.developerMode);
export const setDeveloperMode = (value: boolean) => {
developerModeStore.set(value);
if (isBrowser) {
localStorage.setItem(SETTINGS_KEYS.DEVELOPER_MODE, JSON.stringify(value));
}
};