Client settings (#7381)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -63,9 +63,7 @@ export default function AnnouncementModal() {
|
||||
});
|
||||
|
||||
// Get list of seen announcement IDs
|
||||
const seenAnnouncementIds = JSON.parse(
|
||||
localStorage.getItem('seenAnnouncementIds') || '[]'
|
||||
) as string[];
|
||||
const seenAnnouncementIds = await window.electron.getSetting('seenAnnouncementIds');
|
||||
|
||||
// Find ALL unseen announcements (in order)
|
||||
const unseenAnnouncementsList = applicableAnnouncements.filter(
|
||||
@@ -101,13 +99,11 @@ export default function AnnouncementModal() {
|
||||
loadAnnouncements();
|
||||
}, []);
|
||||
|
||||
const handleCloseAnnouncement = () => {
|
||||
const handleCloseAnnouncement = async () => {
|
||||
if (unseenAnnouncements.length === 0) return;
|
||||
|
||||
// Get existing seen announcement IDs
|
||||
const seenAnnouncementIds = JSON.parse(
|
||||
localStorage.getItem('seenAnnouncementIds') || '[]'
|
||||
) as string[];
|
||||
const seenAnnouncementIds = await window.electron.getSetting('seenAnnouncementIds');
|
||||
|
||||
// Add all unseen announcement IDs to the seen list
|
||||
const newSeenIds = [...seenAnnouncementIds];
|
||||
@@ -117,7 +113,7 @@ export default function AnnouncementModal() {
|
||||
}
|
||||
});
|
||||
|
||||
localStorage.setItem('seenAnnouncementIds', JSON.stringify(newSeenIds));
|
||||
await window.electron.setSetting('seenAnnouncementIds', newSeenIds);
|
||||
setShowAnnouncementModal(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -428,20 +428,20 @@ function ToolCallView({
|
||||
notifications,
|
||||
isStreamingMessage = false,
|
||||
}: ToolCallViewProps) {
|
||||
const [responseStyle, setResponseStyle] = useState(() => localStorage.getItem('response_style'));
|
||||
const [responseStyle, setResponseStyle] = useState<string>('concise');
|
||||
|
||||
useEffect(() => {
|
||||
const handleStorageChange = () => {
|
||||
setResponseStyle(localStorage.getItem('response_style'));
|
||||
// Load initial value from settings
|
||||
window.electron.getSetting('responseStyle').then(setResponseStyle);
|
||||
|
||||
const handleStyleChange = () => {
|
||||
window.electron.getSetting('responseStyle').then(setResponseStyle);
|
||||
};
|
||||
|
||||
window.addEventListener('storage', handleStorageChange);
|
||||
|
||||
window.addEventListener(AppEvents.RESPONSE_STYLE_CHANGED, handleStorageChange);
|
||||
window.addEventListener(AppEvents.RESPONSE_STYLE_CHANGED, handleStyleChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
window.removeEventListener(AppEvents.RESPONSE_STYLE_CHANGED, handleStorageChange);
|
||||
window.removeEventListener(AppEvents.RESPONSE_STYLE_CHANGED, handleStyleChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -26,14 +26,19 @@ export function CostTracker({ inputTokens = 0, outputTokens = 0, sessionCosts }:
|
||||
|
||||
// Check if pricing is enabled
|
||||
useEffect(() => {
|
||||
const checkPricingSetting = () => {
|
||||
const stored = localStorage.getItem('show_pricing');
|
||||
setShowPricing(stored !== 'false');
|
||||
const loadPricingSetting = async () => {
|
||||
const enabled = await window.electron.getSetting('showPricing');
|
||||
setShowPricing(enabled);
|
||||
};
|
||||
|
||||
checkPricingSetting();
|
||||
window.addEventListener('storage', checkPricingSetting);
|
||||
return () => window.removeEventListener('storage', checkPricingSetting);
|
||||
loadPricingSetting();
|
||||
|
||||
const handlePricingChange = () => {
|
||||
loadPricingSetting();
|
||||
};
|
||||
|
||||
window.addEventListener('showPricingChanged', handlePricingChange);
|
||||
return () => window.removeEventListener('showPricingChanged', handlePricingChange);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -151,29 +151,18 @@ const SessionHistoryView: React.FC<SessionHistoryViewProps> = ({
|
||||
const setView = useNavigation();
|
||||
|
||||
useEffect(() => {
|
||||
const savedSessionConfig = localStorage.getItem('session_sharing_config');
|
||||
if (savedSessionConfig) {
|
||||
try {
|
||||
const config = JSON.parse(savedSessionConfig);
|
||||
if (config.enabled && config.baseUrl) {
|
||||
setCanShare(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing session sharing config:', error);
|
||||
window.electron.getSetting('sessionSharing').then((config) => {
|
||||
if (config.enabled && config.baseUrl) {
|
||||
setCanShare(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleShare = async () => {
|
||||
setIsSharing(true);
|
||||
|
||||
try {
|
||||
const savedSessionConfig = localStorage.getItem('session_sharing_config');
|
||||
if (!savedSessionConfig) {
|
||||
throw new Error('Session sharing is not configured. Please configure it in settings.');
|
||||
}
|
||||
|
||||
const config = JSON.parse(savedSessionConfig);
|
||||
const config = await window.electron.getSetting('sessionSharing');
|
||||
if (!config.enabled || !config.baseUrl) {
|
||||
throw new Error('Session sharing is not enabled or base URL is not configured.');
|
||||
}
|
||||
|
||||
@@ -58,8 +58,7 @@ export default function AppSettingsSection({ scrollToSection }: AppSettingsSecti
|
||||
|
||||
// Load show pricing setting
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem('show_pricing');
|
||||
setShowPricing(stored !== 'false');
|
||||
window.electron.getSetting('showPricing').then(setShowPricing);
|
||||
}, []);
|
||||
|
||||
// Handle scrolling to update section
|
||||
@@ -140,12 +139,12 @@ export default function AppSettingsSection({ scrollToSection }: AppSettingsSecti
|
||||
}
|
||||
};
|
||||
|
||||
const handleShowPricingToggle = (checked: boolean) => {
|
||||
const handleShowPricingToggle = async (checked: boolean) => {
|
||||
setShowPricing(checked);
|
||||
localStorage.setItem('show_pricing', String(checked));
|
||||
await window.electron.setSetting('showPricing', checked);
|
||||
trackSettingToggled('cost_tracking', checked);
|
||||
// Trigger storage event for other components
|
||||
window.dispatchEvent(new CustomEvent('storage'));
|
||||
// Trigger event for other components
|
||||
window.dispatchEvent(new CustomEvent('showPricingChanged'));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,33 +3,18 @@ import { Switch } from '../../ui/switch';
|
||||
import { Input } from '../../ui/input';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import { ExternalGoosedConfig } from '../../../utils/settings';
|
||||
import { ExternalGoosedConfig, defaultSettings } from '../../../utils/settings';
|
||||
import { WEB_PROTOCOLS } from '../../../utils/urlSecurity';
|
||||
|
||||
const DEFAULT_CONFIG: ExternalGoosedConfig = {
|
||||
enabled: false,
|
||||
url: '',
|
||||
secret: '',
|
||||
};
|
||||
|
||||
function parseConfig(config: ExternalGoosedConfig | undefined): ExternalGoosedConfig {
|
||||
if (!config) return DEFAULT_CONFIG;
|
||||
return {
|
||||
enabled: config.enabled ?? DEFAULT_CONFIG.enabled,
|
||||
url: config.url ?? DEFAULT_CONFIG.url,
|
||||
secret: config.secret ?? DEFAULT_CONFIG.secret,
|
||||
};
|
||||
}
|
||||
|
||||
export default function ExternalBackendSection() {
|
||||
const [config, setConfig] = useState<ExternalGoosedConfig>(DEFAULT_CONFIG);
|
||||
const [config, setConfig] = useState<ExternalGoosedConfig>(defaultSettings.externalGoosed);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [urlError, setUrlError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
const settings = await window.electron.getSettings();
|
||||
setConfig(parseConfig(settings.externalGoosed));
|
||||
const externalGoosed = await window.electron.getSetting('externalGoosed');
|
||||
setConfig(externalGoosed);
|
||||
};
|
||||
loadSettings();
|
||||
}, []);
|
||||
@@ -56,11 +41,7 @@ export default function ExternalBackendSection() {
|
||||
const saveConfig = async (newConfig: ExternalGoosedConfig): Promise<void> => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const currentSettings = await window.electron.getSettings();
|
||||
await window.electron.saveSettings({
|
||||
...currentSettings,
|
||||
externalGoosed: newConfig,
|
||||
});
|
||||
await window.electron.setSetting('externalGoosed', newConfig);
|
||||
} catch (error) {
|
||||
console.error('Failed to save external backend settings:', error);
|
||||
} finally {
|
||||
|
||||
@@ -122,8 +122,8 @@ export default function KeyboardShortcutsSection() {
|
||||
const [showRestartNotice, setShowRestartNotice] = useState(false);
|
||||
|
||||
const loadShortcuts = useCallback(async () => {
|
||||
const settings = await window.electron.getSettings();
|
||||
setShortcuts(settings.keyboardShortcuts || defaultKeyboardShortcuts);
|
||||
const keyboardShortcuts = await window.electron.getSetting('keyboardShortcuts');
|
||||
setShortcuts(keyboardShortcuts || defaultKeyboardShortcuts);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -163,15 +163,11 @@ export default function KeyboardShortcutsSection() {
|
||||
newShortcuts[key] = null;
|
||||
}
|
||||
|
||||
const settings = await window.electron.getSettings();
|
||||
settings.keyboardShortcuts = newShortcuts;
|
||||
const success = await window.electron.saveSettings(settings);
|
||||
if (success) {
|
||||
setShortcuts(newShortcuts);
|
||||
trackSettingToggled(`shortcut_${key}`, enabled);
|
||||
if (needsRestart.has(key)) {
|
||||
setShowRestartNotice(true);
|
||||
}
|
||||
await window.electron.setSetting('keyboardShortcuts', newShortcuts);
|
||||
setShortcuts(newShortcuts);
|
||||
trackSettingToggled(`shortcut_${key}`, enabled);
|
||||
if (needsRestart.has(key)) {
|
||||
setShowRestartNotice(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -209,15 +205,11 @@ export default function KeyboardShortcutsSection() {
|
||||
|
||||
newShortcuts[editingKey] = shortcut || null;
|
||||
|
||||
const settings = await window.electron.getSettings();
|
||||
settings.keyboardShortcuts = newShortcuts;
|
||||
const success = await window.electron.saveSettings(settings);
|
||||
if (success) {
|
||||
setShortcuts(newShortcuts);
|
||||
setEditingKey(null);
|
||||
if (needsRestart.has(editingKey)) {
|
||||
setShowRestartNotice(true);
|
||||
}
|
||||
await window.electron.setSetting('keyboardShortcuts', newShortcuts);
|
||||
setShortcuts(newShortcuts);
|
||||
setEditingKey(null);
|
||||
if (needsRestart.has(editingKey)) {
|
||||
setShowRestartNotice(true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -236,14 +228,10 @@ export default function KeyboardShortcutsSection() {
|
||||
});
|
||||
|
||||
if (confirmed.response === 0) {
|
||||
const settings = await window.electron.getSettings();
|
||||
settings.keyboardShortcuts = { ...defaultKeyboardShortcuts };
|
||||
const success = await window.electron.saveSettings(settings);
|
||||
if (success) {
|
||||
setShortcuts({ ...defaultKeyboardShortcuts });
|
||||
setShowRestartNotice(true);
|
||||
trackSettingToggled('shortcuts_reset', true);
|
||||
}
|
||||
await window.electron.setSetting('keyboardShortcuts', { ...defaultKeyboardShortcuts });
|
||||
setShortcuts({ ...defaultKeyboardShortcuts });
|
||||
setShowRestartNotice(true);
|
||||
trackSettingToggled('shortcuts_reset', true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,23 +6,24 @@ export const ResponseStylesSection = () => {
|
||||
const [currentStyle, setCurrentStyle] = useState('concise');
|
||||
|
||||
useEffect(() => {
|
||||
const savedStyle = localStorage.getItem('response_style');
|
||||
if (savedStyle) {
|
||||
async function loadResponseStyle() {
|
||||
try {
|
||||
const savedStyle = await window.electron.getSetting('responseStyle');
|
||||
setCurrentStyle(savedStyle);
|
||||
} catch (error) {
|
||||
console.error('Error parsing response style:', error);
|
||||
console.error('Error loading response style:', error);
|
||||
}
|
||||
} else {
|
||||
// Set default to concise for new users
|
||||
localStorage.setItem('response_style', 'concise');
|
||||
setCurrentStyle('concise');
|
||||
}
|
||||
loadResponseStyle();
|
||||
}, []);
|
||||
|
||||
const handleStyleChange = async (newStyle: string) => {
|
||||
setCurrentStyle(newStyle);
|
||||
localStorage.setItem('response_style', newStyle);
|
||||
try {
|
||||
await window.electron.setSetting('responseStyle', newStyle);
|
||||
} catch (error) {
|
||||
console.error('Error saving response style:', error);
|
||||
}
|
||||
|
||||
// Dispatch custom event to notify other components of the change
|
||||
window.dispatchEvent(new CustomEvent(AppEvents.RESPONSE_STYLE_CHANGED));
|
||||
|
||||
@@ -27,25 +27,19 @@ export default function SessionSharingSection() {
|
||||
sessionSharingConfig.enabled &&
|
||||
isValidUrl(String(sessionSharingConfig.baseUrl));
|
||||
|
||||
// Only load saved config from localStorage if the env variable is not provided.
|
||||
// Only load saved config from settings if the env variable is not provided.
|
||||
useEffect(() => {
|
||||
if (envBaseUrlShare) {
|
||||
// If env variable is set, save the forced configuration to localStorage
|
||||
// If env variable is set, save the forced configuration to settings
|
||||
const forcedConfig = {
|
||||
enabled: true,
|
||||
baseUrl: typeof envBaseUrlShare === 'string' ? envBaseUrlShare : '',
|
||||
};
|
||||
localStorage.setItem('session_sharing_config', JSON.stringify(forcedConfig));
|
||||
window.electron.setSetting('sessionSharing', forcedConfig);
|
||||
} else {
|
||||
const savedSessionConfig = localStorage.getItem('session_sharing_config');
|
||||
if (savedSessionConfig) {
|
||||
try {
|
||||
const config = JSON.parse(savedSessionConfig);
|
||||
setSessionSharingConfig(config);
|
||||
} catch (error) {
|
||||
console.error('Error parsing session sharing config:', error);
|
||||
}
|
||||
}
|
||||
window.electron.getSetting('sessionSharing').then((config) => {
|
||||
setSessionSharingConfig(config);
|
||||
});
|
||||
}
|
||||
}, [envBaseUrlShare]);
|
||||
|
||||
@@ -61,20 +55,18 @@ export default function SessionSharingSection() {
|
||||
}
|
||||
|
||||
// Toggle sharing (only allowed when env is not set).
|
||||
const toggleSharing = () => {
|
||||
const toggleSharing = async () => {
|
||||
if (envBaseUrlShare) {
|
||||
return; // Do nothing if the environment variable forces sharing.
|
||||
}
|
||||
setSessionSharingConfig((prev) => {
|
||||
const updated = { ...prev, enabled: !prev.enabled };
|
||||
localStorage.setItem('session_sharing_config', JSON.stringify(updated));
|
||||
trackSettingToggled('session_sharing', updated.enabled);
|
||||
return updated;
|
||||
});
|
||||
const updated = { ...sessionSharingConfig, enabled: !sessionSharingConfig.enabled };
|
||||
setSessionSharingConfig(updated);
|
||||
await window.electron.setSetting('sessionSharing', updated);
|
||||
trackSettingToggled('session_sharing', updated.enabled);
|
||||
};
|
||||
|
||||
// Handle changes to the base URL field
|
||||
const handleBaseUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleBaseUrlChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newBaseUrl = e.target.value;
|
||||
setSessionSharingConfig((prev) => ({
|
||||
...prev,
|
||||
@@ -87,7 +79,7 @@ export default function SessionSharingSection() {
|
||||
if (isValidUrl(newBaseUrl)) {
|
||||
setUrlError('');
|
||||
const updated = { ...sessionSharingConfig, baseUrl: newBaseUrl };
|
||||
localStorage.setItem('session_sharing_config', JSON.stringify(updated));
|
||||
await window.electron.setSetting('sessionSharing', updated);
|
||||
} else {
|
||||
setUrlError('Invalid URL format. Please enter a valid URL (e.g. https://example.com/api).');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user